From 503e908e3650ee9138baa03390589e4a447ad9a6 Mon Sep 17 00:00:00 2001 From: Katalina Okano Date: Sun, 30 Jan 2022 03:35:56 -0500 Subject: [PATCH] remove interactions & worker-utils package, update misc/types --- packages/interactions/bindings.d.ts | 7 -- packages/interactions/handlers/healthz.ts | 5 - packages/interactions/handlers/interaction.ts | 60 --------- .../handlers/interactions/hello-world.ts | 16 --- .../handlers/interactions/pick-role.ts | 86 ------------- .../handlers/interactions/pickable-roles.ts | 63 ---------- .../handlers/interactions/roleypoly.ts | 56 --------- packages/interactions/index.ts | 29 ----- packages/interactions/package.json | 17 --- packages/interactions/tsconfig.json | 15 --- packages/interactions/utils/api.ts | 41 ------ packages/interactions/utils/config.ts | 11 -- packages/interactions/utils/interactions.ts | 83 ------------ packages/interactions/utils/responses.ts | 29 ----- packages/interactions/webpack.config.js | 28 ----- packages/interactions/worker.config.js | 12 -- packages/misc-utils/collection-tools.ts | 26 ++++ packages/types/Session.ts | 2 +- packages/worker-utils/api.ts | 21 ---- packages/worker-utils/discord.ts | 43 ------- packages/worker-utils/index.ts | 4 - packages/worker-utils/kv.ts | 80 ------------ packages/worker-utils/package.json | 10 -- packages/worker-utils/router.ts | 118 ------------------ packages/worker-utils/tsconfig.json | 15 --- 25 files changed, 27 insertions(+), 850 deletions(-) delete mode 100644 packages/interactions/bindings.d.ts delete mode 100644 packages/interactions/handlers/healthz.ts delete mode 100644 packages/interactions/handlers/interaction.ts delete mode 100644 packages/interactions/handlers/interactions/hello-world.ts delete mode 100644 packages/interactions/handlers/interactions/pick-role.ts delete mode 100644 packages/interactions/handlers/interactions/pickable-roles.ts delete mode 100644 packages/interactions/handlers/interactions/roleypoly.ts delete mode 100644 packages/interactions/index.ts delete mode 100644 packages/interactions/package.json delete mode 100644 packages/interactions/tsconfig.json delete mode 100644 packages/interactions/utils/api.ts delete mode 100644 packages/interactions/utils/config.ts delete mode 100644 packages/interactions/utils/interactions.ts delete mode 100644 packages/interactions/utils/responses.ts delete mode 100644 packages/interactions/webpack.config.js delete mode 100644 packages/interactions/worker.config.js create mode 100644 packages/misc-utils/collection-tools.ts delete mode 100644 packages/worker-utils/api.ts delete mode 100644 packages/worker-utils/discord.ts delete mode 100644 packages/worker-utils/index.ts delete mode 100644 packages/worker-utils/kv.ts delete mode 100644 packages/worker-utils/package.json delete mode 100644 packages/worker-utils/router.ts delete mode 100644 packages/worker-utils/tsconfig.json diff --git a/packages/interactions/bindings.d.ts b/packages/interactions/bindings.d.ts deleted file mode 100644 index f28be01..0000000 --- a/packages/interactions/bindings.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export {}; - -declare global { - const DISCORD_PUBLIC_KEY: string; - const UI_PUBLIC_URI: string; - const API_PUBLIC_URI: string; -} diff --git a/packages/interactions/handlers/healthz.ts b/packages/interactions/handlers/healthz.ts deleted file mode 100644 index dc90791..0000000 --- a/packages/interactions/handlers/healthz.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { respond } from '@roleypoly/worker-utils'; - -export const healthz = async (request: Request): Promise => { - return respond({ ok: true }); -}; diff --git a/packages/interactions/handlers/interaction.ts b/packages/interactions/handlers/interaction.ts deleted file mode 100644 index 87c976a..0000000 --- a/packages/interactions/handlers/interaction.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - InteractionData, - InteractionRequest, - InteractionRequestCommand, - InteractionType, -} from '@roleypoly/types'; -import { HandlerTools, respond } from '@roleypoly/worker-utils'; -import { CommandHandler, verifyRequest } from '../utils/interactions'; -import { somethingWentWrong } from '../utils/responses'; -import { helloWorld } from './interactions/hello-world'; -import { pickRole } from './interactions/pick-role'; -import { pickableRoles } from './interactions/pickable-roles'; -import { roleypoly } from './interactions/roleypoly'; - -const commands: Record = { - 'hello-world': helloWorld, - roleypoly: roleypoly, - 'pickable-roles': pickableRoles, - 'pick-role': pickRole('add'), - 'remove-role': pickRole('remove'), -}; - -export const interactionHandler = async ( - request: Request, - { waitUntil }: HandlerTools -): Promise => { - const interaction = (await request.json()) as InteractionRequest; - - if (!verifyRequest(request, interaction)) { - return new Response('invalid request signature', { status: 401 }); - } - - if (interaction.type === InteractionType.PING) { - return respond({ type: 1 }); - } - - if (interaction.type !== InteractionType.APPLICATION_COMMAND) { - return respond({ err: 'not implemented' }, { status: 400 }); - } - - if (!interaction.data) { - return respond({ err: 'data missing' }, { status: 400 }); - } - - const handler = commands[interaction.data.name]; - if (!handler) { - return respond({ err: 'not implemented' }, { status: 400 }); - } - - try { - const response = await handler(interaction as InteractionRequestCommand, { - request, - waitUntil, - }); - return respond(response); - } catch (e) { - console.error(e); - return respond(somethingWentWrong()); - } -}; diff --git a/packages/interactions/handlers/interactions/hello-world.ts b/packages/interactions/handlers/interactions/hello-world.ts deleted file mode 100644 index 990ffe7..0000000 --- a/packages/interactions/handlers/interactions/hello-world.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { - InteractionCallbackType, - InteractionRequestCommand, - InteractionResponse, -} from '@roleypoly/types'; - -export const helloWorld = async ( - interaction: InteractionRequestCommand -): Promise => { - return { - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Hey there, ${interaction.member?.nick || interaction.user?.username}`, - }, - }; -}; diff --git a/packages/interactions/handlers/interactions/pick-role.ts b/packages/interactions/handlers/interactions/pick-role.ts deleted file mode 100644 index 52c0864..0000000 --- a/packages/interactions/handlers/interactions/pick-role.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { selectRole } from '@roleypoly/interactions/utils/api'; -import { - asyncPreflightEphemeral, - asyncResponse, -} from '@roleypoly/interactions/utils/interactions'; -import { invalid, mustBeInGuild } from '@roleypoly/interactions/utils/responses'; -import { - InteractionCallbackType, - InteractionFlags, - InteractionRequestCommand, - InteractionResponse, -} from '@roleypoly/types'; - -export const pickRole = (mode: 'add' | 'remove') => - asyncResponse( - async (interaction: InteractionRequestCommand): Promise => { - if (!interaction.guild_id) { - return mustBeInGuild(); - } - - const userID = interaction.member?.user?.id; - if (!userID) { - return mustBeInGuild(); - } - - const roleID = interaction.data.options?.find( - (option) => option.name === 'role' - )?.value; - if (!roleID) { - return invalid(); - } - - const code = await selectRole(mode, interaction.guild_id, userID, roleID); - - if (code === 409) { - return { - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `:x: You ${mode === 'add' ? 'already' : "don't"} have that role.`, - flags: InteractionFlags.EPHEMERAL, - }, - }; - } - - if (code === 404) { - return { - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `:x: <@&${roleID}> isn't pickable.`, - flags: InteractionFlags.EPHEMERAL, - }, - }; - } - - if (code === 403) { - return { - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `:x: <@&${roleID}> has unsafe permissions.`, - flags: InteractionFlags.EPHEMERAL, - }, - }; - } - - if (code !== 200) { - return { - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `:x: Something went wrong, please try again later.`, - flags: InteractionFlags.EPHEMERAL, - }, - }; - } - - return { - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `:white_check_mark: You ${ - mode === 'add' ? 'got' : 'removed' - } the role: <@&${roleID}>`, - flags: InteractionFlags.EPHEMERAL, - }, - }; - }, - asyncPreflightEphemeral - ); diff --git a/packages/interactions/handlers/interactions/pickable-roles.ts b/packages/interactions/handlers/interactions/pickable-roles.ts deleted file mode 100644 index a1a253a..0000000 --- a/packages/interactions/handlers/interactions/pickable-roles.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { getPickableRoles } from '@roleypoly/interactions/utils/api'; -import { uiPublicURI } from '@roleypoly/interactions/utils/config'; -import { - asyncPreflightEphemeral, - asyncResponse, -} from '@roleypoly/interactions/utils/interactions'; -import { mustBeInGuild } from '@roleypoly/interactions/utils/responses'; -import { - CategoryType, - Embed, - InteractionCallbackType, - InteractionFlags, - InteractionRequestCommand, - InteractionResponse, -} from '@roleypoly/types'; - -export const pickableRoles = asyncResponse( - async (interaction: InteractionRequestCommand): Promise => { - if (!interaction.guild_id) { - return mustBeInGuild(); - } - - const pickableRoles = await getPickableRoles(interaction.guild_id); - const embed: Embed = { - color: 0xab9b9a, - fields: [], - title: 'You can pick any of these roles with /pick-role', - }; - - for (let categoryName in pickableRoles) { - const { roles, type } = pickableRoles[categoryName]; - - embed.fields.push({ - name: `${categoryName}${type === CategoryType.Single ? ' *(pick one)*' : ''}`, - value: roles.map((role) => `<@&${role}>`).join('\n'), - inline: true, - }); - } - - return { - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - embeds: [embed], - flags: InteractionFlags.EPHEMERAL, - components: [ - { - type: 1, - components: [ - // Link to Roleypoly - { - type: 2, - label: 'Pick roles on your browser', - url: `${uiPublicURI}/s/${interaction.guild_id}`, - style: 5, - }, - ], - }, - ], - }, - }; - }, - asyncPreflightEphemeral -); diff --git a/packages/interactions/handlers/interactions/roleypoly.ts b/packages/interactions/handlers/interactions/roleypoly.ts deleted file mode 100644 index f31ce2d..0000000 --- a/packages/interactions/handlers/interactions/roleypoly.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { uiPublicURI } from '@roleypoly/interactions/utils/config'; -import { - Embed, - InteractionCallbackType, - InteractionFlags, - InteractionRequestCommand, - InteractionResponse, -} from '@roleypoly/types'; - -export const roleypoly = async ( - interaction: InteractionRequestCommand -): Promise => { - if (interaction.guild_id) { - return { - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - embeds: [ - { - color: 0x453e3d, - title: `:beginner: Hey there, ${ - interaction.member?.nick || interaction.member?.user?.username || 'friend' - }!`, - description: `Try these slash commands, or pick roles from your browser!`, - fields: [ - { name: 'See all the roles', value: '/pickable-roles' }, - { name: 'Pick a role', value: '/pick-role' }, - { name: 'Remove a role', value: '/remove-role' }, - ], - } as Embed, - ], - components: [ - { - type: 1, - components: [ - // Link to Roleypoly - { - type: 2, - label: `Pick roles on ${new URL(uiPublicURI).hostname}`, - url: `${uiPublicURI}/s/${interaction.guild_id}`, - style: 5, - }, - ], - }, - ], - flags: InteractionFlags.EPHEMERAL, - }, - }; - } - - return { - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `:beginner: Hey! I don't know what server you're in, so check out ${uiPublicURI}`, - }, - }; -}; diff --git a/packages/interactions/index.ts b/packages/interactions/index.ts deleted file mode 100644 index 3d77ef7..0000000 --- a/packages/interactions/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { interactionHandler } from '@roleypoly/interactions/handlers/interaction'; -import { respond } from '@roleypoly/worker-utils'; -import { Router } from '@roleypoly/worker-utils/router'; -import { healthz } from './handlers/healthz'; -import { uiPublicURI } from './utils/config'; - -const router = new Router(); - -router.add('GET', '_healthz', healthz); -router.add('POST', 'interactions', interactionHandler); - -// Root Zen <3 -router.addFallback('root', () => { - return respond({ - __warning: '🦊', - this: 'is', - a: 'fox-based', - web: 'application', - please: 'be', - mindful: 'of', - your: 'surroundings', - warning__: '🦊', - meta: uiPublicURI, - }); -}); - -addEventListener('fetch', (event: FetchEvent) => { - event.respondWith(router.handle(event)); -}); diff --git a/packages/interactions/package.json b/packages/interactions/package.json deleted file mode 100644 index 5b78e15..0000000 --- a/packages/interactions/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@roleypoly/interactions", - "version": "0.1.0", - "scripts": { - "build": "yarn workspace @roleypoly/worker-emulator build --basePath `pwd`", - "lint:types": "tsc --noEmit", - "start": "cfw-emulator" - }, - "devDependencies": { - "@cloudflare/workers-types": "^2.2.2", - "@roleypoly/types": "*", - "@roleypoly/worker-emulator": "*", - "@roleypoly/worker-utils": "*", - "@types/node": "^16.4.10", - "tweetnacl": "^1.0.3" - } -} diff --git a/packages/interactions/tsconfig.json b/packages/interactions/tsconfig.json deleted file mode 100644 index 2c330c0..0000000 --- a/packages/interactions/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./dist", - "lib": ["esnext", "webworker", "ES2020.BigInt", "ES2020.Promise"], - "types": ["@cloudflare/workers-types", "node"], - "target": "ES2019" - }, - "include": [ - "./*.ts", - "./**/*.ts", - "../../node_modules/@cloudflare/workers-types/index.d.ts" - ], - "exclude": ["./**/*.spec.ts", "./dist/**"], - "extends": "../../tsconfig.json" -} diff --git a/packages/interactions/utils/api.ts b/packages/interactions/utils/api.ts deleted file mode 100644 index 2ec79ed..0000000 --- a/packages/interactions/utils/api.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Category, CategorySlug } from '@roleypoly/types'; -import { apiPublicURI, interactionsSharedKey } from './config'; - -export const apiFetch = (url: string, init: RequestInit = {}) => - fetch(`${apiPublicURI}${url}`, { - ...init, - headers: { - ...(init.headers || {}), - authorization: `Shared ${interactionsSharedKey}`, - }, - }); - -export const getPickableRoles = async ( - guildID: string -): Promise> => { - const response = await apiFetch(`/interactions-pickable-roles/${guildID}`); - - if (response.status !== 200) { - throw new Error( - `API request failed to /interactions-pickable-roles, got code: ${response.status}` - ); - } - - return (await response.json()) as Record; -}; - -export const selectRole = async ( - mode: 'add' | 'remove', - guildID: string, - userID: string, - roleID: string -): Promise => { - const response = await apiFetch( - `/interactions-pick-role/${guildID}/${userID}/${roleID}`, - { - method: mode === 'add' ? 'PUT' : 'DELETE', - } - ); - - return response.status; -}; diff --git a/packages/interactions/utils/config.ts b/packages/interactions/utils/config.ts deleted file mode 100644 index cba2f1c..0000000 --- a/packages/interactions/utils/config.ts +++ /dev/null @@ -1,11 +0,0 @@ -const self = global as any as Record; - -const env = (key: string) => self[key] ?? ''; - -const safeURI = (x: string) => x.replace(/\/$/, ''); -const list = (x: string) => x.split(','); - -export const uiPublicURI = safeURI(env('UI_PUBLIC_URI')); -export const apiPublicURI = safeURI(env('API_PUBLIC_URI')); -export const publicKey = safeURI(env('DISCORD_PUBLIC_KEY')); -export const interactionsSharedKey = env('INTERACTIONS_SHARED_KEY'); diff --git a/packages/interactions/utils/interactions.ts b/packages/interactions/utils/interactions.ts deleted file mode 100644 index d2f1eb4..0000000 --- a/packages/interactions/utils/interactions.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { publicKey } from '@roleypoly/interactions/utils/config'; -import { - InteractionCallbackType, - InteractionFlags, - InteractionRequest, - InteractionRequestCommand, - InteractionResponse, -} from '@roleypoly/types'; -import { AuthType, discordFetch, HandlerTools } from '@roleypoly/worker-utils'; -import nacl from 'tweetnacl'; - -export const verifyRequest = ( - request: Request, - interaction: InteractionRequest -): boolean => { - const timestamp = request.headers.get('x-signature-timestamp'); - const signature = request.headers.get('x-signature-ed25519'); - - if (!timestamp || !signature) { - return false; - } - - if ( - !nacl.sign.detached.verify( - Buffer.from(timestamp + JSON.stringify(interaction)), - Buffer.from(signature, 'hex'), - Buffer.from(publicKey, 'hex') - ) - ) { - return false; - } - - return true; -}; - -export type RequestInfo = HandlerTools & { request: Request }; - -export type CommandHandler = ( - request: InteractionRequestCommand, - requestInfo: RequestInfo -) => Promise; - -export const asyncResponse = - ( - handler: CommandHandler, - preflight?: () => InteractionResponse['data'] - ): CommandHandler => - async ( - command: InteractionRequestCommand, - requestInfo: RequestInfo - ): Promise => { - requestInfo.waitUntil( - (async () => { - const response = await handler(command, requestInfo); - await updateOriginalMessage(command.application_id, command.token, response); - })() - ); - - return { - type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, - data: preflight ? preflight() : undefined, - }; - }; - -export const asyncPreflightEphemeral = () => ({ - flags: InteractionFlags.EPHEMERAL, -}); - -const updateOriginalMessage = async ( - appID: string, - token: string, - response: InteractionResponse -) => { - const url = `/webhooks/${appID}/${token}/messages/@original`; - - return await discordFetch(url, '', AuthType.None, { - method: 'PATCH', - body: JSON.stringify(response.data), - headers: { - 'content-type': 'application/json', - }, - }); -}; diff --git a/packages/interactions/utils/responses.ts b/packages/interactions/utils/responses.ts deleted file mode 100644 index f79dccc..0000000 --- a/packages/interactions/utils/responses.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - InteractionCallbackType, - InteractionFlags, - InteractionResponse, -} from '@roleypoly/types'; - -export const mustBeInGuild = (): InteractionResponse => ({ - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: ':x: This command has to be used in a server.', - flags: InteractionFlags.EPHEMERAL, - }, -}); - -export const invalid = (): InteractionResponse => ({ - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: ':x: You filled that command out wrong...', - flags: InteractionFlags.EPHEMERAL, - }, -}); - -export const somethingWentWrong = (): InteractionResponse => ({ - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: ' Something went terribly wrong.', - flags: InteractionFlags.EPHEMERAL, - }, -}); diff --git a/packages/interactions/webpack.config.js b/packages/interactions/webpack.config.js deleted file mode 100644 index bc319c2..0000000 --- a/packages/interactions/webpack.config.js +++ /dev/null @@ -1,28 +0,0 @@ -const path = require('path'); - -const mode = process.env.NODE_ENV || 'production'; - -module.exports = { - target: 'webworker', - entry: path.join(__dirname, 'index.ts'), - output: { - filename: `worker.${mode}.js`, - path: path.join(__dirname, 'dist'), - }, - mode, - resolve: { - extensions: ['.ts', '.tsx', '.js'], - }, - module: { - rules: [ - { - test: /\.tsx?$/, - loader: 'ts-loader', - options: { - transpileOnly: true, - configFile: path.join(__dirname, 'tsconfig.json'), - }, - }, - ], - }, -}; diff --git a/packages/interactions/worker.config.js b/packages/interactions/worker.config.js deleted file mode 100644 index 8baa69e..0000000 --- a/packages/interactions/worker.config.js +++ /dev/null @@ -1,12 +0,0 @@ -const reexportEnv = (keys = []) => { - return keys.reduce((acc, key) => ({ ...acc, [key]: process.env[key] }), {}); -}; - -module.exports = { - environment: reexportEnv([ - 'DISCORD_PUBLIC_KEY', - 'UI_PUBLIC_URI', - 'API_PUBLIC_URI', - 'INTERACTIONS_SHARED_KEY', - ]), -}; diff --git a/packages/misc-utils/collection-tools.ts b/packages/misc-utils/collection-tools.ts new file mode 100644 index 0000000..2612544 --- /dev/null +++ b/packages/misc-utils/collection-tools.ts @@ -0,0 +1,26 @@ +export const difference = (...arrays: T[][]) => { + return arrays.reduce((a, b) => a.filter((v) => !b.includes(v))); +}; + +export const groupBy = (arr: Record[], key: string) => { + return arr.reduce((r, a) => { + r[a[key]] = [...r[a[key]], a]; + return r; + }, {}); +}; + +export const keyBy = (arr: Record[], key: string) => { + return arr.reduce((r, a) => { + r[a[key]] = a; + return r; + }, {}); +}; + +export const union = (...arrays: T[][]) => { + return arrays.reduce((a, b) => [...a, ...b]); +}; + +export const isIdenticalArray = (a1: T[], a2: T[]) => { + // DEIs: http://stackoverflow.com/a/40496893/308645 + return a1.length === a2.length && a1.reduce((a, b) => a && a2.includes(b), true); +}; diff --git a/packages/types/Session.ts b/packages/types/Session.ts index 6754716..5066ffe 100644 --- a/packages/types/Session.ts +++ b/packages/types/Session.ts @@ -21,7 +21,7 @@ export type SessionData = { tokens: AuthTokenResponse; user: DiscordUser; guilds: GuildSlug[]; - flags: SessionFlags; + // flags: SessionFlags; }; export type StateSession = { diff --git a/packages/worker-utils/api.ts b/packages/worker-utils/api.ts deleted file mode 100644 index 26c7491..0000000 --- a/packages/worker-utils/api.ts +++ /dev/null @@ -1,21 +0,0 @@ -export const respond = (obj: Record, init: ResponseInit = {}) => - new Response(JSON.stringify(obj), { - ...init, - headers: { - ...(init.headers || {}), - 'content-type': 'application/json', - }, - }); - -export const userAgent = - 'DiscordBot (https://github.com/roleypoly/roleypoly, git-main) (+https://roleypoly.com)'; - -export const getQuery = (request: Request): { [x: string]: string } => { - const output: { [x: string]: string } = {}; - - for (let [key, value] of new URL(request.url).searchParams.entries()) { - output[key] = value; - } - - return output; -}; diff --git a/packages/worker-utils/discord.ts b/packages/worker-utils/discord.ts deleted file mode 100644 index 18b1716..0000000 --- a/packages/worker-utils/discord.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { userAgent } from './api'; - -export const discordAPIBase = 'https://discordapp.com/api/v9'; - -export enum AuthType { - Bearer = 'Bearer', - Bot = 'Bot', - None = 'None', -} - -export const discordFetch = async ( - url: string, - auth: string, - authType: AuthType = AuthType.Bearer, - init?: RequestInit -): Promise => { - const response = await fetch(discordAPIBase + url, { - ...(init || {}), - headers: { - ...(init?.headers || {}), - ...(authType !== AuthType.None - ? { - authorization: `${AuthType[authType]} ${auth}`, - } - : {}), - 'user-agent': userAgent, - }, - }); - - if (response.status >= 400) { - console.error('discordFetch failed', { - url, - authType, - payload: await response.text(), - }); - } - - if (response.ok) { - return (await response.json()) as T; - } else { - return null; - } -}; diff --git a/packages/worker-utils/index.ts b/packages/worker-utils/index.ts deleted file mode 100644 index 1bfa41f..0000000 --- a/packages/worker-utils/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './api'; -export * from './discord'; -export * from './kv'; -export * from './router'; diff --git a/packages/worker-utils/kv.ts b/packages/worker-utils/kv.ts deleted file mode 100644 index 7c9339b..0000000 --- a/packages/worker-utils/kv.ts +++ /dev/null @@ -1,80 +0,0 @@ -export class WrappedKVNamespace { - constructor(private kvNamespace: KVNamespace) {} - - async get(key: string): Promise { - const data = await this.kvNamespace.get(key, 'text'); - if (!data) { - return null; - } - - return JSON.parse(data) as T; - } - - async put(key: string, value: T, ttlSeconds?: number) { - await this.kvNamespace.put(key, JSON.stringify(value), { - expirationTtl: ttlSeconds, - }); - } - - list = this.kvNamespace.list; - getWithMetadata = this.kvNamespace.getWithMetadata; - delete = this.kvNamespace.delete; -} - -class EmulatedKV implements KVNamespace { - constructor() { - console.warn('EmulatedKV used. Data will be lost.'); - } - - private data: Map = new Map(); - - async get(key: string): Promise { - if (!this.data.has(key)) { - return null; - } - - return this.data.get(key); - } - - async getWithMetadata( - key: string - ): KVValueWithMetadata { - return { - value: await this.get(key), - metadata: {} as Metadata, - }; - } - - async put(key: string, value: string | ReadableStream | ArrayBuffer | FormData) { - this.data.set(key, value); - } - - async delete(key: string) { - this.data.delete(key); - } - - async list(options?: { prefix?: string; limit?: number; cursor?: string }): Promise<{ - keys: { name: string; expiration?: number; metadata?: unknown }[]; - list_complete: boolean; - cursor: string; - }> { - let keys: { name: string }[] = []; - - for (let key of this.data.keys()) { - if (options?.prefix && !key.startsWith(options.prefix)) { - continue; - } - - keys.push({ name: key }); - } - - return { - keys, - cursor: '0', - list_complete: true, - }; - } -} - -export const kvOrLocal = (namespace: KVNamespace | null): KVNamespace => - namespace || new EmulatedKV(); diff --git a/packages/worker-utils/package.json b/packages/worker-utils/package.json deleted file mode 100644 index 657b76e..0000000 --- a/packages/worker-utils/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@roleypoly/worker-utils", - "version": "0.1.0", - "scripts": { - "lint:types": "tsc --noEmit" - }, - "devDependencies": { - "@cloudflare/workers-types": "^2.2.2" - } -} diff --git a/packages/worker-utils/router.ts b/packages/worker-utils/router.ts deleted file mode 100644 index 2ca16d3..0000000 --- a/packages/worker-utils/router.ts +++ /dev/null @@ -1,118 +0,0 @@ -export type Handler = ( - request: Request, - tools: HandlerTools -) => Promise | Response; - -export type HandlerTools = { - waitUntil: FetchEvent['waitUntil']; -}; - -type RoutingTree = { - [method: string]: { - [path: string]: Handler; - }; -}; - -type Fallbacks = { - root: Handler; - 404: Handler; - 500: Handler; -}; - -export class Router { - private routingTree: RoutingTree = {}; - private fallbacks: Fallbacks = { - root: this.respondToRoot, - 404: this.notFound, - 500: this.serverError, - }; - - private corsOrigins: string[] = []; - - addCORSOrigins(origins: string[]) { - this.corsOrigins = [...this.corsOrigins, ...origins]; - } - - addFallback(which: keyof Fallbacks, handler: Handler) { - this.fallbacks[which] = handler; - } - - add(method: string, rootPath: string, handler: Handler) { - const lowerMethod = method.toLowerCase(); - - if (!this.routingTree[lowerMethod]) { - this.routingTree[lowerMethod] = {}; - } - - this.routingTree[lowerMethod][rootPath] = handler; - } - - async handle(event: FetchEvent): Promise { - const response = await this.processRequest(event); - this.injectCORSHeaders(event.request, response.headers); - return response; - } - - private async processRequest({ request, waitUntil }: FetchEvent): Promise { - const url = new URL(request.url); - - if (url.pathname === '/' || url.pathname === '') { - return this.fallbacks.root(request, { waitUntil }); - } - const lowerMethod = request.method.toLowerCase(); - const rootPath = url.pathname.split('/')[1]; - const handler = this.routingTree[lowerMethod]?.[rootPath]; - - if (handler) { - try { - const response = await handler(request, { waitUntil }); - return response; - } catch (e) { - console.error(e); - return this.fallbacks[500](request, { waitUntil }); - } - } - - if (lowerMethod === 'options') { - return new Response(null, {}); - } - - return this.fallbacks[404](request, { waitUntil }); - } - - private respondToRoot(): Response { - return new Response('Hi there!'); - } - - private notFound(): Response { - return new Response(JSON.stringify({ error: 'not_found' }), { - status: 404, - }); - } - - private serverError(): Response { - return new Response(JSON.stringify({ error: 'internal_server_error' }), { - status: 500, - }); - } - - private injectCORSHeaders(request: Request, headers: Headers) { - headers.set('access-control-allow-methods', '*'); - headers.set('access-control-allow-headers', '*'); - - if (this.corsOrigins.length === 0) { - headers.set('access-control-allow-origin', '*'); - return; - } - - const originHeader = request.headers.get('origin'); - if (!originHeader) { - return; - } - - const originHostname = new URL(originHeader).hostname; - if (this.corsOrigins.includes(originHostname)) { - headers.set('access-control-allow-origin', originHostname); - } - } -} diff --git a/packages/worker-utils/tsconfig.json b/packages/worker-utils/tsconfig.json deleted file mode 100644 index 1b989a0..0000000 --- a/packages/worker-utils/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./dist", - "lib": ["esnext", "webworker", "ES2020.BigInt", "ES2020.Promise"], - "types": ["@cloudflare/workers-types"], - "target": "ES2019" - }, - "include": [ - "./*.ts", - "./**/*.ts", - "../../node_modules/@cloudflare/workers-types/index.d.ts" - ], - "exclude": ["./**/*.spec.ts", "./dist/**"], - "extends": "../../tsconfig.json" -}