Skip to content

Commit 8d23577

Browse files
initialize
0 parents  commit 8d23577

File tree

8 files changed

+477
-0
lines changed

8 files changed

+477
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package-lock.json
2+
node_modules
3+
dist
4+
tsconfig.tsbuildinfo

.npmignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.github
2+
node_modules
3+
src
4+
tsconfig.json
5+
tsconfig.tsbuildinfo

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 discord.https
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Vercel Adapter
2+
3+
[![npm version](https://img.shields.io/npm/v/@discordhttps/vercel-adapter.svg)](https://www.npmjs.com/package/@discordhttps/vercel-adapter)
4+
[![License](https://img.shields.io/npm/l/@discordhttps/vercel-adapter.svg)](LICENSE)
5+
[![Downloads](https://img.shields.io/npm/dm/@discordhttps/vercel-adapter.svg)](https://www.npmjs.com/package/@discordhttps/vercel-adapter)
6+
7+
**@discordhttps/vercel-adapter** is an adapter for integrating [**discordhttps**](https://www.npmjs.com/package/discordhttps) with [**Vercel**](https://vercel.com).
8+
9+
## Installation
10+
11+
```bash
12+
npm install @discordhttps/vercel-adapter discordhttps
13+
```
14+
15+
## Usage
16+
17+
```typescript
18+
import Client from "discordhttps";
19+
import VercelAdapter from "@discordhttps/vercel-adapter";
20+
21+
const adapter = new VercelAdapter();
22+
23+
export default async function handler(req, res) {
24+
const client = new Client({
25+
token: process.env.DISCORD_BOT_TOKEN,
26+
publicKey: process.env.DISCORD_PUBLIC_KEY,
27+
httpAdapter: adapter,
28+
debug: true,
29+
});
30+
31+
// Mount routers
32+
client.register(UtilityRoute, HelloRoute);
33+
34+
// Handle Discord interactions on the "/interactions" endpoint
35+
return await client.listen("interactions", req, res);
36+
}
37+
```

package.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "@discordhttps/vercel-adapter",
3+
"version": "1.0.2",
4+
"description": "An adapter for integrating discord.https with Vercel.",
5+
"main": "./dist/index.js",
6+
"type": "module",
7+
"scripts": {
8+
"build": "tsc --build"
9+
},
10+
"types": "./dist/index.d.ts",
11+
"files": [
12+
"dist",
13+
"README.md"
14+
],
15+
"exports": {
16+
".": {
17+
"import": "./dist/index.js",
18+
"types": "./dist/index.d.ts"
19+
}
20+
},
21+
"repository": {
22+
"type": "git",
23+
"url": "https://github.com/discord-http/Vercel-adapter.git"
24+
},
25+
"author": "discord.https",
26+
"license": "MIT",
27+
"keywords": [
28+
"discord-Vercel",
29+
"discord",
30+
"discord.js",
31+
"discord-http-interactions",
32+
"discord-interactions",
33+
"http-interactions",
34+
"discord.https",
35+
"discord-api",
36+
"interaction-router"
37+
],
38+
"devDependencies": {
39+
"@vercel/node": "^5.3.24"
40+
}
41+
}

src/index.ts

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import type { VercelRequest, VercelResponse } from "@vercel/node";
2+
export interface HttpAdapter {
3+
listen(
4+
endpoint: string,
5+
handler: (req: any, res: any) => Promise<any>,
6+
...args: any[]
7+
): Promise<any> | any;
8+
9+
getRequestBody(req: any): Promise<Uint8Array>;
10+
}
11+
12+
export interface HttpAdapterRequest {
13+
method: string;
14+
url: string;
15+
headers: Record<string, string | string[]>;
16+
}
17+
18+
export interface HttpAdapterSererResponse {
19+
headersSent: boolean;
20+
writeHead(status: number, headers?: Record<string, string>): void;
21+
end(chunk?: string | Uint8Array): void;
22+
}
23+
24+
class VercelServerResponse implements HttpAdapterSererResponse {
25+
private statusCode = 200;
26+
private headers: Record<string, string> = {};
27+
private chunks: Uint8Array[] = [];
28+
29+
public headersSent: boolean = false;
30+
private resolved = false;
31+
private resolveResponsePromise?: () => void;
32+
33+
constructor(private response: VercelResponse) {
34+
this.response = response;
35+
}
36+
writeHead(status: number, headers?: Record<string, string>) {
37+
if (this.headersSent) {
38+
throw new Error("Cannot modify headers after they have been sent.");
39+
}
40+
this.statusCode = status;
41+
if (headers) Object.assign(this.headers, headers);
42+
}
43+
44+
end(chunk?: string | Uint8Array) {
45+
if (this.headersSent) {
46+
throw new Error("Cannot send body after headers have been sent.");
47+
}
48+
if (chunk)
49+
this.chunks.push(
50+
typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk
51+
);
52+
this.headersSent = true;
53+
54+
if (!this.resolved) {
55+
this.resolved = true;
56+
57+
if (this.resolveResponsePromise) {
58+
this.resolveResponsePromise(); // promise revoled
59+
}
60+
}
61+
}
62+
63+
toResponse() {
64+
// let body: Uint8Array | null = null;
65+
const totalLength = this.chunks.reduce((sum, c) => sum + c.length, 0);
66+
// discord do support chunking but whatever
67+
Object.assign(this.headers, {
68+
"content-length": totalLength,
69+
});
70+
71+
this.response.writeHead(this.statusCode, this.headers);
72+
for (const chunk of this.chunks) {
73+
this.response.write(chunk);
74+
}
75+
return this.response.end();
76+
}
77+
78+
async waitForResponse(): Promise<void> {
79+
return new Promise((resolve) => {
80+
if (this.resolved) {
81+
resolve();
82+
} else {
83+
this.resolveResponsePromise = resolve; // Resolve when end() is called
84+
}
85+
});
86+
}
87+
}
88+
89+
class VercelIncomingMessage implements HttpAdapterRequest {
90+
headers: Record<string, string | string[]> = {};
91+
url: string;
92+
method: string;
93+
constructor(private request: VercelRequest) {
94+
this.headers = request.headers as any;
95+
this.url = request.url ?? "";
96+
this.method = request.method ?? "GET";
97+
this.request = request;
98+
}
99+
async arrayBuffer(): Promise<ArrayBuffer> {
100+
// https://vercel.com/docs/functions/runtimes/node-js#request-body
101+
102+
try {
103+
const contentType = this.headers["content-type"] as string | undefined;
104+
105+
if (!contentType?.startsWith("application/json")) {
106+
// Instead of throwing, return an empty ArrayBuffer
107+
// or mark it as invalid so the handler can respond
108+
return new ArrayBuffer(0);
109+
}
110+
111+
const json = this.request.body;
112+
113+
if (!json || typeof json !== "object") {
114+
return new ArrayBuffer(0);
115+
}
116+
117+
const encoded = new TextEncoder().encode(JSON.stringify(json));
118+
return encoded.buffer;
119+
} catch {
120+
// When the request body contains malformed JSON, accessing request.body will throw an error.
121+
return new ArrayBuffer(0);
122+
}
123+
}
124+
}
125+
126+
/**
127+
* Adapter for using discord.https with vercel.
128+
*
129+
* This class implements the HttpAdapter interface and provides
130+
* methods to handle incoming requests and send responses in a
131+
* vercel node environment.
132+
*
133+
*
134+
*
135+
* @example
136+
* const adapter = new VercelAdapter();
137+
*
138+
* export default async function handler(req, res) {
139+
* const client = new Client({
140+
* token: process.env.DISCORD_BOT_TOKEN,
141+
* publicKey: process.env.DISCORD_PUBLIC_KEY,
142+
* httpAdapter: adapter,
143+
* debug: true,
144+
* });
145+
*
146+
* // Register your routes
147+
* client.register(UtilityRoute, HelloRoute);
148+
*
149+
* // Handle Discord interactions on the "/interactions" endpoint
150+
* return await client.listen("interactions", request, response);
151+
* }
152+
*/
153+
154+
class VercelAdapter implements HttpAdapter {
155+
/**
156+
*
157+
*
158+
*/
159+
async listen(
160+
endpoint: string,
161+
handler: (req: any, res: any) => Promise<void>,
162+
request: VercelRequest,
163+
response: VercelResponse
164+
) {
165+
const req = new VercelIncomingMessage(request);
166+
const res = new VercelServerResponse(response);
167+
await handler(req, res);
168+
await res.waitForResponse();
169+
return res.toResponse();
170+
}
171+
172+
/**
173+
*
174+
* Reads the request body as a Uint8Array.
175+
*
176+
*/
177+
async getRequestBody(req: VercelIncomingMessage): Promise<Uint8Array> {
178+
return req.arrayBuffer().then((buffer) => new Uint8Array(buffer));
179+
}
180+
}
181+
export default VercelAdapter;

0 commit comments

Comments
 (0)