@server/next 0.25.10 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +2005 -0
- package/package.json +17 -13
- package/readme.md +1 -0
- package/src/{helpers/jsx.js → jsx/jsx-dev-runtime.js} +32 -17
- package/src/{helpers/jsx.test.jsx → jsx/jsx-runtime.test.jsx} +1 -1
- package/src/ServerError.js +0 -27
- package/src/auth/NoSession.js +0 -19
- package/src/auth/auth-cookie.test.js +0 -42
- package/src/auth/auth-token.test.js +0 -110
- package/src/auth/auth.js +0 -62
- package/src/auth/index.js +0 -104
- package/src/auth/index.test.js +0 -133
- package/src/auth/logout.js +0 -20
- package/src/auth/providers/email.js +0 -145
- package/src/auth/providers/github.js +0 -84
- package/src/auth/providers/index.js +0 -4
- package/src/auth/session.js +0 -18
- package/src/auth/updateUser.js +0 -6
- package/src/auth/user.js +0 -9
- package/src/context/node.js +0 -68
- package/src/context/parseBody.js +0 -107
- package/src/context/parseBody.test.js +0 -60
- package/src/context/parseCookies.js +0 -9
- package/src/context/winter.js +0 -46
- package/src/errors/index.js +0 -36
- package/src/helpers/StatusError.js +0 -6
- package/src/helpers/bucket.js +0 -89
- package/src/helpers/bucket.test.js +0 -51
- package/src/helpers/color.js +0 -30
- package/src/helpers/config.js +0 -64
- package/src/helpers/cookies.test.js +0 -23
- package/src/helpers/cors.js +0 -24
- package/src/helpers/cors.test.js +0 -64
- package/src/helpers/createCookies.js +0 -16
- package/src/helpers/createId.js +0 -51
- package/src/helpers/define.js +0 -18
- package/src/helpers/getMachine.js +0 -26
- package/src/helpers/handleRequest.js +0 -34
- package/src/helpers/index.js +0 -11
- package/src/helpers/iterate.js +0 -8
- package/src/helpers/parseHeaders.js +0 -15
- package/src/helpers/toWeb.js +0 -15
- package/src/helpers/types.js +0 -81
- package/src/helpers/validate.js +0 -34
- package/src/index.d.ts +0 -198
- package/src/index.js +0 -268
- package/src/index.test.js +0 -87
- package/src/index.types.ts +0 -28
- package/src/middle/assets.js +0 -17
- package/src/middle/assets.test.js +0 -10
- package/src/middle/index.js +0 -7
- package/src/middle/openapi.js +0 -147
- package/src/middle/timer.js +0 -14
- package/src/parseResponse.js +0 -111
- package/src/pathPattern.js +0 -47
- package/src/pathPattern.test.js +0 -99
- package/src/polyfill.js +0 -18
- package/src/reply.js +0 -153
- package/src/router.js +0 -53
- package/src/router.test.js +0 -57
- package/src/session.test.js +0 -65
- package/src/test/toSucceed.js +0 -64
- package/src/url.test.js +0 -26
package/index.js
ADDED
|
@@ -0,0 +1,2005 @@
|
|
|
1
|
+
// src/polyfill.ts
|
|
2
|
+
globalThis.env = {};
|
|
3
|
+
if (typeof globalThis.Netlify !== "undefined") {
|
|
4
|
+
Object.assign(
|
|
5
|
+
globalThis.env,
|
|
6
|
+
globalThis.Netlify.env.toObject()
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
if (typeof process !== "undefined") {
|
|
10
|
+
Object.assign(globalThis.env, process.env);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/ServerError.ts
|
|
14
|
+
var ServerError = class _ServerError extends Error {
|
|
15
|
+
code;
|
|
16
|
+
status;
|
|
17
|
+
constructor(code, status2, message, vars = {}) {
|
|
18
|
+
let messageStr;
|
|
19
|
+
if (typeof message === "function") {
|
|
20
|
+
messageStr = message(vars);
|
|
21
|
+
} else {
|
|
22
|
+
messageStr = message;
|
|
23
|
+
}
|
|
24
|
+
if (typeof messageStr !== "string")
|
|
25
|
+
throw Error(`Invalid error ${messageStr}`);
|
|
26
|
+
for (const key in vars) {
|
|
27
|
+
let value = vars[key];
|
|
28
|
+
value = Array.isArray(value) ? value.join(",") : value;
|
|
29
|
+
const regex = new RegExp(`\\{${key}\\}`, "g");
|
|
30
|
+
messageStr = messageStr.replace(regex, value);
|
|
31
|
+
}
|
|
32
|
+
super(messageStr);
|
|
33
|
+
this.code = code;
|
|
34
|
+
this.message = messageStr;
|
|
35
|
+
this.status = status2;
|
|
36
|
+
}
|
|
37
|
+
// Add error codes dynamically to the global object
|
|
38
|
+
static extend(errors) {
|
|
39
|
+
for (const code in errors) {
|
|
40
|
+
const error = errors[code];
|
|
41
|
+
if (typeof error === "string") {
|
|
42
|
+
_ServerError[code] = (vars = {}) => new _ServerError(code, 500, error, vars);
|
|
43
|
+
} else {
|
|
44
|
+
_ServerError[code] = (vars = {}) => new _ServerError(code, error.status, error.message, vars);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return errors;
|
|
48
|
+
}
|
|
49
|
+
// Dynamically added error methods from errors/index.ts
|
|
50
|
+
static NO_STORE;
|
|
51
|
+
static NO_STORE_WRITE;
|
|
52
|
+
static NO_STORE_READ;
|
|
53
|
+
static AUTH_ARGON_NEEDED;
|
|
54
|
+
static AUTH_INVALID_TYPE;
|
|
55
|
+
static AUTH_INVALID_TOKEN;
|
|
56
|
+
static AUTH_INVALID_COOKIE;
|
|
57
|
+
static AUTH_NO_PROVIDER;
|
|
58
|
+
static AUTH_INVALID_PROVIDER;
|
|
59
|
+
static AUTH_NO_SESSION;
|
|
60
|
+
static AUTH_NO_USER;
|
|
61
|
+
static LOGIN_NO_EMAIL;
|
|
62
|
+
static LOGIN_INVALID_EMAIL;
|
|
63
|
+
static LOGIN_NO_PASSWORD;
|
|
64
|
+
static LOGIN_INVALID_PASSWORD;
|
|
65
|
+
static LOGIN_WRONG_EMAIL;
|
|
66
|
+
static LOGIN_WRONG_PASSWORD;
|
|
67
|
+
static REGISTER_NO_EMAIL;
|
|
68
|
+
static REGISTER_INVALID_EMAIL;
|
|
69
|
+
static REGISTER_NO_PASSWORD;
|
|
70
|
+
static REGISTER_INVALID_PASSWORD;
|
|
71
|
+
static REGISTER_EMAIL_EXISTS;
|
|
72
|
+
};
|
|
73
|
+
var ServerError_default = ServerError;
|
|
74
|
+
|
|
75
|
+
// src/errors/index.ts
|
|
76
|
+
ServerError_default.extend({
|
|
77
|
+
NO_STORE: "You need a 'store' to write 'ctx.session'",
|
|
78
|
+
NO_STORE_WRITE: "You need a 'store' to write 'ctx.session.{key}'",
|
|
79
|
+
NO_STORE_READ: "You need a 'store' to read 'ctx.session.{key}'",
|
|
80
|
+
AUTH_ARGON_NEEDED: "Argon2 is needed for the auth module, please install it with 'npm i argon2'",
|
|
81
|
+
AUTH_INVALID_TYPE: "Invalid Authorization type, '{type}'",
|
|
82
|
+
AUTH_INVALID_TOKEN: "Invalid Authorization token",
|
|
83
|
+
AUTH_INVALID_COOKIE: "Invalid Authorization cookie",
|
|
84
|
+
AUTH_NO_PROVIDER: "No provider passed to the option 'auth.provider'",
|
|
85
|
+
AUTH_INVALID_PROVIDER: "Invalid provider '{provider}', valid ones are: '{valid}'",
|
|
86
|
+
AUTH_NO_SESSION: { status: 401, message: "Invalid session" },
|
|
87
|
+
AUTH_NO_USER: {
|
|
88
|
+
status: 401,
|
|
89
|
+
message: "Credentials do not correspond to a user"
|
|
90
|
+
},
|
|
91
|
+
LOGIN_NO_EMAIL: "The email is required to log in",
|
|
92
|
+
LOGIN_INVALID_EMAIL: "The email you wrote is not correct",
|
|
93
|
+
LOGIN_NO_PASSWORD: "The email is required to log in",
|
|
94
|
+
LOGIN_INVALID_PASSWORD: "The password you wrote is not correct",
|
|
95
|
+
LOGIN_WRONG_ACCOUNT: "That email does not correspond to any account",
|
|
96
|
+
LOGIN_WRONG_PASSWORD: "That is not the valid password",
|
|
97
|
+
REGISTER_NO_EMAIL: "Email needed",
|
|
98
|
+
REGISTER_INVALID_EMAIL: "The email you wrote is not correct",
|
|
99
|
+
REGISTER_NO_PASSWORD: "Password needed",
|
|
100
|
+
REGISTER_INVALID_PASSWORD: "The password you wrote is not correct",
|
|
101
|
+
REGISTER_EMAIL_EXISTS: "Email is already registered"
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// src/helpers/createCookies.ts
|
|
105
|
+
function createCookies(cookies2) {
|
|
106
|
+
if (!cookies2 || !Object.keys(cookies2).length) return [];
|
|
107
|
+
return Object.entries(cookies2).map(([key, val]) => {
|
|
108
|
+
if (!val) {
|
|
109
|
+
val = { value: "", expires: (/* @__PURE__ */ new Date(0)).toUTCString() };
|
|
110
|
+
}
|
|
111
|
+
if (typeof val === "string") {
|
|
112
|
+
val = { value: val };
|
|
113
|
+
}
|
|
114
|
+
const { value, path: path2, expires } = val;
|
|
115
|
+
const pathPart = `;Path=${path2 || "/"}`;
|
|
116
|
+
const expiresPart = expires ? `;Expires=${expires}` : "";
|
|
117
|
+
return `${key}=${value || ""}${pathPart}${expiresPart}`;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/helpers/createId.ts
|
|
122
|
+
var alphabet = "useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
|
|
123
|
+
var random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
|
|
124
|
+
var cyrb53 = (str, seed = 0) => {
|
|
125
|
+
if (typeof str !== "string") str = String(str);
|
|
126
|
+
let h1 = 3735928559 ^ seed;
|
|
127
|
+
let h2 = 1103547991 ^ seed;
|
|
128
|
+
for (let i = 0, ch; i < str.length; i++) {
|
|
129
|
+
ch = str.charCodeAt(i);
|
|
130
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
131
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
132
|
+
}
|
|
133
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
|
|
134
|
+
h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
135
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
|
|
136
|
+
h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
137
|
+
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
138
|
+
};
|
|
139
|
+
var hash = (str, size) => {
|
|
140
|
+
let chars = "";
|
|
141
|
+
let num = cyrb53(str);
|
|
142
|
+
for (let i = 0; i < size; i++) {
|
|
143
|
+
if (num < alphabet.length) num = cyrb53(str, i);
|
|
144
|
+
chars += alphabet[num % alphabet.length];
|
|
145
|
+
num = Math.floor(num / alphabet.length);
|
|
146
|
+
}
|
|
147
|
+
return chars;
|
|
148
|
+
};
|
|
149
|
+
var randomId = (size = 16) => {
|
|
150
|
+
let id = "";
|
|
151
|
+
const bytes = random(size);
|
|
152
|
+
while (size--) {
|
|
153
|
+
id += alphabet[bytes[size] & 61];
|
|
154
|
+
}
|
|
155
|
+
return id;
|
|
156
|
+
};
|
|
157
|
+
function createId(source, size = 16) {
|
|
158
|
+
if (source) return hash(source, size);
|
|
159
|
+
return randomId(size);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/helpers/createWebsocket.ts
|
|
163
|
+
function createWebsocket(sockets, handlers) {
|
|
164
|
+
return {
|
|
165
|
+
message: async (socket, body) => {
|
|
166
|
+
handlers.socket?.filter((s) => s[1] === "message")?.map((s) => s[2]({ socket, sockets, body }));
|
|
167
|
+
},
|
|
168
|
+
open: (socket) => {
|
|
169
|
+
sockets.push(socket);
|
|
170
|
+
handlers.socket?.filter((s) => s[1] === "open")?.map((s) => s[2]({ socket, sockets, body: void 0 }));
|
|
171
|
+
},
|
|
172
|
+
close: (socket) => {
|
|
173
|
+
sockets.splice(sockets.indexOf(socket), 1);
|
|
174
|
+
handlers.socket?.filter((s) => s[1] === "close")?.map((s) => s[2]({ socket, sockets, body: void 0 }));
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/auth/auth.ts
|
|
180
|
+
var validateToken = (authorization) => {
|
|
181
|
+
const [type2, id] = authorization.trim().split(" ");
|
|
182
|
+
if (type2.toLowerCase() !== "bearer") {
|
|
183
|
+
throw ServerError_default.AUTH_INVALID_TYPE({ type: type2 });
|
|
184
|
+
}
|
|
185
|
+
if (id.length !== 16) {
|
|
186
|
+
throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
187
|
+
}
|
|
188
|
+
return id;
|
|
189
|
+
};
|
|
190
|
+
var validateCookie = (authorization) => {
|
|
191
|
+
if (authorization.length !== 16) {
|
|
192
|
+
throw ServerError_default.AUTH_INVALID_COOKIE();
|
|
193
|
+
}
|
|
194
|
+
return authorization;
|
|
195
|
+
};
|
|
196
|
+
var findSessionId = (ctx) => {
|
|
197
|
+
const type2 = ctx.options.auth.type;
|
|
198
|
+
if (type2.includes("token")) {
|
|
199
|
+
if (!ctx.headers.authorization) return;
|
|
200
|
+
return validateToken(ctx.headers.authorization);
|
|
201
|
+
}
|
|
202
|
+
if (type2.includes("cookie")) {
|
|
203
|
+
if (!ctx.cookies.authentication) return;
|
|
204
|
+
return validateCookie(ctx.cookies.authentication);
|
|
205
|
+
}
|
|
206
|
+
throw new Error(`Invalid auth type "${type2}"`);
|
|
207
|
+
};
|
|
208
|
+
async function auth(ctx) {
|
|
209
|
+
if (!ctx.options.auth) return;
|
|
210
|
+
const options = ctx.options.auth;
|
|
211
|
+
const sessionId = findSessionId(ctx);
|
|
212
|
+
if (!sessionId) return;
|
|
213
|
+
const auth3 = await options.session.get(sessionId);
|
|
214
|
+
if (!auth3) return;
|
|
215
|
+
if (!auth3.provider) throw ServerError_default.AUTH_NO_PROVIDER();
|
|
216
|
+
if (!options.provider.includes(auth3.provider)) {
|
|
217
|
+
throw ServerError_default.AUTH_INVALID_PROVIDER({
|
|
218
|
+
provider: auth3.provider,
|
|
219
|
+
valid: options.provider
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return auth3;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/reply.ts
|
|
226
|
+
var Reply = class {
|
|
227
|
+
res;
|
|
228
|
+
constructor() {
|
|
229
|
+
this.res = {
|
|
230
|
+
headers: {},
|
|
231
|
+
cookies: {}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
generateHeaders() {
|
|
235
|
+
const headers2 = new Headers(this.res.headers);
|
|
236
|
+
for (const cookie of createCookies(this.res.cookies)) {
|
|
237
|
+
headers2.append("set-cookie", cookie);
|
|
238
|
+
}
|
|
239
|
+
return headers2;
|
|
240
|
+
}
|
|
241
|
+
status(status2) {
|
|
242
|
+
this.res.status = status2;
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
245
|
+
type(type2) {
|
|
246
|
+
if (!type2) return this;
|
|
247
|
+
type2 = types_default[type2.replace(/^\./, "")] || type2;
|
|
248
|
+
return this.headers({ "content-type": type2 });
|
|
249
|
+
}
|
|
250
|
+
download(name, type2) {
|
|
251
|
+
if (name && !type2) type2 = name.split(".").pop();
|
|
252
|
+
if (type2) this.type(type2);
|
|
253
|
+
const filename = name ? `; filename="${name}"` : "";
|
|
254
|
+
return this.headers({ "content-disposition": `attachment${filename}` });
|
|
255
|
+
}
|
|
256
|
+
headers(headers2) {
|
|
257
|
+
if (!headers2 || typeof headers2 !== "object") return this;
|
|
258
|
+
for (const key in headers2) {
|
|
259
|
+
this.res.headers[key] = headers2[key];
|
|
260
|
+
}
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
263
|
+
cookies(cookies2) {
|
|
264
|
+
if (!cookies2 || typeof cookies2 !== "object") return this;
|
|
265
|
+
for (const key in cookies2) {
|
|
266
|
+
if (typeof cookies2[key] === "string") {
|
|
267
|
+
this.res.cookies[key] = { value: cookies2[key] };
|
|
268
|
+
} else {
|
|
269
|
+
this.res.cookies[key] = cookies2[key];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return this;
|
|
273
|
+
}
|
|
274
|
+
json(body) {
|
|
275
|
+
return this.headers({
|
|
276
|
+
"content-type": "application/json"
|
|
277
|
+
}).send(JSON.stringify(body));
|
|
278
|
+
}
|
|
279
|
+
redirect(Location) {
|
|
280
|
+
return this.headers({ Location }).status(302).send();
|
|
281
|
+
}
|
|
282
|
+
async file(path2, renderer = async (data) => data) {
|
|
283
|
+
try {
|
|
284
|
+
const fs2 = await import("fs/promises");
|
|
285
|
+
const data = await fs2.readFile(path2);
|
|
286
|
+
const ext = path2.split(".").pop();
|
|
287
|
+
return this.type(ext).send(await renderer(data));
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if (error.code === "ENOENT") {
|
|
290
|
+
return this.status(404).send();
|
|
291
|
+
}
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async view(path2, renderer = async (data) => data, ctx) {
|
|
296
|
+
if (!ctx?.options.views) {
|
|
297
|
+
throw new Error("Views not enabled");
|
|
298
|
+
}
|
|
299
|
+
const data = await ctx.options.views.read(path2);
|
|
300
|
+
if (!data) return this.status(404).send();
|
|
301
|
+
return this.type(path2.split(".").pop()).send(await renderer(data));
|
|
302
|
+
}
|
|
303
|
+
send(body = "") {
|
|
304
|
+
const { status: status2 = 200 } = this.res;
|
|
305
|
+
if (typeof body === "string") {
|
|
306
|
+
if (!this.res.headers["content-type"]) {
|
|
307
|
+
const isHtml = body.startsWith("<");
|
|
308
|
+
this.res.headers["content-type"] = isHtml ? "text/html" : "text/plain";
|
|
309
|
+
}
|
|
310
|
+
const headers2 = this.generateHeaders();
|
|
311
|
+
return new Response(body, { status: status2, headers: headers2 });
|
|
312
|
+
}
|
|
313
|
+
const name = body?.constructor?.name;
|
|
314
|
+
if (name === "Buffer") {
|
|
315
|
+
const headers2 = this.generateHeaders();
|
|
316
|
+
return new Response(body, { status: status2, headers: headers2 });
|
|
317
|
+
}
|
|
318
|
+
if (name === "ReadableStream") {
|
|
319
|
+
const headers2 = this.generateHeaders();
|
|
320
|
+
return new Response(body, { status: status2, headers: headers2 });
|
|
321
|
+
}
|
|
322
|
+
if (name === "PassThrough" || name === "Readable") {
|
|
323
|
+
const headers2 = this.generateHeaders();
|
|
324
|
+
return new Response(toWeb(body), { status: status2, headers: headers2 });
|
|
325
|
+
}
|
|
326
|
+
return this.json(body);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
var status = (...args) => new Reply().status(...args);
|
|
330
|
+
var headers = (...args) => new Reply().headers(...args);
|
|
331
|
+
var type = (...args) => new Reply().type(...args);
|
|
332
|
+
var download = (...args) => new Reply().download(...args);
|
|
333
|
+
var cookies = (...args) => new Reply().cookies(...args);
|
|
334
|
+
var send = (...args) => new Reply().send(...args);
|
|
335
|
+
var json = (...args) => new Reply().json(...args);
|
|
336
|
+
var file = (...args) => new Reply().file(...args);
|
|
337
|
+
var redirect = (...args) => new Reply().redirect(...args);
|
|
338
|
+
var view = (...args) => new Reply().view(...args);
|
|
339
|
+
|
|
340
|
+
// src/auth/logout.ts
|
|
341
|
+
async function logout(ctx) {
|
|
342
|
+
const { id, type: type2 } = ctx.auth;
|
|
343
|
+
await ctx.options.auth.session.del(id);
|
|
344
|
+
if (type2.includes("token")) {
|
|
345
|
+
return { token: null };
|
|
346
|
+
}
|
|
347
|
+
if (type2.includes("cookie")) {
|
|
348
|
+
return cookies({ authorization: null }).redirect("/");
|
|
349
|
+
}
|
|
350
|
+
if (type2.includes("jwt")) {
|
|
351
|
+
throw new Error("JWT auth not supported yet");
|
|
352
|
+
}
|
|
353
|
+
if (type2.includes("key")) {
|
|
354
|
+
throw new Error("Key auth not supported yet");
|
|
355
|
+
}
|
|
356
|
+
throw new Error("Unknown auth type");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/auth/updateUser.ts
|
|
360
|
+
async function updateUser(user2, auth3, store) {
|
|
361
|
+
if (auth3.provider === "email") {
|
|
362
|
+
return await store.set(auth3.email, user2);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/auth/providers/email.ts
|
|
367
|
+
var createSession = async (user2, ctx) => {
|
|
368
|
+
const { type: type2, session: session2, cleanUser, redirect: redirect2 = "/user" } = ctx.options.auth;
|
|
369
|
+
user2 = cleanUser(user2);
|
|
370
|
+
const id = createId();
|
|
371
|
+
const provider = "email";
|
|
372
|
+
const time = (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "");
|
|
373
|
+
ctx.auth = {
|
|
374
|
+
id,
|
|
375
|
+
type: type2,
|
|
376
|
+
provider,
|
|
377
|
+
user: user2.email,
|
|
378
|
+
email: user2.email,
|
|
379
|
+
time
|
|
380
|
+
};
|
|
381
|
+
await session2.set(id, ctx.auth, { expires: "1w" });
|
|
382
|
+
if (type2.includes("token")) {
|
|
383
|
+
return status(201).json({ ...user2, token: id });
|
|
384
|
+
}
|
|
385
|
+
if (type2.includes("cookie")) {
|
|
386
|
+
return status(302).cookies({ authentication: id }).redirect(redirect2);
|
|
387
|
+
}
|
|
388
|
+
if (type2.includes("jwt")) {
|
|
389
|
+
throw new Error("JWT auth not supported yet");
|
|
390
|
+
}
|
|
391
|
+
if (type2.includes("key")) {
|
|
392
|
+
throw new Error("Key auth not supported yet");
|
|
393
|
+
}
|
|
394
|
+
throw new Error("Unknown auth type");
|
|
395
|
+
};
|
|
396
|
+
async function emailLogin(ctx) {
|
|
397
|
+
const { email, password } = ctx.body;
|
|
398
|
+
if (!email) throw ServerError_default.LOGIN_NO_EMAIL();
|
|
399
|
+
if (!/@/.test(email)) throw ServerError_default.LOGIN_INVALID_EMAIL();
|
|
400
|
+
if (!password) throw ServerError_default.LOGIN_NO_PASSWORD();
|
|
401
|
+
if (password.length < 8) throw ServerError_default.LOGIN_INVALID_PASSWORD();
|
|
402
|
+
const store = ctx.options.auth.store;
|
|
403
|
+
if (!await store.has(email)) throw ServerError_default.LOGIN_WRONG_EMAIL();
|
|
404
|
+
const user2 = await store.get(email);
|
|
405
|
+
const isValid = await verify(password, user2.password);
|
|
406
|
+
if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
|
|
407
|
+
return createSession(user2, ctx);
|
|
408
|
+
}
|
|
409
|
+
async function emailRegister(ctx) {
|
|
410
|
+
const { email, password, ...data } = ctx.body;
|
|
411
|
+
if (!email) throw ServerError_default.REGISTER_NO_EMAIL();
|
|
412
|
+
if (!/@/.test(email)) throw ServerError_default.REGISTER_INVALID_EMAIL();
|
|
413
|
+
if (!password) throw ServerError_default.REGISTER_NO_PASSWORD();
|
|
414
|
+
if (password.length < 8) throw ServerError_default.REGISTER_INVALID_PASSWORD();
|
|
415
|
+
const store = ctx.options.auth.store;
|
|
416
|
+
if (await store.has(email)) throw ServerError_default.REGISTER_EMAIL_EXISTS();
|
|
417
|
+
const time = (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "");
|
|
418
|
+
const user2 = {
|
|
419
|
+
id: createId(email),
|
|
420
|
+
email,
|
|
421
|
+
password: await hash2(password),
|
|
422
|
+
time,
|
|
423
|
+
...data
|
|
424
|
+
};
|
|
425
|
+
await store.set(email, user2);
|
|
426
|
+
return createSession(user2, ctx);
|
|
427
|
+
}
|
|
428
|
+
async function emailResetPassword() {
|
|
429
|
+
}
|
|
430
|
+
async function emailUpdatePassword(ctx) {
|
|
431
|
+
const { previous, updated } = ctx.body;
|
|
432
|
+
const fullUser = await ctx.options.auth.store.get(ctx.auth.user);
|
|
433
|
+
const isValid = await verify(previous, fullUser.password);
|
|
434
|
+
if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
|
|
435
|
+
fullUser.password = await hash2(updated);
|
|
436
|
+
await updateUser(fullUser, ctx.auth, ctx.options.auth.store);
|
|
437
|
+
return 200;
|
|
438
|
+
}
|
|
439
|
+
var email_default = {
|
|
440
|
+
login: emailLogin,
|
|
441
|
+
register: emailRegister,
|
|
442
|
+
reset: emailResetPassword,
|
|
443
|
+
password: emailUpdatePassword
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
// src/auth/providers/github.ts
|
|
447
|
+
var oauth = async (code) => {
|
|
448
|
+
const fch = async (url, { body, headers: headers2 = {}, ...rest } = {}) => {
|
|
449
|
+
headers2.accept = "application/json";
|
|
450
|
+
headers2["content-type"] = "application/json";
|
|
451
|
+
const res2 = await fetch(url, { ...rest, body, headers: headers2 });
|
|
452
|
+
if (!res2.ok) throw new Error("Invalid request");
|
|
453
|
+
return res2.json();
|
|
454
|
+
};
|
|
455
|
+
const res = await fch("https://github.com/login/oauth/access_token", {
|
|
456
|
+
method: "post",
|
|
457
|
+
body: JSON.stringify({
|
|
458
|
+
client_id: env.GITHUB_ID,
|
|
459
|
+
client_secret: env.GITHUB_SECRET,
|
|
460
|
+
code
|
|
461
|
+
})
|
|
462
|
+
});
|
|
463
|
+
return (path2) => {
|
|
464
|
+
return fch(`https://api.github.com${path2}`, {
|
|
465
|
+
headers: { Authorization: `Bearer ${res.access_token}` }
|
|
466
|
+
});
|
|
467
|
+
};
|
|
468
|
+
};
|
|
469
|
+
var login = function githubLogin() {
|
|
470
|
+
return redirect(
|
|
471
|
+
`https://github.com/login/oauth/authorize?client_id=${env.GITHUB_ID}&scope=user:email`
|
|
472
|
+
);
|
|
473
|
+
};
|
|
474
|
+
var getUserProfile = async (code) => {
|
|
475
|
+
const api = await oauth(code);
|
|
476
|
+
const [profile, emails] = await Promise.all([
|
|
477
|
+
api("/user"),
|
|
478
|
+
api("/user/emails")
|
|
479
|
+
]);
|
|
480
|
+
const email = emails.sort((a) => a.primary ? -1 : 1)[0]?.email;
|
|
481
|
+
return { ...profile, email };
|
|
482
|
+
};
|
|
483
|
+
var callback = async (ctx) => {
|
|
484
|
+
const { type: type2, cleanUser, store, session: session2, redirect: redirect2 } = ctx.options.auth;
|
|
485
|
+
const profile = await getUserProfile(ctx.url.query.code);
|
|
486
|
+
const auth3 = {
|
|
487
|
+
id: createId(),
|
|
488
|
+
type: type2,
|
|
489
|
+
provider: "github",
|
|
490
|
+
user: createId(profile.email),
|
|
491
|
+
email: profile.email,
|
|
492
|
+
time: (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "")
|
|
493
|
+
};
|
|
494
|
+
const user2 = cleanUser({
|
|
495
|
+
id: profile.id,
|
|
496
|
+
name: profile.name,
|
|
497
|
+
email: profile.email,
|
|
498
|
+
picture: profile.avatar_url,
|
|
499
|
+
location: profile.location,
|
|
500
|
+
created: profile.created_at
|
|
501
|
+
});
|
|
502
|
+
await store.set(auth3.user, user2);
|
|
503
|
+
await session2.set(auth3.id, auth3, { expires: "1w" });
|
|
504
|
+
if (auth3.type.includes("token")) {
|
|
505
|
+
return status(201).json({ ...user2, token: auth3.id });
|
|
506
|
+
}
|
|
507
|
+
if (auth3.type.includes("cookie")) {
|
|
508
|
+
return status(302).cookies({ authentication: auth3.id }).redirect(redirect2);
|
|
509
|
+
}
|
|
510
|
+
if (auth3.type.includes("jwt")) {
|
|
511
|
+
throw new Error("JWT auth not supported yet");
|
|
512
|
+
}
|
|
513
|
+
if (auth3.type.includes("key")) {
|
|
514
|
+
throw new Error("Key auth not supported yet");
|
|
515
|
+
}
|
|
516
|
+
throw new Error("Unknown auth type");
|
|
517
|
+
};
|
|
518
|
+
var github_default = { login, callback };
|
|
519
|
+
|
|
520
|
+
// src/auth/providers/index.ts
|
|
521
|
+
var providers_default = { email: email_default, github: github_default };
|
|
522
|
+
|
|
523
|
+
// src/auth/NoSession.ts
|
|
524
|
+
var NoSession = class {
|
|
525
|
+
};
|
|
526
|
+
function createNoSession() {
|
|
527
|
+
return new Proxy(new NoSession(), {
|
|
528
|
+
get(target, key) {
|
|
529
|
+
if (target[key]) return target[key];
|
|
530
|
+
if (key === "then") return target[key];
|
|
531
|
+
if (typeof key === "symbol") return target[key];
|
|
532
|
+
throw ServerError_default.NO_STORE_READ({ key: String(key) });
|
|
533
|
+
},
|
|
534
|
+
set(target, key, value) {
|
|
535
|
+
if (target[key] || key === "then" || typeof key === "symbol") {
|
|
536
|
+
target[key] = value;
|
|
537
|
+
return true;
|
|
538
|
+
} else {
|
|
539
|
+
throw ServerError_default.NO_STORE_WRITE({ key: String(key) });
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/auth/session.ts
|
|
546
|
+
async function session(ctx) {
|
|
547
|
+
const store = ctx.options.session?.store;
|
|
548
|
+
if (!store) return createNoSession();
|
|
549
|
+
if (ctx.cookies.session) {
|
|
550
|
+
const session2 = await store.get(ctx.cookies.session);
|
|
551
|
+
if (session2) return session2;
|
|
552
|
+
}
|
|
553
|
+
return {};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/auth/user.ts
|
|
557
|
+
async function user(ctx) {
|
|
558
|
+
if (!ctx.auth) return;
|
|
559
|
+
const user2 = await ctx.options.auth.store.get(ctx.auth.user);
|
|
560
|
+
if (!user2) throw ServerError_default.AUTH_NO_USER();
|
|
561
|
+
return ctx.options.auth.cleanUser(user2);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/auth/index.ts
|
|
565
|
+
var parseOptions = (auth3, all) => {
|
|
566
|
+
if (!auth3) return null;
|
|
567
|
+
if (typeof auth3 === "string") {
|
|
568
|
+
const [type2, provider] = auth3.split(":");
|
|
569
|
+
auth3 = { type: type2, provider };
|
|
570
|
+
}
|
|
571
|
+
if (typeof auth3.type === "string") {
|
|
572
|
+
auth3.type = auth3.type.split("|").filter(Boolean);
|
|
573
|
+
}
|
|
574
|
+
if (typeof auth3.provider === "string") {
|
|
575
|
+
auth3.provider = auth3.provider.split("|").filter(Boolean);
|
|
576
|
+
}
|
|
577
|
+
if (!auth3.type) {
|
|
578
|
+
throw new Error("Auth options needs a type");
|
|
579
|
+
}
|
|
580
|
+
if (!auth3.type.length) {
|
|
581
|
+
throw new Error("Auth options needs a type");
|
|
582
|
+
}
|
|
583
|
+
if (!auth3.provider || !auth3.provider.length) {
|
|
584
|
+
throw new Error("Auth options needs a provider");
|
|
585
|
+
}
|
|
586
|
+
const providerNotFound = auth3.provider.find((p) => !providers_default[p]);
|
|
587
|
+
if (providerNotFound) {
|
|
588
|
+
throw new Error(
|
|
589
|
+
`Provider "${providerNotFound}" not found, available ones are "${Object.keys(providers_default).join('", "')}"`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
if (!auth3.session && all.store) {
|
|
593
|
+
auth3.session = all.store.prefix("auth:");
|
|
594
|
+
}
|
|
595
|
+
if (!auth3.store && all.store) {
|
|
596
|
+
auth3.store = all.store.prefix("user:");
|
|
597
|
+
}
|
|
598
|
+
if (!auth3.cleanUser) {
|
|
599
|
+
auth3.cleanUser = (fullUser) => {
|
|
600
|
+
const { password: _password, ...user2 } = fullUser;
|
|
601
|
+
return user2;
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
if (!auth3.redirect) {
|
|
605
|
+
auth3.redirect = "/user";
|
|
606
|
+
}
|
|
607
|
+
return auth3;
|
|
608
|
+
};
|
|
609
|
+
var load = async (ctx) => {
|
|
610
|
+
ctx.session = await session(ctx);
|
|
611
|
+
ctx.auth = await auth(ctx);
|
|
612
|
+
ctx.user = await user(ctx);
|
|
613
|
+
};
|
|
614
|
+
var middle = async (ctx) => {
|
|
615
|
+
if (ctx.options.auth) {
|
|
616
|
+
if (ctx.options.auth.provider.includes("github")) {
|
|
617
|
+
if (!env.GITHUB_ID) throw new Error("GITHUB_ID not defined");
|
|
618
|
+
if (!env.GITHUB_SECRET) throw new Error("GITHUB_SECRET not defined");
|
|
619
|
+
ctx.app.get(
|
|
620
|
+
"/auth/logout",
|
|
621
|
+
{ tags: "Auth", title: "Github logout" },
|
|
622
|
+
logout
|
|
623
|
+
);
|
|
624
|
+
ctx.app.get(
|
|
625
|
+
"/auth/login/github",
|
|
626
|
+
{ tags: "Auth" },
|
|
627
|
+
providers_default.github.login
|
|
628
|
+
);
|
|
629
|
+
ctx.app.get(
|
|
630
|
+
"/auth/callback/github",
|
|
631
|
+
{ tags: "Auth", title: "Github callback" },
|
|
632
|
+
providers_default.github.callback
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
if (ctx.options.auth.provider.includes("email")) {
|
|
636
|
+
ctx.app.post("/auth/logout", { tags: "Auth" }, logout);
|
|
637
|
+
ctx.app.post(
|
|
638
|
+
"/auth/register/email",
|
|
639
|
+
{ tags: "Auth" },
|
|
640
|
+
providers_default.email.register
|
|
641
|
+
);
|
|
642
|
+
ctx.app.post(
|
|
643
|
+
"/auth/login/email",
|
|
644
|
+
{ tags: "Auth" },
|
|
645
|
+
providers_default.email.login
|
|
646
|
+
);
|
|
647
|
+
ctx.app.put(
|
|
648
|
+
"/auth/password/email",
|
|
649
|
+
{ tags: "Auth" },
|
|
650
|
+
providers_default.email.password
|
|
651
|
+
);
|
|
652
|
+
ctx.app.put(
|
|
653
|
+
"/auth/reset/email",
|
|
654
|
+
{ tags: "Auth" },
|
|
655
|
+
providers_default.email.reset
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
var auth_default = { load, parseOptions, middle };
|
|
661
|
+
|
|
662
|
+
// src/helpers/bucket.ts
|
|
663
|
+
import * as fs from "fs";
|
|
664
|
+
import * as path from "path";
|
|
665
|
+
import * as fsp from "fs/promises";
|
|
666
|
+
function thinLocalBucket(root) {
|
|
667
|
+
const absolute = (name) => {
|
|
668
|
+
if (!name) throw new Error("File name is required");
|
|
669
|
+
return path.resolve(path.join(root, name));
|
|
670
|
+
};
|
|
671
|
+
return {
|
|
672
|
+
location: path.resolve(root),
|
|
673
|
+
read: async (name) => {
|
|
674
|
+
const fullPath = absolute(name);
|
|
675
|
+
const stats = await fsp.stat(fullPath).catch(() => null);
|
|
676
|
+
if (!stats || !stats.isFile()) return null;
|
|
677
|
+
const nodeStream = fs.createReadStream(fullPath);
|
|
678
|
+
return new ReadableStream({
|
|
679
|
+
start(controller) {
|
|
680
|
+
nodeStream.on("data", (chunk) => controller.enqueue(chunk));
|
|
681
|
+
nodeStream.on("end", () => controller.close());
|
|
682
|
+
nodeStream.on("error", (err) => controller.error(err));
|
|
683
|
+
},
|
|
684
|
+
cancel() {
|
|
685
|
+
nodeStream.destroy();
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
},
|
|
689
|
+
write: (name, value, type2) => {
|
|
690
|
+
const fullPath = absolute(name);
|
|
691
|
+
if (value) {
|
|
692
|
+
return fsp.writeFile(fullPath, value, type2).then(() => fullPath);
|
|
693
|
+
}
|
|
694
|
+
return fs.createWriteStream(fullPath);
|
|
695
|
+
},
|
|
696
|
+
delete: async (name) => {
|
|
697
|
+
const fullPath = absolute(name);
|
|
698
|
+
try {
|
|
699
|
+
await fsp.unlink(fullPath);
|
|
700
|
+
return true;
|
|
701
|
+
} catch {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function thinBunBucket(s3) {
|
|
708
|
+
return {
|
|
709
|
+
read: async (name) => {
|
|
710
|
+
const file2 = s3.file(name);
|
|
711
|
+
if (!await file2.exists()) return null;
|
|
712
|
+
return await file2.stream();
|
|
713
|
+
},
|
|
714
|
+
write: async (name, value) => {
|
|
715
|
+
const file2 = s3.file(name);
|
|
716
|
+
if (value) {
|
|
717
|
+
await file2.write(value);
|
|
718
|
+
return name;
|
|
719
|
+
}
|
|
720
|
+
return s3.presign(name, { expiresIn: 3600, acl: "public-read-write" });
|
|
721
|
+
},
|
|
722
|
+
delete: async (name) => {
|
|
723
|
+
const file2 = s3.file(name);
|
|
724
|
+
if (!await file2.exists()) return null;
|
|
725
|
+
return await file2.delete();
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
function bucket_default(root) {
|
|
730
|
+
if (!root) return null;
|
|
731
|
+
if (typeof root === "string") {
|
|
732
|
+
return thinLocalBucket(root);
|
|
733
|
+
}
|
|
734
|
+
if (root.file && root.write) {
|
|
735
|
+
return thinBunBucket(root);
|
|
736
|
+
}
|
|
737
|
+
return root;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// src/helpers/color.ts
|
|
741
|
+
var map = {
|
|
742
|
+
reset: 0,
|
|
743
|
+
bright: 1,
|
|
744
|
+
dim: 2,
|
|
745
|
+
under: 4,
|
|
746
|
+
blink: 5,
|
|
747
|
+
reverse: 7,
|
|
748
|
+
black: 30,
|
|
749
|
+
red: 31,
|
|
750
|
+
green: 32,
|
|
751
|
+
yellow: 33,
|
|
752
|
+
blue: 34,
|
|
753
|
+
magenta: 35,
|
|
754
|
+
cyan: 36,
|
|
755
|
+
white: 37,
|
|
756
|
+
bgblack: 40,
|
|
757
|
+
bgred: 41,
|
|
758
|
+
bggreen: 42,
|
|
759
|
+
bgyellow: 43,
|
|
760
|
+
bgblue: 44,
|
|
761
|
+
bgmagenta: 45,
|
|
762
|
+
bgcyan: 46,
|
|
763
|
+
bgwhite: 47
|
|
764
|
+
};
|
|
765
|
+
var replace = (k) => {
|
|
766
|
+
if (process.env.NO_COLOR) return "";
|
|
767
|
+
if (!(k in map)) throw new Error(`"{${k}}" is not a valid color`);
|
|
768
|
+
return `\x1B[${map[k]}m`;
|
|
769
|
+
};
|
|
770
|
+
function color(str, ...vals) {
|
|
771
|
+
if (typeof str === "string") {
|
|
772
|
+
return str.replace(/\{(\w+)\}/g, (_m, k) => replace(k)).replace(/\{\/\w*\}/g, () => replace("reset"));
|
|
773
|
+
}
|
|
774
|
+
return color(str[0] + vals.map((v, i) => v + str[i + 1]).join(""));
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/helpers/debugInfo.ts
|
|
778
|
+
var isDebug = process.argv.includes("--debug");
|
|
779
|
+
function debugInfo(options, name, cb, icon = "") {
|
|
780
|
+
if (!isDebug) return;
|
|
781
|
+
if (!options[name]) {
|
|
782
|
+
console.log(color`options:${String(name)}\t→ {dim}[not set]{/}`);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
console.log(
|
|
786
|
+
color`options:${String(name)}\t→ ${icon ? `${icon} ` : ""}${cb(options[name])}`
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// src/helpers/config.ts
|
|
791
|
+
var env2 = globalThis.env;
|
|
792
|
+
function config(options = {}) {
|
|
793
|
+
const settings = {
|
|
794
|
+
port: options.port || env2.PORT || 3e3,
|
|
795
|
+
secret: options.secret || env2.SECRET || `unsafe-${createId()}`
|
|
796
|
+
};
|
|
797
|
+
options.cors = options.cors || env2.CORS || null;
|
|
798
|
+
if (options.cors) {
|
|
799
|
+
const cors2 = {
|
|
800
|
+
origin: "",
|
|
801
|
+
methods: "GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS",
|
|
802
|
+
headers: "*"
|
|
803
|
+
};
|
|
804
|
+
if (options.cors === true) {
|
|
805
|
+
cors2.origin = true;
|
|
806
|
+
} else if (typeof options.cors === "string") {
|
|
807
|
+
cors2.origin = options.cors;
|
|
808
|
+
} else if (Array.isArray(options.cors)) {
|
|
809
|
+
cors2.origin = options.cors.join(",");
|
|
810
|
+
} else if (typeof options.cors === "object") {
|
|
811
|
+
if (!options.cors.origin) {
|
|
812
|
+
cors2.origin = "*";
|
|
813
|
+
} else if (typeof options.cors.origin === "string") {
|
|
814
|
+
cors2.origin = options.cors.origin;
|
|
815
|
+
} else if (Array.isArray(options.cors.origin)) {
|
|
816
|
+
cors2.origin = options.cors.origin.join(",");
|
|
817
|
+
}
|
|
818
|
+
if ("methods" in options.cors) {
|
|
819
|
+
cors2.methods = Array.isArray(options.cors.methods) ? options.cors.methods.join(",") : options.cors.methods;
|
|
820
|
+
}
|
|
821
|
+
if ("headers" in options.cors) {
|
|
822
|
+
cors2.headers = Array.isArray(options.cors.headers) ? options.cors.headers.join(",") : options.cors.headers;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (typeof cors2.origin === "string") {
|
|
826
|
+
cors2.origin = cors2.origin.toLowerCase();
|
|
827
|
+
}
|
|
828
|
+
settings.cors = cors2;
|
|
829
|
+
}
|
|
830
|
+
settings.views = options.views ? bucket_default(options.views) : null;
|
|
831
|
+
debugInfo(options, "views", (views) => views?.location || "true", "\u{1F4C2}");
|
|
832
|
+
settings.public = options.public ? bucket_default(options.public) : null;
|
|
833
|
+
debugInfo(options, "public", (pub) => pub?.location || "true", "\u{1F4C2}");
|
|
834
|
+
settings.uploads = options.uploads ? bucket_default(options.uploads) : null;
|
|
835
|
+
debugInfo(options, "uploads", (ups) => ups?.location || "true", "\u{1F4C2}");
|
|
836
|
+
settings.store = options.store ?? null;
|
|
837
|
+
debugInfo(options, "store", (store) => store?.name || "working", "\u{1F4E6}");
|
|
838
|
+
settings.cookies = options.cookies ?? null;
|
|
839
|
+
debugInfo(options, "cookies", (cookies2) => cookies2?.name || "working", "\u{1F36A}");
|
|
840
|
+
if (options.store && !options.session) {
|
|
841
|
+
settings.session = { store: options.store.prefix("session:") };
|
|
842
|
+
}
|
|
843
|
+
debugInfo(
|
|
844
|
+
options,
|
|
845
|
+
"session",
|
|
846
|
+
(session2) => session2?.store?.name || "working",
|
|
847
|
+
"\u{1F510}"
|
|
848
|
+
);
|
|
849
|
+
settings.auth = auth_default.parseOptions(options.auth || env2.AUTH || null, options);
|
|
850
|
+
if (options.openapi) {
|
|
851
|
+
if (options.openapi === true) {
|
|
852
|
+
settings.openapi = {};
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return settings;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/helpers/cors.ts
|
|
859
|
+
var localhost = /^https?:\/\/localhost(:\d+)?$/;
|
|
860
|
+
function cors(config2, origin = "") {
|
|
861
|
+
origin = origin.toLowerCase();
|
|
862
|
+
if (config2 === true) return origin || null;
|
|
863
|
+
if (config2 === "*") return "*";
|
|
864
|
+
if (!origin) return null;
|
|
865
|
+
if (localhost.test(origin)) return origin;
|
|
866
|
+
const arr = Array.isArray(config2) ? config2 : typeof config2 === "string" ? config2.split(/\s*,\s*/g) : [];
|
|
867
|
+
if (arr.includes(origin)) return origin;
|
|
868
|
+
console.warn(`CORS: Origin "${origin}" not allowed. Allowed "${config2}"`);
|
|
869
|
+
return null;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/helpers/define.ts
|
|
873
|
+
function define(obj, key, cb) {
|
|
874
|
+
Object.defineProperty(obj, key, {
|
|
875
|
+
configurable: true,
|
|
876
|
+
get() {
|
|
877
|
+
const value = cb(obj);
|
|
878
|
+
Object.defineProperty(obj, key, {
|
|
879
|
+
configurable: true,
|
|
880
|
+
writable: true,
|
|
881
|
+
value
|
|
882
|
+
});
|
|
883
|
+
return obj[key];
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// src/helpers/getMachine.ts
|
|
889
|
+
function getProvider() {
|
|
890
|
+
if (typeof globalThis.Netlify !== "undefined") return "netlify";
|
|
891
|
+
return null;
|
|
892
|
+
}
|
|
893
|
+
function getRuntime() {
|
|
894
|
+
if (typeof Bun !== "undefined") return "bun";
|
|
895
|
+
if (typeof globalThis.Deno !== "undefined") return "deno";
|
|
896
|
+
if (globalThis.process?.versions?.node) return "node";
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
function getProduction() {
|
|
900
|
+
if (typeof globalThis.Netlify !== "undefined")
|
|
901
|
+
return globalThis.Netlify.env.get("NETLIFY_DEV") !== "true";
|
|
902
|
+
return process.env.NODE_ENV === "production";
|
|
903
|
+
}
|
|
904
|
+
function getMachine() {
|
|
905
|
+
return {
|
|
906
|
+
provider: getProvider(),
|
|
907
|
+
runtime: getRuntime(),
|
|
908
|
+
production: getProduction()
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// src/parseResponse.ts
|
|
913
|
+
async function parseResponse(out, ctx) {
|
|
914
|
+
if (!out && typeof out !== "string") return;
|
|
915
|
+
if (typeof out === "function") {
|
|
916
|
+
out = await out(ctx);
|
|
917
|
+
}
|
|
918
|
+
if (out instanceof Blob) {
|
|
919
|
+
out = new Response(out, { headers: { "Content-Type": out.type } });
|
|
920
|
+
}
|
|
921
|
+
if (out instanceof ReadableStream) {
|
|
922
|
+
out = new Response(out);
|
|
923
|
+
}
|
|
924
|
+
if (typeof out === "number") {
|
|
925
|
+
out = new Response(void 0, { status: out });
|
|
926
|
+
}
|
|
927
|
+
if (typeof out === "string") {
|
|
928
|
+
const type2 = /^\s*</.test(out) ? "text/html" : "text/plain";
|
|
929
|
+
out = new Response(out, { headers: { "content-type": type2 } });
|
|
930
|
+
}
|
|
931
|
+
if (out?.constructor === Object || Array.isArray(out)) {
|
|
932
|
+
out = json(out);
|
|
933
|
+
}
|
|
934
|
+
if (out[Symbol.iterator]) {
|
|
935
|
+
out = new Response(iteratorToReadable(out));
|
|
936
|
+
}
|
|
937
|
+
if (out[Symbol.asyncIterator] && !(out instanceof Response)) {
|
|
938
|
+
out = new Response(iteratorAsyncToReadable(out));
|
|
939
|
+
}
|
|
940
|
+
if (out instanceof Response && out.url && out.body) {
|
|
941
|
+
out = new Response(out.body, {
|
|
942
|
+
status: out.status,
|
|
943
|
+
headers: out.headers
|
|
944
|
+
});
|
|
945
|
+
if (/^(br|gzip)$/.test(out.headers.get("content-encoding") || "")) {
|
|
946
|
+
out.headers.delete("content-encoding");
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
if (!(out instanceof Response)) {
|
|
950
|
+
throw new Error(`Invalid response type ${out}`);
|
|
951
|
+
}
|
|
952
|
+
if (ctx.options.cors) {
|
|
953
|
+
const origin = cors(ctx.options.cors.origin, ctx.headers.origin);
|
|
954
|
+
if (origin) {
|
|
955
|
+
out.headers.set("Access-Control-Allow-Origin", origin);
|
|
956
|
+
out.headers.set("Access-Control-Allow-Methods", ctx.options.cors.methods);
|
|
957
|
+
out.headers.set("Access-Control-Allow-Headers", ctx.options.cors.headers);
|
|
958
|
+
if (ctx.options.cors.credentials) {
|
|
959
|
+
out.headers.set("Access-Control-Allow-Credentials", "true");
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
if (ctx.time?.times?.length > 1) {
|
|
964
|
+
out.headers.set("Server-Timing", ctx.time.headers());
|
|
965
|
+
}
|
|
966
|
+
if (Object.keys(ctx.session || {}).length) {
|
|
967
|
+
if (!ctx.options.session?.store) {
|
|
968
|
+
throw ServerError_default.NO_STORE({});
|
|
969
|
+
}
|
|
970
|
+
if (!ctx.cookies.session) {
|
|
971
|
+
ctx.res.cookies.session = createId();
|
|
972
|
+
}
|
|
973
|
+
const id = ctx.cookies.session;
|
|
974
|
+
ctx.options.session.store.set(id, ctx.session);
|
|
975
|
+
}
|
|
976
|
+
if (ctx.options.cookies) {
|
|
977
|
+
if (Object.keys(ctx.res.cookies).length) {
|
|
978
|
+
for (const cookie of ctx.res.cookies) {
|
|
979
|
+
ctx.res.headers.append("set-cookie", cookie);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (ctx?.res?.headers) {
|
|
984
|
+
for (const key in ctx.res.headers) {
|
|
985
|
+
out.headers[key] = ctx.res.headers[key];
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return out;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// src/pathPattern.ts
|
|
992
|
+
function pathPattern(pattern, path2) {
|
|
993
|
+
pattern = `/${pattern.replace(/^\//, "")}`;
|
|
994
|
+
pattern = pattern.replace(/\/$/, "") || "/";
|
|
995
|
+
path2 = path2.replace(/\/$/, "") || "/";
|
|
996
|
+
if (pattern === path2) return {};
|
|
997
|
+
const params = {};
|
|
998
|
+
const pathParts = path2.split("/").slice(1);
|
|
999
|
+
const pattParts = pattern.split("/").slice(1);
|
|
1000
|
+
let allSame = true;
|
|
1001
|
+
for (let i = 0; i < Math.max(pathParts.length, pattParts.length); i++) {
|
|
1002
|
+
const patt = pattParts[i] || "";
|
|
1003
|
+
const part = pathParts[i] || "";
|
|
1004
|
+
const last = pattParts[pattParts.length - 1];
|
|
1005
|
+
const key = patt.replace(/^:/, "").replace(/\?$/, "").replace(/\(\w*\)/, "");
|
|
1006
|
+
if (patt === part) continue;
|
|
1007
|
+
if (patt.endsWith("?") && !part) continue;
|
|
1008
|
+
if (patt.startsWith(":")) {
|
|
1009
|
+
params[key] = part;
|
|
1010
|
+
if (/\(\w*\)/.test(patt)) {
|
|
1011
|
+
if (patt.includes("(number)")) {
|
|
1012
|
+
const value = Number(part);
|
|
1013
|
+
params[key] = Number.isNaN(value) ? void 0 : value;
|
|
1014
|
+
}
|
|
1015
|
+
if (patt.includes("(date)")) {
|
|
1016
|
+
const value = new Date(part);
|
|
1017
|
+
params[key] = Number.isNaN(value.getTime()) ? void 0 : value;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
if (!patt && last === "*" && part || patt === "*" && part) {
|
|
1023
|
+
params["*"] = params["*"] || [];
|
|
1024
|
+
params["*"].push(part);
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
allSame = false;
|
|
1028
|
+
}
|
|
1029
|
+
if (allSame) return params;
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// src/helpers/StatusError.ts
|
|
1034
|
+
var StatusError = class extends Error {
|
|
1035
|
+
status;
|
|
1036
|
+
constructor(msg, status2 = 500) {
|
|
1037
|
+
super(msg);
|
|
1038
|
+
this.status = status2;
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
// src/helpers/validate.ts
|
|
1043
|
+
function validate(ctx, schema) {
|
|
1044
|
+
if (!schema || typeof schema !== "object") return;
|
|
1045
|
+
let base;
|
|
1046
|
+
try {
|
|
1047
|
+
if (typeof schema?.body === "function") {
|
|
1048
|
+
base = "body";
|
|
1049
|
+
schema.body(ctx.body || {});
|
|
1050
|
+
}
|
|
1051
|
+
if (typeof schema?.body?.parse === "function") {
|
|
1052
|
+
base = "body";
|
|
1053
|
+
schema.body.parse(ctx.body || {});
|
|
1054
|
+
}
|
|
1055
|
+
if (typeof schema?.query === "function") {
|
|
1056
|
+
base = "query";
|
|
1057
|
+
schema.query(ctx.url.query || {});
|
|
1058
|
+
}
|
|
1059
|
+
if (typeof schema?.query?.parse === "function") {
|
|
1060
|
+
base = "query";
|
|
1061
|
+
schema.query.parse(ctx.url.query || {});
|
|
1062
|
+
}
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
if (error.name === "ZodError" || error.constructor.name === "ZodError") {
|
|
1065
|
+
const message = error.issues.map(({ path: path2, message: message2 }) => `[${base}.${path2.join(".")}]: ${message2}`).sort().join("\n");
|
|
1066
|
+
throw new StatusError(message, 422);
|
|
1067
|
+
}
|
|
1068
|
+
throw error;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// src/helpers/handleRequest.ts
|
|
1073
|
+
async function handleRequest(handlers, ctx) {
|
|
1074
|
+
try {
|
|
1075
|
+
if (ctx.error) {
|
|
1076
|
+
throw ctx.error;
|
|
1077
|
+
}
|
|
1078
|
+
for (const [method, matcher, ...cbs] of handlers[ctx.method]) {
|
|
1079
|
+
const match = pathPattern(matcher, ctx.url.pathname || "/");
|
|
1080
|
+
if (!match) continue;
|
|
1081
|
+
define(ctx.url, "params", () => match);
|
|
1082
|
+
for (const cb of cbs) {
|
|
1083
|
+
if (typeof cb === "function") {
|
|
1084
|
+
const res = await cb(ctx);
|
|
1085
|
+
const out = await parseResponse(res, ctx);
|
|
1086
|
+
if (out) return out;
|
|
1087
|
+
} else {
|
|
1088
|
+
validate(ctx, cb);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
if (method !== "*") break;
|
|
1092
|
+
}
|
|
1093
|
+
if (ctx.machine?.provider === "netlify") return;
|
|
1094
|
+
return new Response("Not Found", { status: 404 });
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
return new Response(error.message || "", { status: error.status || 500 });
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// src/helpers/hash.ts
|
|
1101
|
+
import * as crypto2 from "crypto";
|
|
1102
|
+
import { getRandomValues } from "crypto";
|
|
1103
|
+
import { promisify } from "util";
|
|
1104
|
+
async function hash2(password) {
|
|
1105
|
+
if ("argon2" in crypto2) {
|
|
1106
|
+
const argon23 = promisify(crypto2.argon2);
|
|
1107
|
+
const buf = await argon23("argon2id", {
|
|
1108
|
+
message: Buffer.from(password),
|
|
1109
|
+
nonce: getRandomValues(new Uint8Array(16)),
|
|
1110
|
+
parallelism: 4,
|
|
1111
|
+
tagLength: 64,
|
|
1112
|
+
memory: 65536,
|
|
1113
|
+
passes: 3
|
|
1114
|
+
});
|
|
1115
|
+
return buf.toString("base64");
|
|
1116
|
+
}
|
|
1117
|
+
return await Bun.password.hash(password, {
|
|
1118
|
+
algorithm: "argon2id",
|
|
1119
|
+
memoryCost: 65536,
|
|
1120
|
+
timeCost: 3
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// src/helpers/iterate.ts
|
|
1125
|
+
async function iterate(stream, cb) {
|
|
1126
|
+
const reader = stream.getReader();
|
|
1127
|
+
while (true) {
|
|
1128
|
+
const chunk = await reader.read();
|
|
1129
|
+
if (chunk.done || !chunk.value) return;
|
|
1130
|
+
cb(chunk.value);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// src/helpers/iteratorToReadable.ts
|
|
1135
|
+
function iteratorToReadable(generator) {
|
|
1136
|
+
return new ReadableStream({
|
|
1137
|
+
async start(controller) {
|
|
1138
|
+
for await (const chunk of generator) {
|
|
1139
|
+
controller.enqueue(chunk);
|
|
1140
|
+
}
|
|
1141
|
+
controller.close();
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// src/helpers/iteratorAsyncToReadable.ts
|
|
1147
|
+
function iteratorAsyncToReadable(asyncGenerator) {
|
|
1148
|
+
return new ReadableStream({
|
|
1149
|
+
async pull(controller) {
|
|
1150
|
+
try {
|
|
1151
|
+
const { value, done } = await asyncGenerator.next();
|
|
1152
|
+
if (done) {
|
|
1153
|
+
controller.close();
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
controller.enqueue(new TextEncoder().encode(value));
|
|
1157
|
+
} catch (err) {
|
|
1158
|
+
console.error("Stream error:", err);
|
|
1159
|
+
controller.error(err);
|
|
1160
|
+
}
|
|
1161
|
+
},
|
|
1162
|
+
cancel() {
|
|
1163
|
+
console.log("Stream cancelled");
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/helpers/parseHeaders.ts
|
|
1169
|
+
var parseHeaders_default = (raw) => {
|
|
1170
|
+
const headers2 = {};
|
|
1171
|
+
raw.forEach((value, originalKey) => {
|
|
1172
|
+
const key = originalKey.toLowerCase();
|
|
1173
|
+
if (headers2[key]) {
|
|
1174
|
+
if (!Array.isArray(headers2[key])) {
|
|
1175
|
+
headers2[key] = [headers2[key]];
|
|
1176
|
+
}
|
|
1177
|
+
headers2[key].push(value);
|
|
1178
|
+
} else {
|
|
1179
|
+
headers2[key] = value;
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
return headers2;
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
// src/helpers/toWeb.ts
|
|
1186
|
+
function toWeb(nodeStream) {
|
|
1187
|
+
if (typeof ReadableStream === "undefined") {
|
|
1188
|
+
throw new Error("Environment not supported, please report this as a bug");
|
|
1189
|
+
}
|
|
1190
|
+
return new ReadableStream({
|
|
1191
|
+
start(controller) {
|
|
1192
|
+
nodeStream.on("data", (chunk) => controller.enqueue(chunk));
|
|
1193
|
+
nodeStream.on("end", () => controller.close());
|
|
1194
|
+
nodeStream.on("error", (err) => controller.error(err));
|
|
1195
|
+
},
|
|
1196
|
+
cancel() {
|
|
1197
|
+
nodeStream.destroy();
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// src/helpers/types.ts
|
|
1203
|
+
var types = {
|
|
1204
|
+
aac: "audio/aac",
|
|
1205
|
+
abw: "application/x-abiword",
|
|
1206
|
+
arc: "application/x-freearc",
|
|
1207
|
+
avif: "image/avif",
|
|
1208
|
+
avi: "video/x-msvideo",
|
|
1209
|
+
azw: "application/vnd.amazon.ebook",
|
|
1210
|
+
bin: "application/octet-stream",
|
|
1211
|
+
bmp: "image/bmp",
|
|
1212
|
+
bz: "application/x-bzip",
|
|
1213
|
+
bz2: "application/x-bzip2",
|
|
1214
|
+
cda: "application/x-cdf",
|
|
1215
|
+
csh: "application/x-csh",
|
|
1216
|
+
css: "text/css",
|
|
1217
|
+
csv: "text/csv",
|
|
1218
|
+
doc: "application/msword",
|
|
1219
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1220
|
+
eot: "application/vnd.ms-fontobject",
|
|
1221
|
+
epub: "application/epub+zip",
|
|
1222
|
+
gz: "application/gzip",
|
|
1223
|
+
gif: "image/gif",
|
|
1224
|
+
htm: "text/html",
|
|
1225
|
+
html: "text/html",
|
|
1226
|
+
ico: "image/vnd.microsoft.icon",
|
|
1227
|
+
ics: "text/calendar",
|
|
1228
|
+
jar: "application/java-archive",
|
|
1229
|
+
jpeg: "image/jpeg",
|
|
1230
|
+
jpg: "image/jpeg",
|
|
1231
|
+
js: "text/javascript",
|
|
1232
|
+
json: "application/json",
|
|
1233
|
+
jsonld: "application/ld+json",
|
|
1234
|
+
md: "text/markdown",
|
|
1235
|
+
mid: "audio/midi",
|
|
1236
|
+
midi: "audio/midi",
|
|
1237
|
+
mjs: "text/javascript",
|
|
1238
|
+
mp3: "audio/mpeg",
|
|
1239
|
+
mp4: "video/mp4",
|
|
1240
|
+
mpeg: "video/mpeg",
|
|
1241
|
+
mpkg: "application/vnd.apple.installer+xml",
|
|
1242
|
+
odp: "application/vnd.oasis.opendocument.presentation",
|
|
1243
|
+
ods: "application/vnd.oasis.opendocument.spreadsheet",
|
|
1244
|
+
odt: "application/vnd.oasis.opendocument.text",
|
|
1245
|
+
oga: "audio/ogg",
|
|
1246
|
+
ogv: "video/ogg",
|
|
1247
|
+
ogx: "application/ogg",
|
|
1248
|
+
opus: "audio/opus",
|
|
1249
|
+
otf: "font/otf",
|
|
1250
|
+
png: "image/png",
|
|
1251
|
+
pdf: "application/pdf",
|
|
1252
|
+
php: "application/x-httpd-php",
|
|
1253
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
1254
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
1255
|
+
rar: "application/vnd.rar",
|
|
1256
|
+
rtf: "application/rtf",
|
|
1257
|
+
sh: "application/x-sh",
|
|
1258
|
+
svg: "image/svg+xml",
|
|
1259
|
+
tar: "application/x-tar",
|
|
1260
|
+
text: "text/plain",
|
|
1261
|
+
tif: "image/tiff",
|
|
1262
|
+
tiff: "image/tiff",
|
|
1263
|
+
ts: "video/mp2t",
|
|
1264
|
+
ttf: "font/ttf",
|
|
1265
|
+
txt: "text/plain",
|
|
1266
|
+
vsd: "application/vnd.visio",
|
|
1267
|
+
wav: "audio/wav",
|
|
1268
|
+
weba: "audio/webm",
|
|
1269
|
+
webm: "video/webm",
|
|
1270
|
+
webp: "image/webp",
|
|
1271
|
+
woff: "font/woff",
|
|
1272
|
+
woff2: "font/woff2",
|
|
1273
|
+
xhtml: "application/xhtml+xml",
|
|
1274
|
+
xls: "application/vnd.ms-excel",
|
|
1275
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1276
|
+
xml: "application/xml",
|
|
1277
|
+
xul: "application/vnd.mozilla.xul+xml",
|
|
1278
|
+
zip: "application/zip",
|
|
1279
|
+
"3gp": "video/3gpp",
|
|
1280
|
+
"3g2": "video/3gpp2",
|
|
1281
|
+
"7z": "application/x-7z-compressed"
|
|
1282
|
+
};
|
|
1283
|
+
var types_default = types;
|
|
1284
|
+
|
|
1285
|
+
// src/helpers/verify.ts
|
|
1286
|
+
import * as crypto3 from "crypto";
|
|
1287
|
+
function timingSafeEqual(a, b) {
|
|
1288
|
+
const len = Math.max(a.length, b.length);
|
|
1289
|
+
let mismatch = a.length ^ b.length;
|
|
1290
|
+
for (let i = 0; i < len; i++) {
|
|
1291
|
+
const ca = a.charCodeAt(i) || 0;
|
|
1292
|
+
const cb = b.charCodeAt(i) || 0;
|
|
1293
|
+
mismatch |= ca ^ cb;
|
|
1294
|
+
}
|
|
1295
|
+
return mismatch === 0;
|
|
1296
|
+
}
|
|
1297
|
+
async function verify(password, hash3) {
|
|
1298
|
+
if ("Bun" in globalThis) {
|
|
1299
|
+
return Bun.password.verify(password, hash3, "argon2id");
|
|
1300
|
+
}
|
|
1301
|
+
const match = /^\$argon2(id|i|d)\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([^$]+)\$([^$]+)$/.exec(
|
|
1302
|
+
hash3
|
|
1303
|
+
);
|
|
1304
|
+
if (!match) throw new Error("Invalid Argon2 hash format");
|
|
1305
|
+
const [, variant, , memory, passes, parallelism, saltB64, hashB64] = match;
|
|
1306
|
+
const nonce = Buffer.from(saltB64, "base64");
|
|
1307
|
+
const expected = Buffer.from(hashB64, "base64");
|
|
1308
|
+
return new Promise((resolve2, reject) => {
|
|
1309
|
+
crypto3.argon2(
|
|
1310
|
+
`argon2${variant}`,
|
|
1311
|
+
{
|
|
1312
|
+
message: password,
|
|
1313
|
+
nonce,
|
|
1314
|
+
memory: parseInt(memory, 10),
|
|
1315
|
+
passes: parseInt(passes, 10),
|
|
1316
|
+
parallelism: parseInt(parallelism, 10),
|
|
1317
|
+
tagLength: expected.length
|
|
1318
|
+
},
|
|
1319
|
+
(err, derivedKey) => {
|
|
1320
|
+
if (err) return reject(err);
|
|
1321
|
+
if (derivedKey.length === expected.length && timingSafeEqual(derivedKey, expected)) {
|
|
1322
|
+
resolve2(true);
|
|
1323
|
+
} else {
|
|
1324
|
+
resolve2(false);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
);
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// src/middle/assets.ts
|
|
1332
|
+
async function assets(ctx) {
|
|
1333
|
+
if (!ctx.options.public) return;
|
|
1334
|
+
if (ctx.method !== "get") return;
|
|
1335
|
+
if (ctx.url.pathname === "/") return;
|
|
1336
|
+
try {
|
|
1337
|
+
const asset = await ctx.options.public.read(ctx.url.pathname);
|
|
1338
|
+
if (!asset) return;
|
|
1339
|
+
return type(ctx.url.pathname.split(".").pop()).send(asset);
|
|
1340
|
+
} catch {
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// src/middle/timer.ts
|
|
1345
|
+
var createTime = () => {
|
|
1346
|
+
const times = [["init", performance.now()]];
|
|
1347
|
+
const time = (name) => times.push([name, performance.now()]);
|
|
1348
|
+
time.times = times;
|
|
1349
|
+
time.headers = () => {
|
|
1350
|
+
const r = (t) => Math.round(t);
|
|
1351
|
+
const times2 = time.times;
|
|
1352
|
+
const timing = times2.slice(1).map(([name, time2], i) => `${name};dur=${r(time2 - times2[i][1])}`).join(", ");
|
|
1353
|
+
return timing;
|
|
1354
|
+
};
|
|
1355
|
+
return time;
|
|
1356
|
+
};
|
|
1357
|
+
function timer(ctx) {
|
|
1358
|
+
ctx.time = createTime();
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// src/middle/openapi.ts
|
|
1362
|
+
import * as fsp2 from "fs/promises";
|
|
1363
|
+
var entities = {
|
|
1364
|
+
"&": "&",
|
|
1365
|
+
"<": "<",
|
|
1366
|
+
">": ">",
|
|
1367
|
+
'"': """
|
|
1368
|
+
};
|
|
1369
|
+
var encode = (str = "") => {
|
|
1370
|
+
if (typeof str === "number") str = String(str);
|
|
1371
|
+
if (typeof str !== "string") return "";
|
|
1372
|
+
return str.replace(/[&<>"]/g, (tag) => entities[tag]);
|
|
1373
|
+
};
|
|
1374
|
+
var getConfig = (routes) => {
|
|
1375
|
+
const config2 = routes.find(
|
|
1376
|
+
(r) => typeof r !== "string" && typeof r !== "function" && typeof r === "object"
|
|
1377
|
+
);
|
|
1378
|
+
if (!config2) return {};
|
|
1379
|
+
if (config2.tags) {
|
|
1380
|
+
if (typeof config2.tags === "string") {
|
|
1381
|
+
config2.tags = config2.tags.split(/\s*,\s*/g);
|
|
1382
|
+
}
|
|
1383
|
+
if (!Array.isArray(config2.tags)) {
|
|
1384
|
+
throw new Error("invalid tags");
|
|
1385
|
+
}
|
|
1386
|
+
config2.tags = config2.tags.map((t) => t.trim());
|
|
1387
|
+
}
|
|
1388
|
+
return config2;
|
|
1389
|
+
};
|
|
1390
|
+
function zodToSchema(schema) {
|
|
1391
|
+
const type2 = schema?.def?.type || "string";
|
|
1392
|
+
if (type2 === "object") {
|
|
1393
|
+
const shape = schema.def.shape;
|
|
1394
|
+
const properties = {};
|
|
1395
|
+
const req = [];
|
|
1396
|
+
for (const key in shape) {
|
|
1397
|
+
const field = shape[key];
|
|
1398
|
+
properties[key] = zodToSchema(field);
|
|
1399
|
+
if (!field.isOptional() && !field.isNullable()) {
|
|
1400
|
+
req.push(key);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
const required = req.length ? req : void 0;
|
|
1404
|
+
return { type: type2, properties, required };
|
|
1405
|
+
}
|
|
1406
|
+
if (type2 === "array") {
|
|
1407
|
+
return { type: type2, items: zodToSchema(schema.def.element) };
|
|
1408
|
+
}
|
|
1409
|
+
return { type: type2 };
|
|
1410
|
+
}
|
|
1411
|
+
var pkgProm = fsp2.readFile("package.json", "utf-8").then((data) => JSON.parse(data)).catch(() => ({}));
|
|
1412
|
+
var getTag = (name, fn) => {
|
|
1413
|
+
const found = fn.toString().split("\n").filter((l) => /\s+\/\/\s/.test(l)).map((l) => l.trim().replace("// ", "")).find((l) => l.startsWith(name));
|
|
1414
|
+
if (!found) return "";
|
|
1415
|
+
return encode(found.replace(name, "").trim());
|
|
1416
|
+
};
|
|
1417
|
+
var getDescription = (fn) => getTag("@description", fn) || "";
|
|
1418
|
+
var getReturn = (fn) => getTag("@returns", fn) || "OK";
|
|
1419
|
+
var generateOpenApiPaths = (handlers) => {
|
|
1420
|
+
const paths = {};
|
|
1421
|
+
for (const [method, routes] of Object.entries(handlers)) {
|
|
1422
|
+
for (const route of routes) {
|
|
1423
|
+
const [_, path2, fn, meta] = [
|
|
1424
|
+
route[0],
|
|
1425
|
+
route[1],
|
|
1426
|
+
route.find((p) => typeof p === "function"),
|
|
1427
|
+
route.find((p) => typeof p === "object")
|
|
1428
|
+
];
|
|
1429
|
+
const config2 = getConfig(route);
|
|
1430
|
+
if (typeof path2 !== "string" || path2 === "*" || path2 === "/docs" || !fn) {
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
const normalizedPath = path2.replace(/\(\w+\)/gi, "").replace(/:([a-zA-Z0-9_]+)/g, "{$1}");
|
|
1434
|
+
if (!paths[normalizedPath]) {
|
|
1435
|
+
paths[normalizedPath] = {};
|
|
1436
|
+
}
|
|
1437
|
+
const getTitle = (fn2) => {
|
|
1438
|
+
if (!fn2.name) return null;
|
|
1439
|
+
const wrongNames = ["default"];
|
|
1440
|
+
if (wrongNames.includes(fn2.name)) return null;
|
|
1441
|
+
if (fn2.name.length <= 3) return null;
|
|
1442
|
+
if (fn2.name.includes("_")) return fn2.name.replace(/_/g, " ");
|
|
1443
|
+
const name = fn2.name.split(/(?=[A-Z])/).join(" ").toLowerCase();
|
|
1444
|
+
return name[0].toUpperCase() + name.slice(1);
|
|
1445
|
+
};
|
|
1446
|
+
let requestBody;
|
|
1447
|
+
if (meta?.body) {
|
|
1448
|
+
const schema = zodToSchema(meta.body);
|
|
1449
|
+
requestBody = { content: { "application/json": { schema } } };
|
|
1450
|
+
}
|
|
1451
|
+
let responses;
|
|
1452
|
+
if (meta?.response) {
|
|
1453
|
+
const schema = zodToSchema(meta.response);
|
|
1454
|
+
const description = getReturn(fn);
|
|
1455
|
+
responses = {
|
|
1456
|
+
200: { description, content: { "application/json": { schema } } }
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
const parameters = [];
|
|
1460
|
+
const matched = Array.from(path2.matchAll(/:[\w()]+/gi));
|
|
1461
|
+
matched.forEach((match) => {
|
|
1462
|
+
const [name, type2 = "string"] = match[0].slice(1).replace(/\)/, "").split("(");
|
|
1463
|
+
parameters.push({
|
|
1464
|
+
name,
|
|
1465
|
+
in: "path",
|
|
1466
|
+
required: true,
|
|
1467
|
+
schema: { type: type2 }
|
|
1468
|
+
});
|
|
1469
|
+
});
|
|
1470
|
+
if (meta?.query) {
|
|
1471
|
+
Object.entries(meta.query).map(([key, value]) => ({
|
|
1472
|
+
name: key,
|
|
1473
|
+
in: "query",
|
|
1474
|
+
required: false,
|
|
1475
|
+
schema: { type: typeof value },
|
|
1476
|
+
example: value
|
|
1477
|
+
}));
|
|
1478
|
+
}
|
|
1479
|
+
paths[normalizedPath][method] = {
|
|
1480
|
+
tags: config2.tags,
|
|
1481
|
+
summary: config2.title || getTag("@title", fn) || `${method.toUpperCase()} ${normalizedPath}`,
|
|
1482
|
+
description: getTitle(fn) || getDescription(fn),
|
|
1483
|
+
requestBody,
|
|
1484
|
+
parameters,
|
|
1485
|
+
responses
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
return paths;
|
|
1490
|
+
};
|
|
1491
|
+
var openapi_default = async (ctx) => {
|
|
1492
|
+
const pkg = await pkgProm;
|
|
1493
|
+
const domain = pkg.homepage || ctx.url.origin;
|
|
1494
|
+
const openApi = {
|
|
1495
|
+
openapi: "3.0.0",
|
|
1496
|
+
info: {
|
|
1497
|
+
title: pkg.name || "API Documentation",
|
|
1498
|
+
version: pkg.version || "1.0.0",
|
|
1499
|
+
description: pkg.description || ""
|
|
1500
|
+
},
|
|
1501
|
+
servers: domain ? [{ url: domain }] : [],
|
|
1502
|
+
paths: generateOpenApiPaths(ctx.app.handlers)
|
|
1503
|
+
};
|
|
1504
|
+
const configuration = ctx.options.openapi?.scalar || {};
|
|
1505
|
+
return `
|
|
1506
|
+
<!doctype html>
|
|
1507
|
+
<html>
|
|
1508
|
+
<head>
|
|
1509
|
+
<title>API Reference</title>
|
|
1510
|
+
<meta charset="utf-8" />
|
|
1511
|
+
<meta
|
|
1512
|
+
name="viewport"
|
|
1513
|
+
content="width=device-width, initial-scale=1" />
|
|
1514
|
+
<style>.open-api-client-button {display: none!important;}</style>
|
|
1515
|
+
</head>
|
|
1516
|
+
<body>
|
|
1517
|
+
<script id="api-reference" type="application/json" data-configuration="${encode(JSON.stringify(configuration))}">${JSON.stringify(openApi, null, 2)}</script>
|
|
1518
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
1519
|
+
</body>
|
|
1520
|
+
</html> `;
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
// src/middle/index.ts
|
|
1524
|
+
var auth2 = auth_default.middle;
|
|
1525
|
+
|
|
1526
|
+
// src/router.ts
|
|
1527
|
+
var Router = class _Router {
|
|
1528
|
+
handlers = {
|
|
1529
|
+
socket: [],
|
|
1530
|
+
get: [],
|
|
1531
|
+
head: [],
|
|
1532
|
+
post: [],
|
|
1533
|
+
put: [],
|
|
1534
|
+
patch: [],
|
|
1535
|
+
delete: [],
|
|
1536
|
+
options: []
|
|
1537
|
+
};
|
|
1538
|
+
// For the router we can just return itself since it's not the final export,
|
|
1539
|
+
// but then on the root it'll return some fancy wrappers
|
|
1540
|
+
self() {
|
|
1541
|
+
return this;
|
|
1542
|
+
}
|
|
1543
|
+
handle(method, path2, ...middleware) {
|
|
1544
|
+
if (typeof path2 !== "string") {
|
|
1545
|
+
middleware.unshift(path2);
|
|
1546
|
+
path2 = "*";
|
|
1547
|
+
}
|
|
1548
|
+
const methods = method === "*" ? Object.keys(this.handlers) : [method];
|
|
1549
|
+
for (const m of methods) {
|
|
1550
|
+
this.handlers[m].push([method, path2, ...middleware]);
|
|
1551
|
+
}
|
|
1552
|
+
return this.self();
|
|
1553
|
+
}
|
|
1554
|
+
socket(path2, ...middleware) {
|
|
1555
|
+
return this.handle("socket", path2, ...middleware);
|
|
1556
|
+
}
|
|
1557
|
+
get(path2, ...middleware) {
|
|
1558
|
+
return this.handle("get", path2, ...middleware);
|
|
1559
|
+
}
|
|
1560
|
+
head(path2, ...middleware) {
|
|
1561
|
+
return this.handle("head", path2, ...middleware);
|
|
1562
|
+
}
|
|
1563
|
+
post(path2, ...middleware) {
|
|
1564
|
+
return this.handle("post", path2, ...middleware);
|
|
1565
|
+
}
|
|
1566
|
+
put(path2, ...middleware) {
|
|
1567
|
+
return this.handle("put", path2, ...middleware);
|
|
1568
|
+
}
|
|
1569
|
+
patch(path2, ...middleware) {
|
|
1570
|
+
return this.handle("patch", path2, ...middleware);
|
|
1571
|
+
}
|
|
1572
|
+
del(path2, ...middleware) {
|
|
1573
|
+
return this.handle("delete", path2, ...middleware);
|
|
1574
|
+
}
|
|
1575
|
+
options(path2, ...middleware) {
|
|
1576
|
+
return this.handle("options", path2, ...middleware);
|
|
1577
|
+
}
|
|
1578
|
+
use(...args) {
|
|
1579
|
+
const path2 = typeof args[0] === "string" ? args.shift() : "*";
|
|
1580
|
+
if (args[0] instanceof _Router) {
|
|
1581
|
+
const basePath = `/${path2.replace(/\*$/, "")}/`.replace(/^\/+/, "/").replace(/\/+$/, "/");
|
|
1582
|
+
const handlers = args[0].handlers;
|
|
1583
|
+
for (const m in handlers) {
|
|
1584
|
+
for (const [method, path3, ...middleware] of handlers[m]) {
|
|
1585
|
+
const fullPath = basePath + path3.replace(/^\//, "");
|
|
1586
|
+
this.handlers[m].push([method, fullPath, ...middleware]);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
return this.self();
|
|
1590
|
+
}
|
|
1591
|
+
return this.handle("*", path2, ...args);
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
function router() {
|
|
1595
|
+
return new Router();
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// src/ServerTest.ts
|
|
1599
|
+
function isSerializable(body) {
|
|
1600
|
+
if (!body) return false;
|
|
1601
|
+
if (typeof body === "string") return false;
|
|
1602
|
+
if (body instanceof ReadableStream) return false;
|
|
1603
|
+
if (body instanceof FormData) return false;
|
|
1604
|
+
return true;
|
|
1605
|
+
}
|
|
1606
|
+
function ServerTest(app) {
|
|
1607
|
+
const port = app.settings.port;
|
|
1608
|
+
const fetch2 = async (path2, method, options = {}) => {
|
|
1609
|
+
try {
|
|
1610
|
+
if (!options.headers) options.headers = {};
|
|
1611
|
+
if (isSerializable(options.body)) {
|
|
1612
|
+
options.headers["content-type"] = "application/json";
|
|
1613
|
+
options.body = JSON.stringify(options.body);
|
|
1614
|
+
}
|
|
1615
|
+
const res = await app.fetch(
|
|
1616
|
+
new Request(`http://localhost:${port}${path2}`, {
|
|
1617
|
+
method,
|
|
1618
|
+
...options
|
|
1619
|
+
})
|
|
1620
|
+
);
|
|
1621
|
+
const headers2 = parseHeaders_default(res.headers);
|
|
1622
|
+
let body;
|
|
1623
|
+
if (headers2["content-type"]?.includes("application/json")) {
|
|
1624
|
+
body = await res.json();
|
|
1625
|
+
} else {
|
|
1626
|
+
body = await res.text();
|
|
1627
|
+
}
|
|
1628
|
+
return { status: res.status, headers: headers2, body };
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
return { status: 500, headers: {}, body: error.message };
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1633
|
+
return {
|
|
1634
|
+
get: (path2, options) => fetch2(path2, "get", options),
|
|
1635
|
+
head: (path2, options) => fetch2(path2, "head", options),
|
|
1636
|
+
post: (path2, body, options) => fetch2(path2, "post", { body, ...options }),
|
|
1637
|
+
put: (path2, body, options) => fetch2(path2, "put", { body, ...options }),
|
|
1638
|
+
patch: (path2, body, options) => fetch2(path2, "patch", { body, ...options }),
|
|
1639
|
+
delete: (path2, options) => fetch2(path2, "delete", options),
|
|
1640
|
+
options: (path2, options) => fetch2(path2, "options", options)
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// src/context/parseBody.ts
|
|
1645
|
+
function getBoundary(header) {
|
|
1646
|
+
if (!header) return null;
|
|
1647
|
+
if (header.includes("multipart/form-data") && !header.includes("boundary=")) {
|
|
1648
|
+
console.error("Do not set the `Content-Type` manually for FormData");
|
|
1649
|
+
}
|
|
1650
|
+
const items = header.split(";");
|
|
1651
|
+
for (const item of items) {
|
|
1652
|
+
const trimmedItem = item.trim();
|
|
1653
|
+
if (trimmedItem.startsWith("boundary=")) {
|
|
1654
|
+
return trimmedItem.split("=")[1].trim();
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
function getMatching(string, regex) {
|
|
1660
|
+
const matches = string.match(regex);
|
|
1661
|
+
return matches?.[1] ?? "";
|
|
1662
|
+
}
|
|
1663
|
+
var saveFile = async (name, value, bucket) => {
|
|
1664
|
+
const ext = name.split(".").pop();
|
|
1665
|
+
const id = `${createId()}.${ext}`;
|
|
1666
|
+
await bucket.write(id, value);
|
|
1667
|
+
return id;
|
|
1668
|
+
};
|
|
1669
|
+
function splitBuffer(buffer, delimiter) {
|
|
1670
|
+
const result = [];
|
|
1671
|
+
let start = 0;
|
|
1672
|
+
let index = buffer.indexOf(delimiter);
|
|
1673
|
+
while (index !== -1) {
|
|
1674
|
+
result.push(buffer.slice(start, index));
|
|
1675
|
+
start = index + delimiter.length;
|
|
1676
|
+
index = buffer.indexOf(delimiter, start);
|
|
1677
|
+
}
|
|
1678
|
+
result.push(buffer.slice(start));
|
|
1679
|
+
return result;
|
|
1680
|
+
}
|
|
1681
|
+
var BREAK_BUFFER = Buffer.from("\r\n\r\n");
|
|
1682
|
+
function isProbablyText(buffer) {
|
|
1683
|
+
for (let i = 0; i < Math.min(buffer.length, 512); i++) {
|
|
1684
|
+
const byte = buffer[i];
|
|
1685
|
+
if (byte === 0) return false;
|
|
1686
|
+
if (byte < 7 || byte > 13 && byte < 32) return false;
|
|
1687
|
+
}
|
|
1688
|
+
return true;
|
|
1689
|
+
}
|
|
1690
|
+
async function parseBody(raw, contentType, bucket) {
|
|
1691
|
+
const contentTypeStr = Array.isArray(contentType) ? contentType[0] : contentType;
|
|
1692
|
+
let rawBuffer;
|
|
1693
|
+
if (raw instanceof Buffer) {
|
|
1694
|
+
rawBuffer = raw;
|
|
1695
|
+
} else if ("arrayBuffer" in raw && typeof raw.arrayBuffer === "function") {
|
|
1696
|
+
const arrayBuf = await raw.arrayBuffer();
|
|
1697
|
+
rawBuffer = Buffer.from(arrayBuf);
|
|
1698
|
+
} else {
|
|
1699
|
+
throw new Error("Unsupported raw type");
|
|
1700
|
+
}
|
|
1701
|
+
if (!rawBuffer) return {};
|
|
1702
|
+
if (!contentTypeStr || /text\/plain/.test(contentTypeStr)) {
|
|
1703
|
+
return rawBuffer.toString("utf-8");
|
|
1704
|
+
}
|
|
1705
|
+
if (/application\/json/.test(contentTypeStr)) {
|
|
1706
|
+
return JSON.parse(rawBuffer.toString("utf-8"));
|
|
1707
|
+
}
|
|
1708
|
+
const boundary = getBoundary(contentTypeStr);
|
|
1709
|
+
if (!boundary) return null;
|
|
1710
|
+
const body = {};
|
|
1711
|
+
const boundaryBuffer = Buffer.from(`--${boundary}`);
|
|
1712
|
+
const parts = splitBuffer(rawBuffer, boundaryBuffer);
|
|
1713
|
+
for (const part of parts) {
|
|
1714
|
+
if (part.length === 0 || part.equals(Buffer.from("--\r\n"))) continue;
|
|
1715
|
+
const idx = part.indexOf(BREAK_BUFFER);
|
|
1716
|
+
if (idx === -1) continue;
|
|
1717
|
+
const headerStr = part.slice(0, idx).toString("utf-8");
|
|
1718
|
+
const contentBuf = part.slice(idx + BREAK_BUFFER.length, part.length - 2);
|
|
1719
|
+
const name = getMatching(headerStr, /name="(.+?)"/).trim().replace(/\[\]$/, "");
|
|
1720
|
+
if (!name) continue;
|
|
1721
|
+
const filename = getMatching(headerStr, /filename="(.+?)"/).trim();
|
|
1722
|
+
if (filename) {
|
|
1723
|
+
if (!bucket) throw new Error("Bucket is required to save files");
|
|
1724
|
+
body[name] = await saveFile(filename, contentBuf, bucket);
|
|
1725
|
+
} else {
|
|
1726
|
+
const value = isProbablyText(contentBuf) ? contentBuf.toString("utf-8").trim() : contentBuf;
|
|
1727
|
+
if (body[name]) {
|
|
1728
|
+
if (!Array.isArray(body[name])) body[name] = [body[name]];
|
|
1729
|
+
body[name].push(value);
|
|
1730
|
+
} else {
|
|
1731
|
+
body[name] = value;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
return body;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// src/context/parseCookies.ts
|
|
1739
|
+
function parseCookies(cookies2) {
|
|
1740
|
+
if (!cookies2) return {};
|
|
1741
|
+
const cookieStr = Array.isArray(cookies2) ? cookies2[0] : cookies2;
|
|
1742
|
+
if (!cookieStr) return {};
|
|
1743
|
+
return Object.fromEntries(
|
|
1744
|
+
cookieStr.split(/;\s*/).map((part) => {
|
|
1745
|
+
const [key, ...rest] = part.split("=");
|
|
1746
|
+
return [key, decodeURIComponent(rest.join("="))];
|
|
1747
|
+
})
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
// src/context/winter.ts
|
|
1752
|
+
function isValidMethod(method) {
|
|
1753
|
+
return [
|
|
1754
|
+
"get",
|
|
1755
|
+
"post",
|
|
1756
|
+
"put",
|
|
1757
|
+
"patch",
|
|
1758
|
+
"delete",
|
|
1759
|
+
"head",
|
|
1760
|
+
"options",
|
|
1761
|
+
"socket"
|
|
1762
|
+
].includes(method);
|
|
1763
|
+
}
|
|
1764
|
+
var winter_default = async (request, app) => {
|
|
1765
|
+
try {
|
|
1766
|
+
const ctx = {
|
|
1767
|
+
headers: {},
|
|
1768
|
+
cookies: {},
|
|
1769
|
+
url: void 0,
|
|
1770
|
+
options: app.settings || {},
|
|
1771
|
+
method: "get",
|
|
1772
|
+
init: performance.now(),
|
|
1773
|
+
req: request
|
|
1774
|
+
};
|
|
1775
|
+
const method = request.method.toLowerCase();
|
|
1776
|
+
if (!isValidMethod(method)) {
|
|
1777
|
+
throw new Error(`Invalid HTTP method: ${method}`);
|
|
1778
|
+
}
|
|
1779
|
+
ctx.method = method;
|
|
1780
|
+
const events = {};
|
|
1781
|
+
ctx.on = (name, callback2) => {
|
|
1782
|
+
events[name] = events[name] || [];
|
|
1783
|
+
events[name].push(callback2);
|
|
1784
|
+
};
|
|
1785
|
+
ctx.trigger = (name, data) => {
|
|
1786
|
+
if (!events[name]) return;
|
|
1787
|
+
for (const cb of events[name]) {
|
|
1788
|
+
cb(data);
|
|
1789
|
+
}
|
|
1790
|
+
};
|
|
1791
|
+
ctx.headers = parseHeaders_default(request.headers);
|
|
1792
|
+
ctx.cookies = parseCookies(ctx.headers.cookie);
|
|
1793
|
+
await auth_default.load(ctx);
|
|
1794
|
+
ctx.url = new URL(request.url.replace(/\/$/, ""));
|
|
1795
|
+
define(
|
|
1796
|
+
ctx.url,
|
|
1797
|
+
"query",
|
|
1798
|
+
(url) => Object.fromEntries(url.searchParams.entries())
|
|
1799
|
+
);
|
|
1800
|
+
if (request.body) {
|
|
1801
|
+
const type2 = ctx.headers["content-type"];
|
|
1802
|
+
ctx.body = await parseBody(request, type2, ctx.options.uploads);
|
|
1803
|
+
}
|
|
1804
|
+
ctx.app = app;
|
|
1805
|
+
ctx.platform = app.platform;
|
|
1806
|
+
ctx.machine = app.platform;
|
|
1807
|
+
return ctx;
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
return { error };
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
|
|
1813
|
+
// src/context/node.ts
|
|
1814
|
+
function isValidMethod2(method) {
|
|
1815
|
+
return [
|
|
1816
|
+
"get",
|
|
1817
|
+
"post",
|
|
1818
|
+
"put",
|
|
1819
|
+
"patch",
|
|
1820
|
+
"delete",
|
|
1821
|
+
"head",
|
|
1822
|
+
"options",
|
|
1823
|
+
"socket"
|
|
1824
|
+
].includes(method);
|
|
1825
|
+
}
|
|
1826
|
+
var chunkArray = (arr, size) => arr.length > size ? [arr.slice(0, size), ...chunkArray(arr.slice(size), size)] : [arr];
|
|
1827
|
+
var node_default = async (request, app) => {
|
|
1828
|
+
try {
|
|
1829
|
+
const ctx = {
|
|
1830
|
+
headers: {},
|
|
1831
|
+
cookies: {},
|
|
1832
|
+
url: void 0,
|
|
1833
|
+
options: app.settings || {},
|
|
1834
|
+
method: "get",
|
|
1835
|
+
req: request
|
|
1836
|
+
};
|
|
1837
|
+
const method = request.method?.toLowerCase() || "get";
|
|
1838
|
+
if (!isValidMethod2(method)) {
|
|
1839
|
+
throw new Error(`Invalid HTTP method: ${method}`);
|
|
1840
|
+
}
|
|
1841
|
+
ctx.method = method;
|
|
1842
|
+
const events = {};
|
|
1843
|
+
ctx.on = (name, callback2) => {
|
|
1844
|
+
events[name] = events[name] || [];
|
|
1845
|
+
events[name].push(callback2);
|
|
1846
|
+
};
|
|
1847
|
+
ctx.trigger = (name, data) => {
|
|
1848
|
+
if (!events[name]) return;
|
|
1849
|
+
for (const cb of events[name]) {
|
|
1850
|
+
cb(data);
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
ctx.headers = parseHeaders_default(
|
|
1854
|
+
new Headers(chunkArray(request.rawHeaders, 2))
|
|
1855
|
+
);
|
|
1856
|
+
ctx.cookies = parseCookies(ctx.headers.cookie);
|
|
1857
|
+
await auth_default.load(ctx);
|
|
1858
|
+
const https = request.connection?.encrypted ? "https" : "http";
|
|
1859
|
+
const host = ctx.headers.host || `localhost:${ctx.options.port}`;
|
|
1860
|
+
const path2 = (request.url || "/").replace(/\/$/, "") || "/";
|
|
1861
|
+
ctx.url = new URL(path2, `${https}://${host}`);
|
|
1862
|
+
define(
|
|
1863
|
+
ctx.url,
|
|
1864
|
+
"query",
|
|
1865
|
+
(url) => Object.fromEntries(url.searchParams.entries())
|
|
1866
|
+
);
|
|
1867
|
+
await new Promise((resolve2, reject) => {
|
|
1868
|
+
const body = [];
|
|
1869
|
+
request.on("data", (chunk) => {
|
|
1870
|
+
body.push(chunk);
|
|
1871
|
+
}).on("end", async () => {
|
|
1872
|
+
const type2 = ctx.headers["content-type"];
|
|
1873
|
+
const concatenated = Buffer.concat(body);
|
|
1874
|
+
ctx.body = await parseBody(concatenated, type2, ctx.options.uploads);
|
|
1875
|
+
resolve2();
|
|
1876
|
+
}).on("error", reject);
|
|
1877
|
+
});
|
|
1878
|
+
ctx.app = app;
|
|
1879
|
+
ctx.platform = app.platform;
|
|
1880
|
+
ctx.machine = app.platform;
|
|
1881
|
+
return ctx;
|
|
1882
|
+
} catch (error) {
|
|
1883
|
+
return { error };
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
|
|
1887
|
+
// src/context/handlers.ts
|
|
1888
|
+
var Winter = async (app, request, env3) => {
|
|
1889
|
+
if (env3?.upgrade(request)) return;
|
|
1890
|
+
Object.assign(globalThis.env, env3);
|
|
1891
|
+
const ctx = await winter_default(request, app);
|
|
1892
|
+
if ("error" in ctx) {
|
|
1893
|
+
throw ctx.error;
|
|
1894
|
+
}
|
|
1895
|
+
const res = await handleRequest(app.handlers, ctx);
|
|
1896
|
+
ctx.trigger("finish", { ...ctx, res, end: performance.now() });
|
|
1897
|
+
return res;
|
|
1898
|
+
};
|
|
1899
|
+
var Node = async (app) => {
|
|
1900
|
+
const http = await import("http");
|
|
1901
|
+
http.createServer(async (request, response) => {
|
|
1902
|
+
const ctx = await node_default(request, app);
|
|
1903
|
+
if ("error" in ctx) {
|
|
1904
|
+
throw ctx.error;
|
|
1905
|
+
}
|
|
1906
|
+
const out = await handleRequest(app.handlers, ctx);
|
|
1907
|
+
response.writeHead(out.status || 200, parseHeaders_default(out.headers));
|
|
1908
|
+
if (out.body instanceof ReadableStream) {
|
|
1909
|
+
await iterate(out.body, (chunk) => response.write(chunk));
|
|
1910
|
+
} else {
|
|
1911
|
+
response.write(out.body || "");
|
|
1912
|
+
}
|
|
1913
|
+
response.end();
|
|
1914
|
+
}).listen(app.settings.port);
|
|
1915
|
+
};
|
|
1916
|
+
var Netlify = async (app, request, context) => {
|
|
1917
|
+
request.context = context;
|
|
1918
|
+
if (typeof Netlify === "undefined") {
|
|
1919
|
+
throw new Error("Netlify doesn't exist");
|
|
1920
|
+
}
|
|
1921
|
+
const ctx = await winter_default(request, app);
|
|
1922
|
+
if ("error" in ctx) {
|
|
1923
|
+
throw ctx.error;
|
|
1924
|
+
}
|
|
1925
|
+
return await handleRequest(app.handlers, ctx);
|
|
1926
|
+
};
|
|
1927
|
+
|
|
1928
|
+
// src/index.ts
|
|
1929
|
+
var Server = class extends Router {
|
|
1930
|
+
settings;
|
|
1931
|
+
platform;
|
|
1932
|
+
port;
|
|
1933
|
+
sockets;
|
|
1934
|
+
websocket;
|
|
1935
|
+
constructor(options = {}) {
|
|
1936
|
+
super();
|
|
1937
|
+
this.settings = config(options);
|
|
1938
|
+
this.platform = getMachine();
|
|
1939
|
+
if (this.settings.port) {
|
|
1940
|
+
this.port = this.settings.port;
|
|
1941
|
+
}
|
|
1942
|
+
this.sockets = [];
|
|
1943
|
+
this.websocket = createWebsocket(this.sockets, this.handlers);
|
|
1944
|
+
if (this.platform.runtime === "node") {
|
|
1945
|
+
this.node();
|
|
1946
|
+
}
|
|
1947
|
+
this.use(timer);
|
|
1948
|
+
this.use(assets);
|
|
1949
|
+
if (this.settings.openapi) {
|
|
1950
|
+
this.get(this.settings.openapi.path || "/docs", openapi_default);
|
|
1951
|
+
}
|
|
1952
|
+
if (this.settings.auth) {
|
|
1953
|
+
this.use(auth2);
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
// We need to return a function; some environment expect the default export
|
|
1957
|
+
// to be a function that is called with the request, but we also want to
|
|
1958
|
+
// allow chaining, so we return a function that "extends" the instance
|
|
1959
|
+
self() {
|
|
1960
|
+
const cb = this.callback.bind(this);
|
|
1961
|
+
const proto = Object.getPrototypeOf(this);
|
|
1962
|
+
const keys = Object.keys({ ...this.handlers, ...proto, ...this });
|
|
1963
|
+
for (const key of ["use", "node", "fetch", "callback", "test", ...keys]) {
|
|
1964
|
+
if (typeof this[key] === "function") {
|
|
1965
|
+
cb[key] = this[key].bind(this);
|
|
1966
|
+
} else {
|
|
1967
|
+
cb[key] = this[key];
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
return cb;
|
|
1971
|
+
}
|
|
1972
|
+
// The different handlers for different platforms/runtimes
|
|
1973
|
+
node() {
|
|
1974
|
+
return Node(this);
|
|
1975
|
+
}
|
|
1976
|
+
fetch(request, env3) {
|
|
1977
|
+
return Winter(this, request, env3);
|
|
1978
|
+
}
|
|
1979
|
+
callback(request, context) {
|
|
1980
|
+
return Netlify(this, request, context);
|
|
1981
|
+
}
|
|
1982
|
+
// Helper purely for testing
|
|
1983
|
+
test() {
|
|
1984
|
+
return ServerTest(this);
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
function server(options = {}) {
|
|
1988
|
+
return new Server(options).self();
|
|
1989
|
+
}
|
|
1990
|
+
export {
|
|
1991
|
+
Reply,
|
|
1992
|
+
ServerError_default as ServerError,
|
|
1993
|
+
cookies,
|
|
1994
|
+
server as default,
|
|
1995
|
+
download,
|
|
1996
|
+
file,
|
|
1997
|
+
headers,
|
|
1998
|
+
json,
|
|
1999
|
+
redirect,
|
|
2000
|
+
router,
|
|
2001
|
+
send,
|
|
2002
|
+
status,
|
|
2003
|
+
type,
|
|
2004
|
+
view
|
|
2005
|
+
};
|