@server/next 0.31.0 → 0.32.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.d.ts +10 -6
- package/index.js +462 -248
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -27,10 +27,12 @@ declare namespace JSX {
|
|
|
27
27
|
[elem: string]: any;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
type BodyMode = "parse" | "raw" | "stream";
|
|
30
31
|
type RouteOptions = {
|
|
31
32
|
tags?: string | string[];
|
|
32
33
|
title?: string;
|
|
33
34
|
description?: string;
|
|
35
|
+
body?: BodyMode;
|
|
34
36
|
};
|
|
35
37
|
type Route = {
|
|
36
38
|
path: string;
|
|
@@ -48,9 +50,11 @@ type Cookie = {
|
|
|
48
50
|
};
|
|
49
51
|
type RouterMethod = "*" | Method;
|
|
50
52
|
type Bucket = {
|
|
53
|
+
location?: string;
|
|
51
54
|
read: (path: string) => Promise<ReadableStream | null>;
|
|
52
|
-
write: (path: string, data: string | Buffer) => Promise<void | string>;
|
|
55
|
+
write: (path: string, data: string | Buffer | ReadableStream) => Promise<void | string>;
|
|
53
56
|
delete: (path: string) => Promise<boolean>;
|
|
57
|
+
folder?: (prefix: string) => Bucket;
|
|
54
58
|
};
|
|
55
59
|
type UploadedFile = {
|
|
56
60
|
name: string;
|
|
@@ -134,7 +138,6 @@ type OnError = (error: Error, ctx: Context) => Response | Promise<Response>;
|
|
|
134
138
|
type Options = {
|
|
135
139
|
port?: number;
|
|
136
140
|
secret?: string;
|
|
137
|
-
views?: string | Bucket;
|
|
138
141
|
public?: string | Bucket;
|
|
139
142
|
uploads?: string | Bucket | UploadPipeline;
|
|
140
143
|
store?: KVStore;
|
|
@@ -149,11 +152,11 @@ type Options = {
|
|
|
149
152
|
log?: LogLevel | boolean;
|
|
150
153
|
favicon?: string | Bucket;
|
|
151
154
|
security?: SecurityOptions;
|
|
155
|
+
body?: BodyMode;
|
|
152
156
|
};
|
|
153
157
|
type Settings = {
|
|
154
158
|
port: number;
|
|
155
159
|
secret: string;
|
|
156
|
-
views?: Bucket;
|
|
157
160
|
public?: Bucket;
|
|
158
161
|
uploads?: Bucket | UploadPipeline;
|
|
159
162
|
store?: KVStore;
|
|
@@ -168,6 +171,7 @@ type Settings = {
|
|
|
168
171
|
log: Logger;
|
|
169
172
|
favicon?: string | Bucket;
|
|
170
173
|
security: SecuritySettings;
|
|
174
|
+
body: BodyMode;
|
|
171
175
|
};
|
|
172
176
|
type Time = {
|
|
173
177
|
(name: string): void;
|
|
@@ -203,7 +207,7 @@ type Context<Params extends Record<string, string | undefined> = Record<string,
|
|
|
203
207
|
ip: string;
|
|
204
208
|
headers: Record<string, string | string[]>;
|
|
205
209
|
cookies: Record<string, string>;
|
|
206
|
-
body?: SerializableValue;
|
|
210
|
+
body?: SerializableValue | Buffer | ReadableStream;
|
|
207
211
|
url: URL & {
|
|
208
212
|
params: Params;
|
|
209
213
|
query: Record<string, string>;
|
|
@@ -230,7 +234,7 @@ type Context<Params extends Record<string, string | undefined> = Record<string,
|
|
|
230
234
|
type InlineReply = Response | {
|
|
231
235
|
body: string;
|
|
232
236
|
headers?: Headers;
|
|
233
|
-
} | SerializableValue | JSX.Element;
|
|
237
|
+
} | SerializableValue | JSX.Element | Buffer | ReadableStream;
|
|
234
238
|
type Body = InlineReply;
|
|
235
239
|
type Middleware<O extends ServerConfig = object, Params extends Record<string, string | undefined> = Record<string, string>> = (ctx: Context<Params, O>) => InlineReply | Promise<InlineReply> | void | Promise<void>;
|
|
236
240
|
|
|
@@ -454,4 +458,4 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
|
|
|
454
458
|
}
|
|
455
459
|
declare function server<Session extends Record<string, any> = {}, User extends Record<string, any> = {}>(options?: Options): Server<ServerConfig<Session, User>>;
|
|
456
460
|
|
|
457
|
-
export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type Bucket, type BunEnv, type Context, type Cookie, type CorsSettings, type EventCallback, type ExtractPathParams, type InferParamType, type InlineReply, type KVStore, type LimitOptions, type LogLevel, type Logger, type Method, type Middleware, type Options, type ParamTypeMap, type ParamsToObject, type PathToParams, type Platform, type Provider, type Route, type RouteOptions, type RouterMethod, type SecurityOptions, type SecuritySettings, type SerializableValue, Server, type ServerConfig, TypedServerError as ServerError, type Settings, type Strategy, type Time, UploadPipeline, type UploadedFile, cookies, server as default, download, file, headers, json, redirect, router, send, status, type, upload };
|
|
461
|
+
export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type BodyMode, type Bucket, type BunEnv, type Context, type Cookie, type CorsSettings, type EventCallback, type ExtractPathParams, type InferParamType, type InlineReply, type KVStore, type LimitOptions, type LogLevel, type Logger, type Method, type Middleware, type Options, type ParamTypeMap, type ParamsToObject, type PathToParams, type Platform, type Provider, type Route, type RouteOptions, type RouterMethod, type SecurityOptions, type SecuritySettings, type SerializableValue, Server, type ServerConfig, TypedServerError as ServerError, type Settings, type Strategy, type Time, UploadPipeline, type UploadedFile, cookies, server as default, download, file, headers, json, redirect, router, send, status, type, upload };
|
package/index.js
CHANGED
|
@@ -89,6 +89,403 @@ if (typeof process !== "undefined") {
|
|
|
89
89
|
Object.assign(globalThis.env, process.env);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// src/helpers/createId.ts
|
|
93
|
+
var alphabet = "useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
|
|
94
|
+
var random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
|
|
95
|
+
var cyrb53 = (str, seed = 0) => {
|
|
96
|
+
if (typeof str !== "string") str = String(str);
|
|
97
|
+
let h1 = 3735928559 ^ seed;
|
|
98
|
+
let h2 = 1103547991 ^ seed;
|
|
99
|
+
for (let i = 0, ch; i < str.length; i++) {
|
|
100
|
+
ch = str.charCodeAt(i);
|
|
101
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
102
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
103
|
+
}
|
|
104
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
|
|
105
|
+
h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
106
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
|
|
107
|
+
h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
108
|
+
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
109
|
+
};
|
|
110
|
+
var hash = (str, size) => {
|
|
111
|
+
let chars = "";
|
|
112
|
+
let num = cyrb53(str);
|
|
113
|
+
for (let i = 0; i < size; i++) {
|
|
114
|
+
if (num < alphabet.length) num = cyrb53(str, i);
|
|
115
|
+
chars += alphabet[num % alphabet.length];
|
|
116
|
+
num = Math.floor(num / alphabet.length);
|
|
117
|
+
}
|
|
118
|
+
return chars;
|
|
119
|
+
};
|
|
120
|
+
var randomId = (size = 16) => {
|
|
121
|
+
let id = "";
|
|
122
|
+
const bytes = random(size);
|
|
123
|
+
while (size--) {
|
|
124
|
+
id += alphabet[bytes[size] & 61];
|
|
125
|
+
}
|
|
126
|
+
return id;
|
|
127
|
+
};
|
|
128
|
+
function createId(source, size = 16) {
|
|
129
|
+
if (source) return hash(source, size);
|
|
130
|
+
return randomId(size);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/helpers/upload.ts
|
|
134
|
+
function parseBytes(value) {
|
|
135
|
+
if (typeof value === "number") return value;
|
|
136
|
+
const units = {
|
|
137
|
+
b: 1,
|
|
138
|
+
kb: 1024,
|
|
139
|
+
mb: 1024 ** 2,
|
|
140
|
+
gb: 1024 ** 3
|
|
141
|
+
};
|
|
142
|
+
const match = value.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)$/);
|
|
143
|
+
if (!match) throw new Error(`Invalid size: "${value}"`);
|
|
144
|
+
return parseFloat(match[1]) * (units[match[2]] ?? 1);
|
|
145
|
+
}
|
|
146
|
+
function getExt(filename) {
|
|
147
|
+
const i = filename.lastIndexOf(".");
|
|
148
|
+
if (i <= 0) return ".bin";
|
|
149
|
+
return filename.slice(i).toLowerCase();
|
|
150
|
+
}
|
|
151
|
+
async function saveFileToBucket(originalName, data, bucket, contentType) {
|
|
152
|
+
const ext = getExt(originalName);
|
|
153
|
+
const id = `${createId()}${ext}`;
|
|
154
|
+
const path2 = await bucket.write(id, data);
|
|
155
|
+
return { name: originalName, id, path: path2, type: contentType, size: data.length };
|
|
156
|
+
}
|
|
157
|
+
var UploadPipeline = class {
|
|
158
|
+
_bucket;
|
|
159
|
+
_limits = {};
|
|
160
|
+
constructor(bucket) {
|
|
161
|
+
this._bucket = bucket ?? null;
|
|
162
|
+
}
|
|
163
|
+
limit(options) {
|
|
164
|
+
this._limits = { ...this._limits, ...options };
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
store(bucket) {
|
|
168
|
+
this._bucket = bucket;
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
async processFile(originalName, data, contentType) {
|
|
172
|
+
const { maxSize, minSize, fileType } = this._limits;
|
|
173
|
+
if (maxSize !== void 0 && data.length > parseBytes(maxSize)) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`File "${originalName}" is too large (${data.length} bytes, limit is ${maxSize})`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (minSize !== void 0 && data.length < parseBytes(minSize)) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`File "${originalName}" is too small (${data.length} bytes, minimum is ${minSize})`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (fileType && fileType.length > 0) {
|
|
184
|
+
const ext = getExt(originalName);
|
|
185
|
+
const mime = contentType.toLowerCase();
|
|
186
|
+
const allowed = fileType.some(
|
|
187
|
+
(t) => t.toLowerCase() === mime || t.toLowerCase() === ext
|
|
188
|
+
);
|
|
189
|
+
if (!allowed) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`File type not allowed for "${originalName}" (got "${contentType}", allowed: ${fileType.join(", ")})`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!this._bucket) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`No destination configured. Pass a bucket to upload() or call .store()`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
return saveFileToBucket(originalName, data, this._bucket, contentType);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
function upload(bucket) {
|
|
204
|
+
return new UploadPipeline(bucket);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/helpers/parseBody.ts
|
|
208
|
+
function getBoundary(header) {
|
|
209
|
+
if (!header) return null;
|
|
210
|
+
if (header.includes("multipart/form-data") && !header.includes("boundary=")) {
|
|
211
|
+
console.error("Do not set the `Content-Type` manually for FormData");
|
|
212
|
+
}
|
|
213
|
+
const items = header.split(";");
|
|
214
|
+
for (const item of items) {
|
|
215
|
+
const trimmedItem = item.trim();
|
|
216
|
+
if (trimmedItem.startsWith("boundary=")) {
|
|
217
|
+
return trimmedItem.split("=")[1].trim();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
function getMatching(string, regex) {
|
|
223
|
+
const matches = string.match(regex);
|
|
224
|
+
return matches?.[1] ?? "";
|
|
225
|
+
}
|
|
226
|
+
function isProbablyText(buffer) {
|
|
227
|
+
for (let i = 0; i < Math.min(buffer.length, 512); i++) {
|
|
228
|
+
const byte = buffer[i];
|
|
229
|
+
if (byte === 0) return false;
|
|
230
|
+
if (byte < 7 || byte > 13 && byte < 32) return false;
|
|
231
|
+
}
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
var MIME_EXT = {
|
|
235
|
+
"application/json": ".json",
|
|
236
|
+
"application/pdf": ".pdf",
|
|
237
|
+
"application/zip": ".zip",
|
|
238
|
+
"text/plain": ".txt",
|
|
239
|
+
"text/html": ".html",
|
|
240
|
+
"text/csv": ".csv",
|
|
241
|
+
"image/jpeg": ".jpg",
|
|
242
|
+
"image/png": ".png",
|
|
243
|
+
"image/gif": ".gif",
|
|
244
|
+
"image/webp": ".webp",
|
|
245
|
+
"image/svg+xml": ".svg",
|
|
246
|
+
"video/mp4": ".mp4",
|
|
247
|
+
"audio/mpeg": ".mp3"
|
|
248
|
+
};
|
|
249
|
+
function extFromType(type2) {
|
|
250
|
+
const base = (type2 || "").split(";")[0].trim().toLowerCase();
|
|
251
|
+
if (MIME_EXT[base]) return MIME_EXT[base];
|
|
252
|
+
const sub = base.split("/")[1];
|
|
253
|
+
return sub && /^[a-z0-9]+$/.test(sub) ? `.${sub}` : ".bin";
|
|
254
|
+
}
|
|
255
|
+
var asIterable = (s) => s;
|
|
256
|
+
function toStream(input) {
|
|
257
|
+
if (input instanceof ReadableStream) return input;
|
|
258
|
+
return new ReadableStream({
|
|
259
|
+
start(controller) {
|
|
260
|
+
controller.enqueue(input);
|
|
261
|
+
controller.close();
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
async function toBuffer(input) {
|
|
266
|
+
if (!(input instanceof ReadableStream)) return input;
|
|
267
|
+
const chunks = [];
|
|
268
|
+
for await (const chunk of asIterable(input)) chunks.push(Buffer.from(chunk));
|
|
269
|
+
return Buffer.concat(chunks);
|
|
270
|
+
}
|
|
271
|
+
function parseUrlEncoded(text) {
|
|
272
|
+
const out = {};
|
|
273
|
+
for (const [key, value] of new URLSearchParams(text)) {
|
|
274
|
+
const existing = out[key];
|
|
275
|
+
if (existing === void 0) out[key] = value;
|
|
276
|
+
else if (Array.isArray(existing)) existing.push(value);
|
|
277
|
+
else out[key] = [existing, value];
|
|
278
|
+
}
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
function addField(body, name, value) {
|
|
282
|
+
if (body[name] === void 0) {
|
|
283
|
+
body[name] = value;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (!Array.isArray(body[name])) body[name] = [body[name]];
|
|
287
|
+
body[name].push(value);
|
|
288
|
+
}
|
|
289
|
+
function startPart(headerStr, dest) {
|
|
290
|
+
const name = getMatching(headerStr, /name="(.+?)"/).trim().replace(/\[\]$/, "");
|
|
291
|
+
if (!name) return { kind: "skip" };
|
|
292
|
+
const filename = getMatching(headerStr, /filename="(.+?)"/).trim();
|
|
293
|
+
if (!filename) return { kind: "text", name, chunks: [] };
|
|
294
|
+
const type2 = getMatching(headerStr, /Content-Type:\s*([^\r\n]+)/i).trim() || "application/octet-stream";
|
|
295
|
+
if (!dest) return { kind: "drop" };
|
|
296
|
+
if (dest instanceof UploadPipeline) {
|
|
297
|
+
return { kind: "pipefile", name, filename, type: type2, pipeline: dest, chunks: [] };
|
|
298
|
+
}
|
|
299
|
+
const id = `${createId()}${getExt(filename)}`;
|
|
300
|
+
let controller;
|
|
301
|
+
const readable = new ReadableStream({
|
|
302
|
+
start(c) {
|
|
303
|
+
controller = c;
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
return {
|
|
307
|
+
kind: "file",
|
|
308
|
+
name,
|
|
309
|
+
filename,
|
|
310
|
+
type: type2,
|
|
311
|
+
id,
|
|
312
|
+
controller,
|
|
313
|
+
write: dest.write(id, readable),
|
|
314
|
+
size: 0
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function feedPart(part, data) {
|
|
318
|
+
if (data.length === 0) return;
|
|
319
|
+
if (part.kind === "text" || part.kind === "pipefile") part.chunks.push(data);
|
|
320
|
+
else if (part.kind === "file") {
|
|
321
|
+
part.controller.enqueue(data);
|
|
322
|
+
part.size += data.length;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async function endPart(part, body) {
|
|
326
|
+
if (part.kind === "text") {
|
|
327
|
+
const buf = Buffer.concat(part.chunks);
|
|
328
|
+
const value = isProbablyText(buf) ? buf.toString("utf-8").trim() : buf;
|
|
329
|
+
addField(body, part.name, value);
|
|
330
|
+
} else if (part.kind === "pipefile") {
|
|
331
|
+
const buf = Buffer.concat(part.chunks);
|
|
332
|
+
const ref = await part.pipeline.processFile(part.filename, buf, part.type);
|
|
333
|
+
addField(body, part.name, ref);
|
|
334
|
+
} else if (part.kind === "file") {
|
|
335
|
+
part.controller.close();
|
|
336
|
+
const path2 = await part.write;
|
|
337
|
+
addField(body, part.name, {
|
|
338
|
+
name: part.filename,
|
|
339
|
+
id: part.id,
|
|
340
|
+
path: path2,
|
|
341
|
+
type: part.type,
|
|
342
|
+
size: part.size
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
var BREAK = Buffer.from("\r\n\r\n");
|
|
347
|
+
async function parseMultipart(stream, boundary, dest) {
|
|
348
|
+
const delim = Buffer.from(`\r
|
|
349
|
+
--${boundary}`);
|
|
350
|
+
const body = {};
|
|
351
|
+
let buf = Buffer.from("\r\n");
|
|
352
|
+
let state = "boundary";
|
|
353
|
+
let part = null;
|
|
354
|
+
for await (const chunk of asIterable(stream)) {
|
|
355
|
+
buf = Buffer.concat([buf, Buffer.from(chunk)]);
|
|
356
|
+
let advanced = true;
|
|
357
|
+
while (advanced) {
|
|
358
|
+
advanced = false;
|
|
359
|
+
if (state === "boundary") {
|
|
360
|
+
const i = buf.indexOf(delim);
|
|
361
|
+
if (i === -1) {
|
|
362
|
+
if (buf.length >= delim.length) {
|
|
363
|
+
buf = buf.subarray(buf.length - delim.length + 1);
|
|
364
|
+
}
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
if (buf.length < i + delim.length + 2) break;
|
|
368
|
+
const after = i + delim.length;
|
|
369
|
+
if (buf[after] === 45 && buf[after + 1] === 45) return body;
|
|
370
|
+
buf = buf.subarray(after + 2);
|
|
371
|
+
state = "headers";
|
|
372
|
+
advanced = true;
|
|
373
|
+
} else if (state === "headers") {
|
|
374
|
+
const i = buf.indexOf(BREAK);
|
|
375
|
+
if (i === -1) break;
|
|
376
|
+
part = startPart(buf.subarray(0, i).toString("utf-8"), dest);
|
|
377
|
+
buf = buf.subarray(i + BREAK.length);
|
|
378
|
+
state = "body";
|
|
379
|
+
advanced = true;
|
|
380
|
+
} else {
|
|
381
|
+
const i = buf.indexOf(delim);
|
|
382
|
+
if (i === -1) {
|
|
383
|
+
const safe = buf.length - (delim.length - 1);
|
|
384
|
+
if (safe > 0 && part) {
|
|
385
|
+
feedPart(part, buf.subarray(0, safe));
|
|
386
|
+
buf = buf.subarray(safe);
|
|
387
|
+
}
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
if (part) {
|
|
391
|
+
feedPart(part, buf.subarray(0, i));
|
|
392
|
+
await endPart(part, body);
|
|
393
|
+
part = null;
|
|
394
|
+
}
|
|
395
|
+
buf = buf.subarray(i);
|
|
396
|
+
state = "boundary";
|
|
397
|
+
advanced = true;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (part) await endPart(part, body);
|
|
402
|
+
return body;
|
|
403
|
+
}
|
|
404
|
+
async function streamToBucket(stream, type2, bucket) {
|
|
405
|
+
const id = `${createId()}${extFromType(type2)}`;
|
|
406
|
+
let size = 0;
|
|
407
|
+
let controller;
|
|
408
|
+
const readable = new ReadableStream({
|
|
409
|
+
start(c) {
|
|
410
|
+
controller = c;
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
const write = bucket.write(id, readable);
|
|
414
|
+
for await (const chunk of asIterable(stream)) {
|
|
415
|
+
controller.enqueue(chunk);
|
|
416
|
+
size += chunk.byteLength;
|
|
417
|
+
}
|
|
418
|
+
controller.close();
|
|
419
|
+
const path2 = await write;
|
|
420
|
+
if (!size) return void 0;
|
|
421
|
+
return { name: id, id, path: path2, type: type2, size };
|
|
422
|
+
}
|
|
423
|
+
async function parseBody(input, contentType, dest) {
|
|
424
|
+
const type2 = Array.isArray(contentType) ? contentType[0] : contentType;
|
|
425
|
+
const boundary = type2 && /multipart\/form-data/i.test(type2) ? getBoundary(type2) : null;
|
|
426
|
+
if (boundary) return parseMultipart(toStream(input), boundary, dest);
|
|
427
|
+
if (!type2 || /^text\//i.test(type2)) {
|
|
428
|
+
const buf = await toBuffer(input);
|
|
429
|
+
return buf.length ? buf.toString("utf-8") : void 0;
|
|
430
|
+
}
|
|
431
|
+
if (/application\/json/i.test(type2)) {
|
|
432
|
+
const buf = await toBuffer(input);
|
|
433
|
+
return buf.length ? JSON.parse(buf.toString("utf-8")) : void 0;
|
|
434
|
+
}
|
|
435
|
+
if (/application\/x-www-form-urlencoded/i.test(type2)) {
|
|
436
|
+
const buf = await toBuffer(input);
|
|
437
|
+
return buf.length ? parseUrlEncoded(buf.toString("utf-8")) : void 0;
|
|
438
|
+
}
|
|
439
|
+
if (!dest) {
|
|
440
|
+
const buf = await toBuffer(input);
|
|
441
|
+
return buf.length ? buf : void 0;
|
|
442
|
+
}
|
|
443
|
+
if (dest instanceof UploadPipeline) {
|
|
444
|
+
const buf = await toBuffer(input);
|
|
445
|
+
return buf.length ? dest.processFile(`upload${extFromType(type2)}`, buf, type2) : void 0;
|
|
446
|
+
}
|
|
447
|
+
return streamToBucket(toStream(input), type2, dest);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/helpers/body.ts
|
|
451
|
+
var sources = /* @__PURE__ */ new WeakMap();
|
|
452
|
+
function setBodySource(ctx, source) {
|
|
453
|
+
sources.set(ctx, source);
|
|
454
|
+
}
|
|
455
|
+
async function resolveBody(ctx, mode) {
|
|
456
|
+
const source = sources.get(ctx);
|
|
457
|
+
if (!source) return void 0;
|
|
458
|
+
if (mode === "stream") return source.getStream();
|
|
459
|
+
if (mode === "raw") {
|
|
460
|
+
const raw = await source.getBuffer();
|
|
461
|
+
if (!raw.length) return void 0;
|
|
462
|
+
if (!ctx.headers["content-length"]) {
|
|
463
|
+
ctx.headers["content-length"] = String(raw.length);
|
|
464
|
+
}
|
|
465
|
+
return raw;
|
|
466
|
+
}
|
|
467
|
+
const stream = source.getStream();
|
|
468
|
+
if (!stream) return void 0;
|
|
469
|
+
let size = 0;
|
|
470
|
+
const counted = stream.pipeThrough(
|
|
471
|
+
new TransformStream({
|
|
472
|
+
transform(chunk, controller) {
|
|
473
|
+
size += chunk.byteLength;
|
|
474
|
+
controller.enqueue(chunk);
|
|
475
|
+
}
|
|
476
|
+
})
|
|
477
|
+
);
|
|
478
|
+
const body = await parseBody(
|
|
479
|
+
counted,
|
|
480
|
+
ctx.headers["content-type"],
|
|
481
|
+
ctx.options.uploads
|
|
482
|
+
);
|
|
483
|
+
if (size && !ctx.headers["content-length"]) {
|
|
484
|
+
ctx.headers["content-length"] = String(size);
|
|
485
|
+
}
|
|
486
|
+
return body;
|
|
487
|
+
}
|
|
488
|
+
|
|
92
489
|
// src/helpers/clientIp.ts
|
|
93
490
|
var first = (v) => (Array.isArray(v) ? v[0] : v) || "";
|
|
94
491
|
var normalize = (ip) => ip.replace(/^::ffff:/, "");
|
|
@@ -558,7 +955,7 @@ async function emailRegister(ctx) {
|
|
|
558
955
|
strategy: ctx.options.auth.strategy,
|
|
559
956
|
provider: "email",
|
|
560
957
|
email,
|
|
561
|
-
password: await
|
|
958
|
+
password: await hash2(password),
|
|
562
959
|
time,
|
|
563
960
|
...data
|
|
564
961
|
};
|
|
@@ -579,7 +976,7 @@ async function emailUpdatePassword(ctx) {
|
|
|
579
976
|
if (!fullUser) throw ServerError_default.AUTH_NO_USER();
|
|
580
977
|
const isValid = await verify(passwords.previous, fullUser.password);
|
|
581
978
|
if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
|
|
582
|
-
fullUser.password = await
|
|
979
|
+
fullUser.password = await hash2(passwords.updated);
|
|
583
980
|
await updateUser(fullUser, ctx.user, ctx.options.auth.store);
|
|
584
981
|
return 200;
|
|
585
982
|
}
|
|
@@ -797,12 +1194,23 @@ function thinLocalBucket(root) {
|
|
|
797
1194
|
}
|
|
798
1195
|
});
|
|
799
1196
|
},
|
|
800
|
-
write: (name, value, type2) => {
|
|
1197
|
+
write: async (name, value, type2) => {
|
|
801
1198
|
const fullPath = absolute(name);
|
|
802
|
-
if (value)
|
|
803
|
-
|
|
1199
|
+
if (!value) return fs.createWriteStream(fullPath);
|
|
1200
|
+
await fsp.mkdir(path.dirname(fullPath), { recursive: true });
|
|
1201
|
+
if (value instanceof ReadableStream) {
|
|
1202
|
+
const writable = fs.createWriteStream(fullPath);
|
|
1203
|
+
for await (const chunk of value) {
|
|
1204
|
+
writable.write(chunk);
|
|
1205
|
+
}
|
|
1206
|
+
await new Promise((resolve2, reject) => {
|
|
1207
|
+
writable.on("error", reject);
|
|
1208
|
+
writable.end(resolve2);
|
|
1209
|
+
});
|
|
1210
|
+
return fullPath;
|
|
804
1211
|
}
|
|
805
|
-
|
|
1212
|
+
await fsp.writeFile(fullPath, value, type2);
|
|
1213
|
+
return fullPath;
|
|
806
1214
|
},
|
|
807
1215
|
delete: async (name) => {
|
|
808
1216
|
const fullPath = absolute(name);
|
|
@@ -812,29 +1220,35 @@ function thinLocalBucket(root) {
|
|
|
812
1220
|
} catch {
|
|
813
1221
|
return false;
|
|
814
1222
|
}
|
|
815
|
-
}
|
|
1223
|
+
},
|
|
1224
|
+
folder: (prefix) => thinLocalBucket(path.join(root, prefix))
|
|
816
1225
|
};
|
|
817
1226
|
}
|
|
818
|
-
function thinBunBucket(s3) {
|
|
1227
|
+
function thinBunBucket(s3, prefix = "") {
|
|
1228
|
+
const key = (name) => prefix ? `${prefix}/${name}` : name;
|
|
819
1229
|
return {
|
|
820
1230
|
read: async (name) => {
|
|
821
|
-
const file2 = s3.file(name);
|
|
1231
|
+
const file2 = s3.file(key(name));
|
|
822
1232
|
if (!await file2.exists()) return null;
|
|
823
1233
|
return await file2.stream();
|
|
824
1234
|
},
|
|
825
1235
|
write: async (name, value) => {
|
|
826
|
-
const file2 = s3.file(name);
|
|
1236
|
+
const file2 = s3.file(key(name));
|
|
827
1237
|
if (value) {
|
|
828
1238
|
await file2.write(value);
|
|
829
|
-
return name;
|
|
1239
|
+
return key(name);
|
|
830
1240
|
}
|
|
831
|
-
return s3.presign(name, {
|
|
1241
|
+
return s3.presign(key(name), {
|
|
1242
|
+
expiresIn: 3600,
|
|
1243
|
+
acl: "public-read-write"
|
|
1244
|
+
});
|
|
832
1245
|
},
|
|
833
1246
|
delete: async (name) => {
|
|
834
|
-
const file2 = s3.file(name);
|
|
1247
|
+
const file2 = s3.file(key(name));
|
|
835
1248
|
if (!await file2.exists()) return null;
|
|
836
1249
|
return await file2.delete();
|
|
837
|
-
}
|
|
1250
|
+
},
|
|
1251
|
+
folder: (sub) => thinBunBucket(s3, key(sub))
|
|
838
1252
|
};
|
|
839
1253
|
}
|
|
840
1254
|
function bucket_default(root) {
|
|
@@ -848,47 +1262,6 @@ function bucket_default(root) {
|
|
|
848
1262
|
return root;
|
|
849
1263
|
}
|
|
850
1264
|
|
|
851
|
-
// src/helpers/createId.ts
|
|
852
|
-
var alphabet = "useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
|
|
853
|
-
var random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
|
|
854
|
-
var cyrb53 = (str, seed = 0) => {
|
|
855
|
-
if (typeof str !== "string") str = String(str);
|
|
856
|
-
let h1 = 3735928559 ^ seed;
|
|
857
|
-
let h2 = 1103547991 ^ seed;
|
|
858
|
-
for (let i = 0, ch; i < str.length; i++) {
|
|
859
|
-
ch = str.charCodeAt(i);
|
|
860
|
-
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
861
|
-
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
862
|
-
}
|
|
863
|
-
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
|
|
864
|
-
h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
865
|
-
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
|
|
866
|
-
h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
867
|
-
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
868
|
-
};
|
|
869
|
-
var hash2 = (str, size) => {
|
|
870
|
-
let chars = "";
|
|
871
|
-
let num = cyrb53(str);
|
|
872
|
-
for (let i = 0; i < size; i++) {
|
|
873
|
-
if (num < alphabet.length) num = cyrb53(str, i);
|
|
874
|
-
chars += alphabet[num % alphabet.length];
|
|
875
|
-
num = Math.floor(num / alphabet.length);
|
|
876
|
-
}
|
|
877
|
-
return chars;
|
|
878
|
-
};
|
|
879
|
-
var randomId = (size = 16) => {
|
|
880
|
-
let id = "";
|
|
881
|
-
const bytes = random(size);
|
|
882
|
-
while (size--) {
|
|
883
|
-
id += alphabet[bytes[size] & 61];
|
|
884
|
-
}
|
|
885
|
-
return id;
|
|
886
|
-
};
|
|
887
|
-
function createId(source, size = 16) {
|
|
888
|
-
if (source) return hash2(source, size);
|
|
889
|
-
return randomId(size);
|
|
890
|
-
}
|
|
891
|
-
|
|
892
1265
|
// src/helpers/color.ts
|
|
893
1266
|
var map = {
|
|
894
1267
|
reset: 0,
|
|
@@ -998,80 +1371,6 @@ function createLogger(level) {
|
|
|
998
1371
|
};
|
|
999
1372
|
}
|
|
1000
1373
|
|
|
1001
|
-
// src/helpers/upload.ts
|
|
1002
|
-
function parseBytes(value) {
|
|
1003
|
-
if (typeof value === "number") return value;
|
|
1004
|
-
const units = {
|
|
1005
|
-
b: 1,
|
|
1006
|
-
kb: 1024,
|
|
1007
|
-
mb: 1024 ** 2,
|
|
1008
|
-
gb: 1024 ** 3
|
|
1009
|
-
};
|
|
1010
|
-
const match = value.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)$/);
|
|
1011
|
-
if (!match) throw new Error(`Invalid size: "${value}"`);
|
|
1012
|
-
return parseFloat(match[1]) * (units[match[2]] ?? 1);
|
|
1013
|
-
}
|
|
1014
|
-
function getExt(filename) {
|
|
1015
|
-
const i = filename.lastIndexOf(".");
|
|
1016
|
-
if (i <= 0) return ".bin";
|
|
1017
|
-
return filename.slice(i).toLowerCase();
|
|
1018
|
-
}
|
|
1019
|
-
async function saveFileToBucket(originalName, data, bucket, contentType) {
|
|
1020
|
-
const ext = getExt(originalName);
|
|
1021
|
-
const id = `${createId()}${ext}`;
|
|
1022
|
-
const path2 = await bucket.write(id, data);
|
|
1023
|
-
return { name: originalName, id, path: path2, type: contentType, size: data.length };
|
|
1024
|
-
}
|
|
1025
|
-
var UploadPipeline = class {
|
|
1026
|
-
_bucket;
|
|
1027
|
-
_limits = {};
|
|
1028
|
-
constructor(bucket) {
|
|
1029
|
-
this._bucket = bucket ?? null;
|
|
1030
|
-
}
|
|
1031
|
-
limit(options) {
|
|
1032
|
-
this._limits = { ...this._limits, ...options };
|
|
1033
|
-
return this;
|
|
1034
|
-
}
|
|
1035
|
-
store(bucket) {
|
|
1036
|
-
this._bucket = bucket;
|
|
1037
|
-
return this;
|
|
1038
|
-
}
|
|
1039
|
-
async processFile(originalName, data, contentType) {
|
|
1040
|
-
const { maxSize, minSize, fileType } = this._limits;
|
|
1041
|
-
if (maxSize !== void 0 && data.length > parseBytes(maxSize)) {
|
|
1042
|
-
throw new Error(
|
|
1043
|
-
`File "${originalName}" is too large (${data.length} bytes, limit is ${maxSize})`
|
|
1044
|
-
);
|
|
1045
|
-
}
|
|
1046
|
-
if (minSize !== void 0 && data.length < parseBytes(minSize)) {
|
|
1047
|
-
throw new Error(
|
|
1048
|
-
`File "${originalName}" is too small (${data.length} bytes, minimum is ${minSize})`
|
|
1049
|
-
);
|
|
1050
|
-
}
|
|
1051
|
-
if (fileType && fileType.length > 0) {
|
|
1052
|
-
const ext = getExt(originalName);
|
|
1053
|
-
const mime = contentType.toLowerCase();
|
|
1054
|
-
const allowed = fileType.some(
|
|
1055
|
-
(t) => t.toLowerCase() === mime || t.toLowerCase() === ext
|
|
1056
|
-
);
|
|
1057
|
-
if (!allowed) {
|
|
1058
|
-
throw new Error(
|
|
1059
|
-
`File type not allowed for "${originalName}" (got "${contentType}", allowed: ${fileType.join(", ")})`
|
|
1060
|
-
);
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
if (!this._bucket) {
|
|
1064
|
-
throw new Error(
|
|
1065
|
-
`No destination configured. Pass a bucket to upload() or call .store()`
|
|
1066
|
-
);
|
|
1067
|
-
}
|
|
1068
|
-
return saveFileToBucket(originalName, data, this._bucket, contentType);
|
|
1069
|
-
}
|
|
1070
|
-
};
|
|
1071
|
-
function upload(bucket) {
|
|
1072
|
-
return new UploadPipeline(bucket);
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
1374
|
// src/helpers/config.ts
|
|
1076
1375
|
function config(options = {}) {
|
|
1077
1376
|
const env2 = globalThis.env;
|
|
@@ -1082,6 +1381,9 @@ function config(options = {}) {
|
|
|
1082
1381
|
port: options.port || env2.PORT || 3e3,
|
|
1083
1382
|
secret: options.secret || env2.SECRET || `unsafe-${createId()}`,
|
|
1084
1383
|
log,
|
|
1384
|
+
// How request bodies are read: parsed into ctx.body by default; `raw` keeps
|
|
1385
|
+
// the Buffer, `stream` hands the handler the unread web ReadableStream.
|
|
1386
|
+
body: options.body ?? "parse",
|
|
1085
1387
|
// Trust X-Forwarded-* headers for ctx.ip (on by default; set it to false
|
|
1086
1388
|
// when clients connect directly so a client can't spoof its IP).
|
|
1087
1389
|
security: {
|
|
@@ -1124,7 +1426,6 @@ function config(options = {}) {
|
|
|
1124
1426
|
}
|
|
1125
1427
|
settings.cors = cors2;
|
|
1126
1428
|
}
|
|
1127
|
-
settings.views = options.views ? bucket_default(options.views) : null;
|
|
1128
1429
|
settings.public = options.public ? bucket_default(options.public) : null;
|
|
1129
1430
|
settings.uploads = options.uploads instanceof UploadPipeline ? options.uploads : options.uploads ? bucket_default(options.uploads) : null;
|
|
1130
1431
|
if (options.favicon) settings.favicon = options.favicon;
|
|
@@ -1154,7 +1455,6 @@ function config(options = {}) {
|
|
|
1154
1455
|
log.message("auth", `${settings.auth.provider.join(", ")} auth enabled`);
|
|
1155
1456
|
}
|
|
1156
1457
|
if (settings.public) log.message("public", loc(options.public));
|
|
1157
|
-
if (settings.views) log.message("views", loc(options.views));
|
|
1158
1458
|
if (settings.uploads) log.message("uploads", loc(options.uploads));
|
|
1159
1459
|
if (settings.session) log.message("session", "enabled");
|
|
1160
1460
|
if (settings.cors) {
|
|
@@ -1276,6 +1576,9 @@ async function parseResponse(out, ctx) {
|
|
|
1276
1576
|
if (out instanceof ReadableStream) {
|
|
1277
1577
|
out = new Response(out);
|
|
1278
1578
|
}
|
|
1579
|
+
if (out instanceof Uint8Array) {
|
|
1580
|
+
out = new Response(out);
|
|
1581
|
+
}
|
|
1279
1582
|
if (typeof out === "number") {
|
|
1280
1583
|
out = new Response(void 0, { status: out });
|
|
1281
1584
|
}
|
|
@@ -1442,6 +1745,7 @@ async function getResponse(app, ctx) {
|
|
|
1442
1745
|
if (Object.keys(route.options).length) {
|
|
1443
1746
|
ctx.options = { ...app.settings, ...route.options };
|
|
1444
1747
|
}
|
|
1748
|
+
ctx.body = await resolveBody(ctx, ctx.options.body);
|
|
1445
1749
|
for (const cb of route.fns) {
|
|
1446
1750
|
if (typeof cb === "function") {
|
|
1447
1751
|
const res = await cb(ctx);
|
|
@@ -1454,6 +1758,7 @@ async function getResponse(app, ctx) {
|
|
|
1454
1758
|
break;
|
|
1455
1759
|
}
|
|
1456
1760
|
if (!matched) {
|
|
1761
|
+
ctx.body = await resolveBody(ctx, ctx.options.body);
|
|
1457
1762
|
for (const mw of app.middleware) {
|
|
1458
1763
|
const out = await parseResponse(await mw(ctx), ctx);
|
|
1459
1764
|
if (out) return out;
|
|
@@ -1472,7 +1777,7 @@ async function getResponse(app, ctx) {
|
|
|
1472
1777
|
import * as crypto2 from "crypto";
|
|
1473
1778
|
import { getRandomValues } from "crypto";
|
|
1474
1779
|
import { promisify } from "util";
|
|
1475
|
-
async function
|
|
1780
|
+
async function hash2(password) {
|
|
1476
1781
|
if ("argon2" in crypto2) {
|
|
1477
1782
|
const argon23 = promisify(crypto2.argon2);
|
|
1478
1783
|
const buf = await argon23("argon2id", {
|
|
@@ -1536,102 +1841,6 @@ function iteratorToReadable(generator) {
|
|
|
1536
1841
|
});
|
|
1537
1842
|
}
|
|
1538
1843
|
|
|
1539
|
-
// src/helpers/parseBody.ts
|
|
1540
|
-
function getBoundary(header) {
|
|
1541
|
-
if (!header) return null;
|
|
1542
|
-
if (header.includes("multipart/form-data") && !header.includes("boundary=")) {
|
|
1543
|
-
console.error("Do not set the `Content-Type` manually for FormData");
|
|
1544
|
-
}
|
|
1545
|
-
const items = header.split(";");
|
|
1546
|
-
for (const item of items) {
|
|
1547
|
-
const trimmedItem = item.trim();
|
|
1548
|
-
if (trimmedItem.startsWith("boundary=")) {
|
|
1549
|
-
return trimmedItem.split("=")[1].trim();
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
return null;
|
|
1553
|
-
}
|
|
1554
|
-
function getMatching(string, regex) {
|
|
1555
|
-
const matches = string.match(regex);
|
|
1556
|
-
return matches?.[1] ?? "";
|
|
1557
|
-
}
|
|
1558
|
-
function splitBuffer(buffer, delimiter) {
|
|
1559
|
-
const result = [];
|
|
1560
|
-
let start = 0;
|
|
1561
|
-
let index = buffer.indexOf(delimiter);
|
|
1562
|
-
while (index !== -1) {
|
|
1563
|
-
result.push(buffer.slice(start, index));
|
|
1564
|
-
start = index + delimiter.length;
|
|
1565
|
-
index = buffer.indexOf(delimiter, start);
|
|
1566
|
-
}
|
|
1567
|
-
result.push(buffer.slice(start));
|
|
1568
|
-
return result;
|
|
1569
|
-
}
|
|
1570
|
-
var BREAK_BUFFER = Buffer.from("\r\n\r\n");
|
|
1571
|
-
var END_BUFFER = Buffer.from("--\r\n");
|
|
1572
|
-
function isProbablyText(buffer) {
|
|
1573
|
-
for (let i = 0; i < Math.min(buffer.length, 512); i++) {
|
|
1574
|
-
const byte = buffer[i];
|
|
1575
|
-
if (byte === 0) return false;
|
|
1576
|
-
if (byte < 7 || byte > 13 && byte < 32) return false;
|
|
1577
|
-
}
|
|
1578
|
-
return true;
|
|
1579
|
-
}
|
|
1580
|
-
async function parseBody(raw, contentType, bucket) {
|
|
1581
|
-
const contentTypeStr = Array.isArray(contentType) ? contentType[0] : contentType;
|
|
1582
|
-
if (!raw || raw.length === 0) return {};
|
|
1583
|
-
if (!contentTypeStr || /^text\//.test(contentTypeStr)) {
|
|
1584
|
-
return raw.toString("utf-8");
|
|
1585
|
-
}
|
|
1586
|
-
if (/application\/json/.test(contentTypeStr)) {
|
|
1587
|
-
return JSON.parse(raw.toString("utf-8"));
|
|
1588
|
-
}
|
|
1589
|
-
const boundary = getBoundary(contentTypeStr);
|
|
1590
|
-
if (!boundary) return null;
|
|
1591
|
-
const body = {};
|
|
1592
|
-
const boundaryBuffer = Buffer.from(`--${boundary}`);
|
|
1593
|
-
const parts = splitBuffer(raw, boundaryBuffer);
|
|
1594
|
-
for (const part of parts) {
|
|
1595
|
-
if (part.length === 0 || part.equals(END_BUFFER)) continue;
|
|
1596
|
-
const idx = part.indexOf(BREAK_BUFFER);
|
|
1597
|
-
if (idx === -1) continue;
|
|
1598
|
-
const headerStr = part.slice(0, idx).toString("utf-8");
|
|
1599
|
-
const contentBuf = part.slice(idx + BREAK_BUFFER.length, part.length - 2);
|
|
1600
|
-
const name = getMatching(headerStr, /name="(.+?)"/).trim().replace(/\[\]$/, "");
|
|
1601
|
-
if (!name) continue;
|
|
1602
|
-
const filename = getMatching(headerStr, /filename="(.+?)"/).trim();
|
|
1603
|
-
if (filename) {
|
|
1604
|
-
const partContentType = getMatching(headerStr, /Content-Type:\s*([^\r\n]+)/i).trim() || "application/octet-stream";
|
|
1605
|
-
if (!bucket) {
|
|
1606
|
-
continue;
|
|
1607
|
-
}
|
|
1608
|
-
if (bucket instanceof UploadPipeline) {
|
|
1609
|
-
body[name] = await bucket.processFile(
|
|
1610
|
-
filename,
|
|
1611
|
-
contentBuf,
|
|
1612
|
-
partContentType
|
|
1613
|
-
);
|
|
1614
|
-
} else {
|
|
1615
|
-
body[name] = await saveFileToBucket(
|
|
1616
|
-
filename,
|
|
1617
|
-
contentBuf,
|
|
1618
|
-
bucket,
|
|
1619
|
-
partContentType
|
|
1620
|
-
);
|
|
1621
|
-
}
|
|
1622
|
-
} else {
|
|
1623
|
-
const value = isProbablyText(contentBuf) ? contentBuf.toString("utf-8").trim() : contentBuf;
|
|
1624
|
-
if (body[name]) {
|
|
1625
|
-
if (!Array.isArray(body[name])) body[name] = [body[name]];
|
|
1626
|
-
body[name].push(value);
|
|
1627
|
-
} else {
|
|
1628
|
-
body[name] = value;
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
return body;
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
1844
|
// src/helpers/parseCookies.ts
|
|
1636
1845
|
function parseCookies(cookies2) {
|
|
1637
1846
|
if (!cookies2) return {};
|
|
@@ -1899,13 +2108,13 @@ function auth(app) {
|
|
|
1899
2108
|
app.use(async function middle(ctx) {
|
|
1900
2109
|
ctx.user = await getUser(ctx);
|
|
1901
2110
|
});
|
|
2111
|
+
app.post("/auth/logout", logout);
|
|
1902
2112
|
const enabled = app.settings.auth.provider;
|
|
1903
2113
|
for (const name of oauth2) {
|
|
1904
2114
|
if (!enabled.includes(name)) continue;
|
|
1905
2115
|
const key = name.toUpperCase();
|
|
1906
2116
|
if (!env[`${key}_ID`]) throw new Error(`${key}_ID not defined`);
|
|
1907
2117
|
if (!env[`${key}_SECRET`]) throw new Error(`${key}_SECRET not defined`);
|
|
1908
|
-
app.get("/auth/logout", logout);
|
|
1909
2118
|
app.get(`/auth/login/${name}`, providers_default[name].login);
|
|
1910
2119
|
app.get(`/auth/callback/${name}`, providers_default[name].callback);
|
|
1911
2120
|
}
|
|
@@ -1914,12 +2123,10 @@ function auth(app) {
|
|
|
1914
2123
|
for (const key of keys) {
|
|
1915
2124
|
if (!env[key]) throw new Error(`${key} not defined`);
|
|
1916
2125
|
}
|
|
1917
|
-
app.get("/auth/logout", logout);
|
|
1918
2126
|
app.get("/auth/login/apple", providers_default.apple.login);
|
|
1919
2127
|
app.post("/auth/callback/apple", providers_default.apple.callback);
|
|
1920
2128
|
}
|
|
1921
2129
|
if (enabled.includes("email")) {
|
|
1922
|
-
app.post("/auth/logout", logout);
|
|
1923
2130
|
app.post("/auth/register/email", providers_default.email.register);
|
|
1924
2131
|
app.post("/auth/login/email", providers_default.email.login);
|
|
1925
2132
|
app.put("/auth/password/email", providers_default.email.password);
|
|
@@ -2229,21 +2436,20 @@ async function createNode(req, app) {
|
|
|
2229
2436
|
"query",
|
|
2230
2437
|
(url2) => Object.fromEntries(url2.searchParams.entries())
|
|
2231
2438
|
);
|
|
2232
|
-
const
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
}
|
|
2239
|
-
const body = rawBody ? await parseBody(rawBody, headers2["content-type"], app.settings.uploads) : void 0;
|
|
2439
|
+
const source = {
|
|
2440
|
+
getBuffer: () => new Promise((resolve2, reject) => {
|
|
2441
|
+
const chunks2 = [];
|
|
2442
|
+
req.on("data", (chunk) => chunks2.push(chunk)).on("end", () => resolve2(Buffer.concat(chunks2))).on("error", reject);
|
|
2443
|
+
}),
|
|
2444
|
+
getStream: () => toWeb(req)
|
|
2445
|
+
};
|
|
2240
2446
|
const events = createEvents();
|
|
2241
|
-
|
|
2447
|
+
const ctx = {
|
|
2242
2448
|
options: app.settings,
|
|
2243
2449
|
platform: app.platform,
|
|
2244
2450
|
url,
|
|
2245
2451
|
method,
|
|
2246
|
-
body,
|
|
2452
|
+
body: void 0,
|
|
2247
2453
|
headers: headers2,
|
|
2248
2454
|
cookies: cookies2,
|
|
2249
2455
|
session: {},
|
|
@@ -2255,6 +2461,8 @@ async function createNode(req, app) {
|
|
|
2255
2461
|
trustProxy: app.settings.security.trustProxy
|
|
2256
2462
|
})
|
|
2257
2463
|
};
|
|
2464
|
+
setBodySource(ctx, source);
|
|
2465
|
+
return ctx;
|
|
2258
2466
|
}
|
|
2259
2467
|
|
|
2260
2468
|
// src/context/winter.ts
|
|
@@ -2273,18 +2481,17 @@ async function createWinter(req, app, server2) {
|
|
|
2273
2481
|
"query",
|
|
2274
2482
|
(url2) => Object.fromEntries(url2.searchParams.entries())
|
|
2275
2483
|
);
|
|
2276
|
-
const
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
}
|
|
2280
|
-
const body = req.body ? await parseBody(rawBody, headers2["content-type"], app.settings.uploads) : void 0;
|
|
2484
|
+
const source = {
|
|
2485
|
+
getBuffer: async () => Buffer.from(await req.arrayBuffer()),
|
|
2486
|
+
getStream: () => req.body ?? void 0
|
|
2487
|
+
};
|
|
2281
2488
|
const events = createEvents();
|
|
2282
|
-
|
|
2489
|
+
const ctx = {
|
|
2283
2490
|
options: app.settings,
|
|
2284
2491
|
platform: app.platform,
|
|
2285
2492
|
url,
|
|
2286
2493
|
method,
|
|
2287
|
-
body,
|
|
2494
|
+
body: void 0,
|
|
2288
2495
|
headers: headers2,
|
|
2289
2496
|
cookies: cookies2,
|
|
2290
2497
|
session: {},
|
|
@@ -2296,6 +2503,8 @@ async function createWinter(req, app, server2) {
|
|
|
2296
2503
|
trustProxy: app.settings.security.trustProxy
|
|
2297
2504
|
})
|
|
2298
2505
|
};
|
|
2506
|
+
setBodySource(ctx, source);
|
|
2507
|
+
return ctx;
|
|
2299
2508
|
}
|
|
2300
2509
|
|
|
2301
2510
|
// src/context/handlers.ts
|
|
@@ -2322,6 +2531,11 @@ var Node = async (app) => {
|
|
|
2322
2531
|
response.end();
|
|
2323
2532
|
}).listen(app.settings.port, () => {
|
|
2324
2533
|
app.settings.log.start(`http://localhost:${app.settings.port}/`);
|
|
2534
|
+
if (app.handlers.socket.length) {
|
|
2535
|
+
console.warn(
|
|
2536
|
+
"[server] WebSockets (.socket()) are only supported on Bun, not Node"
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2325
2539
|
});
|
|
2326
2540
|
};
|
|
2327
2541
|
var Netlify = async (app, request, context) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@server/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "A fully-fledged web server with routing, file uploads, sessions, static files, schema validation, websockets, testing, etc.",
|
|
5
5
|
"homepage": "https://server-js.com/",
|
|
6
6
|
"repository": "github:franciscop/server-next",
|