From 1b2272ad003d5f1a3b3dac1af07035edd874c9e4 Mon Sep 17 00:00:00 2001 From: Gabriel Enrico Date: Mon, 13 Sep 2021 00:03:31 -0400 Subject: [PATCH] feat: Add Vercel Serverless Hosting Support (#121) * feat: Vercel Server Support * Add Docs --- docs/examples/vercel.md | 52 +++++++++++++++++++++++++++++++++++++++++ docs/index.yml | 2 ++ package.json | 1 + src/index.ts | 1 + src/servers/vercel.ts | 44 ++++++++++++++++++++++++++++++++++ yarn.lock | 45 ++++++++++++++++++++++++++++++++++- 6 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 docs/examples/vercel.md create mode 100644 src/servers/vercel.ts diff --git a/docs/examples/vercel.md b/docs/examples/vercel.md new file mode 100644 index 00000000..2ec6b044 --- /dev/null +++ b/docs/examples/vercel.md @@ -0,0 +1,52 @@ +**➡️ Please note that a working templatereference can be found [here](https://github.com/GabrielTK/VercelSlashCreate).** + +The most important thing is to: +1. Export the VercelServer instance: +```ts +const { VercelServer } = require('slash-create'); +const vercelServer = new VercelServer(); +creator + .withServer(vercelServer) + .registerCommandsIn(path.join(__dirname, 'commands')) + //.syncCommands() + +export const vercel = vercelServer; +export const slash = creator; +``` +2. Then, create a directory called `api`, with the two following files: + +* `interactions.ts` +```ts +import { vercel } from ".."; //This is the VercelServer we exported from the previous step. + + +export default vercel.vercelEndpoint; +``` + +* `resync.ts` - This file can be renamed, and will be used to resync the interactions with Discord API. I recommend you use this as a post-deploy hook. + +```ts +import { VercelRequest, VercelResponse } from "@vercel/node"; +import { slash } from ".."; + +const api = async (req: VercelRequest, res: VercelResponse) => { + //I do recommend you add secret verification here, or some security check. + slash.syncCommands(); + //This basically waits until the sync is done + let awaiter = new Promise((resolve,reject) => { + slash.on('synced', () => { + console.log("Elapsed Sync") + resolve(true); + }); + }); + slash.syncCommands(); + await awaiter; + res.status(200).send(JSON.stringify(slash.commands.map(c => [{name: c.commandName, description: c.description}]))); +} +export default api; +``` + +Now, just push it to Vercel! Be sure to verify that the source files are not exposed, and that `/api/interactions` results in a response such as `Server only supports POST requests.` - That's the URL you'll use as your `INTERACTIONS ENDPOINT URL` on your [Discord Developers Page](https://discord.com/developers/) + + + diff --git a/docs/index.yml b/docs/index.yml index 1f2228c8..9867d873 100644 --- a/docs/index.yml +++ b/docs/index.yml @@ -30,3 +30,5 @@ path: lambda.md - name: Azure Functions path: azure.md + - name: Vercel + path: vercel.md diff --git a/package.json b/package.json index 2bdf1f31..4ba2710b 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@types/sinonjs__fake-timers": "6.0.1", "@typescript-eslint/eslint-plugin": "^4.29.1", "@typescript-eslint/parser": "^4.29.1", + "@vercel/node": "^1.12.1", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "chai-nock": "^1.3.0", diff --git a/src/index.ts b/src/index.ts index c1c19c8a..fac60c7a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,7 @@ export * from './servers/express'; export * from './servers/fastify'; export * from './servers/gateway'; export * from './servers/gcf'; +export * from './servers/vercel'; export * from './structures/member'; export * from './structures/message'; diff --git a/src/servers/vercel.ts b/src/servers/vercel.ts new file mode 100644 index 00000000..6250f97e --- /dev/null +++ b/src/servers/vercel.ts @@ -0,0 +1,44 @@ +import { Server, ServerRequestHandler } from '../server'; +// @ts-ignore +import { VercelRequest, VercelResponse } from '@vercel/node'; + +/** + * A server for Vercel. + * @see https://vercel.com/ + * @see https://vercel.com/guides/handling-node-request-body + */ +export class VercelServer extends Server { + private _handler?: ServerRequestHandler; + + /** + * @param moduleExports The exports for your module, must be `module.exports` + * @param target The name of the exported function + */ + constructor() { + super({ alreadyListening: true }); + // moduleExports = this._onRequest.bind(this); + } + + vercelEndpoint = (req: VercelRequest, res: VercelResponse) => { + if (!this._handler) return res.status(503).send('Server has no handler.'); + if (req.method !== 'POST') return res.status(405).send('Server only supports POST requests.'); + this._handler( + { + headers: req.headers, + body: req.body, + request: req, + response: res + }, + async (response) => { + res.status(response.status || 200); + if (response.headers) for (const key in response.headers) res.setHeader(key, response.headers[key] as string); + res.send(response.body); + } + ); + }; + + /** @private */ + createEndpoint(path: string, handler: ServerRequestHandler) { + this._handler = handler; + } +} diff --git a/yarn.lock b/yarn.lock index c9444d9e..77bee627 100644 --- a/yarn.lock +++ b/yarn.lock @@ -184,6 +184,11 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.0.0.tgz#3205bcd15ada9bc681ac20bef64e9e6df88fd297" integrity sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA== +"@types/node@*": + version "16.7.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.13.tgz#86fae356b03b5a12f2506c6cf6cd9287b205973f" + integrity sha512-pLUPDn+YG3FYEt/pHI74HmnJOWzeR+tOIQzUx93pi9M7D8OE7PSLr97HboXwk5F+JS+TLtWuzCOW97AHjmOXXA== + "@types/node@^16.4.14": version "16.7.10" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.10.tgz#7aa732cc47341c12a16b7d562f519c2383b6d4fc" @@ -290,6 +295,15 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== +"@vercel/node@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@vercel/node/-/node-1.12.1.tgz#15f42f64690f904f8a52a387123ce0958657060f" + integrity sha512-NcawIY05BvVkWlsowaxF2hl/hJg475U8JvT2FnGykFPMx31q1/FtqyTw/awSrKfOSRXR0InrbEIDIelmS9NzPA== + dependencies: + "@types/node" "*" + ts-node "8.9.1" + typescript "4.3.4" + abstract-logging@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839" @@ -474,6 +488,11 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -2143,7 +2162,15 @@ sonic-boom@^1.0.2: atomic-sleep "^1.0.0" flatstr "^1.0.12" -source-map@^0.6.1: +source-map-support@^0.5.17: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -2292,6 +2319,17 @@ to-regex-range@^5.0.1: typedoc "^0.20.29" typedoc-plugin-as-member-of "^1.0.2" +ts-node@8.9.1: + version "8.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5" + integrity sha512-yrq6ODsxEFTLz0R3BX2myf0WBCSQh9A+py8PBo1dCzWIOcvisbyH6akNKqDHMgXePF2kir5mm5JXJTH3OUJYOQ== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + ts-node@^10.2.0: version "10.2.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.2.1.tgz#4cc93bea0a7aba2179497e65bb08ddfc198b3ab5" @@ -2398,6 +2436,11 @@ typedoc@^0.20.29: shiki "^0.9.3" typedoc-default-themes "^0.12.10" +typescript@4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.4.tgz#3f85b986945bcf31071decdd96cf8bfa65f9dcbc" + integrity sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew== + typescript@4.3.5: version "4.3.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4"