@entity-access/server-pages 1.0.42 → 1.1.2

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.
Files changed (65) hide show
  1. package/dist/Page.d.ts +6 -13
  2. package/dist/Page.d.ts.map +1 -1
  3. package/dist/Page.js +10 -41
  4. package/dist/Page.js.map +1 -1
  5. package/dist/ServerPages.d.ts.map +1 -1
  6. package/dist/ServerPages.js +14 -2
  7. package/dist/ServerPages.js.map +1 -1
  8. package/dist/core/CacheProperty.d.ts +5 -0
  9. package/dist/core/CacheProperty.d.ts.map +1 -0
  10. package/dist/core/CacheProperty.js +19 -0
  11. package/dist/core/CacheProperty.js.map +1 -0
  12. package/dist/core/Executor.d.ts +5 -0
  13. package/dist/core/Executor.d.ts.map +1 -0
  14. package/dist/core/Executor.js +13 -0
  15. package/dist/core/Executor.js.map +1 -0
  16. package/dist/core/RouteTree.d.ts.map +1 -1
  17. package/dist/core/RouteTree.js +15 -6
  18. package/dist/core/RouteTree.js.map +1 -1
  19. package/dist/core/SessionUser.d.ts +7 -5
  20. package/dist/core/SessionUser.d.ts.map +1 -1
  21. package/dist/core/SessionUser.js +36 -0
  22. package/dist/core/SessionUser.js.map +1 -1
  23. package/dist/core/Wrapped.d.ts +13 -16
  24. package/dist/core/Wrapped.d.ts.map +1 -1
  25. package/dist/core/Wrapped.js +151 -235
  26. package/dist/core/Wrapped.js.map +1 -1
  27. package/dist/decorators/Prepare.d.ts +14 -0
  28. package/dist/decorators/Prepare.d.ts.map +1 -0
  29. package/dist/decorators/Prepare.js +111 -0
  30. package/dist/decorators/Prepare.js.map +1 -0
  31. package/dist/routes/api/entity/index.d.ts.map +1 -1
  32. package/dist/routes/api/entity/index.js +9 -3
  33. package/dist/routes/api/entity/index.js.map +1 -1
  34. package/dist/routes/api/entity/model/get.d.ts.map +1 -1
  35. package/dist/routes/api/entity/model/get.js.map +1 -1
  36. package/dist/routes/api/entity/query/get.d.ts.map +1 -1
  37. package/dist/routes/api/entity/query/get.js +9 -3
  38. package/dist/routes/api/entity/query/get.js.map +1 -1
  39. package/dist/services/CookieService.d.ts.map +1 -1
  40. package/dist/services/CookieService.js +10 -2
  41. package/dist/services/CookieService.js.map +1 -1
  42. package/dist/socket/SocketService.d.ts.map +1 -1
  43. package/dist/socket/SocketService.js +1 -3
  44. package/dist/socket/SocketService.js.map +1 -1
  45. package/dist/tsconfig.tsbuildinfo +1 -1
  46. package/package.json +1 -1
  47. package/src/Page.tsx +11 -51
  48. package/src/ServerPages.ts +14 -2
  49. package/src/core/CacheProperty.ts +18 -0
  50. package/src/core/Executor.ts +16 -0
  51. package/src/core/RouteTree.ts +7 -3
  52. package/src/core/SessionUser.ts +21 -5
  53. package/src/core/Wrapped.ts +206 -270
  54. package/src/decorators/Prepare.ts +132 -0
  55. package/src/index.d.ts +10 -10
  56. package/src/routes/api/entity/index.tsx +4 -1
  57. package/src/routes/api/entity/model/get.tsx +1 -1
  58. package/src/routes/api/entity/query/get.ts +4 -1
  59. package/src/services/CookieService.ts +10 -2
  60. package/src/socket/SocketService.ts +1 -2
  61. package/dist/decorators/Authorize.d.ts +0 -2
  62. package/dist/decorators/Authorize.d.ts.map +0 -1
  63. package/dist/decorators/Authorize.js +0 -3
  64. package/dist/decorators/Authorize.js.map +0 -1
  65. package/src/decorators/Authorize.ts +0 -3
@@ -10,28 +10,39 @@ import { ServiceProvider } from "@entity-access/entity-access/dist/di/di.js";
10
10
  import CookieService from "../services/CookieService.js";
11
11
  import { stat } from "fs/promises";
12
12
  import TokenService from "../services/TokenService.js";
13
+ import { CacheProperty } from "./CacheProperty.js";
13
14
 
14
15
 
15
16
  type UnwrappedRequest = IncomingMessage | Http2ServerRequest;
16
17
 
18
+ type UnwrappedResponse = ServerResponse | Http2ServerResponse;
19
+
17
20
  export interface IFormData {
18
21
  fields: { [key: string]: string};
19
22
  files: LocalFile[];
20
23
  }
21
24
 
25
+ const extendedSymbol = Symbol("extended");
26
+
22
27
  export interface IWrappedRequest {
23
28
 
29
+ headers?: any;
30
+
31
+ disposables?: Disposable[];
32
+
33
+ response?: WrappedResponse;
34
+
24
35
  get host(): string;
25
36
 
26
37
  get path(): string;
27
38
 
28
- get asyncSessionUser(): Promise<SessionUser>;
39
+ get sessionUser(): SessionUser;
29
40
 
30
- get asyncJsonBody(): Promise<any>;
41
+ get body(): any;
31
42
 
32
- get asyncForm(): Promise<IFormData>;
43
+ get form(): IFormData;
33
44
 
34
- get asyncParams(): Promise<any>;
45
+ get params(): any;
35
46
 
36
47
  get query(): { [key: string]: string};
37
48
 
@@ -45,8 +56,11 @@ export interface IWrappedRequest {
45
56
  accepts(... types: string[]): boolean;
46
57
  }
47
58
 
59
+
48
60
  export interface IWrappedResponse {
49
61
 
62
+ request?: WrappedRequest;
63
+
50
64
  asyncEnd();
51
65
 
52
66
  asyncWrite(buffer: Buffer): Promise<void>;
@@ -70,305 +84,227 @@ export interface IWrappedResponse {
70
84
 
71
85
  }
72
86
 
73
- export type WrappedRequest = UnwrappedRequest & IWrappedRequest & {
74
- scope: ServiceProvider;
75
- response: WrappedResponse;
76
- disposables: Disposable[];
77
- };
78
-
79
- type UnwrappedResponse = ServerResponse | Http2ServerResponse;
80
-
81
- export type WrappedResponse = UnwrappedResponse & IWrappedResponse & {
82
- request: WrappedRequest
83
- };
87
+ export type WrappedRequest = IWrappedRequest & UnwrappedRequest;
84
88
 
85
- const requestMethods: { [P in keyof IWrappedRequest]: (this: WrappedRequest) => any} = {
86
- remoteIPAddress(this: UnwrappedRequest) {
87
- return this.socket?.remoteAddress;
88
- },
89
+ export type WrappedResponse = IWrappedResponse & UnwrappedResponse;
89
90
 
90
- accepts(this: UnwrappedRequest) {
91
- const accepts = (this.headers.accept ?? "").split(";");
92
- return (...types: string[]) => {
93
- if (types.length > 0) {
94
- for (const type of types) {
95
- for (const iterator of accepts) {
96
- if (iterator.includes(type)) {
97
- return true;
98
- }
99
- }
100
- }
101
- return false;
102
- }
103
- return accepts;
104
- };
105
- },
91
+ const extendRequest = (A: typeof IncomingMessage | typeof Http2ServerRequest) => {
106
92
 
107
- URL(this: UnwrappedRequest) {
108
- const w = this as WrappedRequest;
109
- return new URL(this.url, `http://${w.host}`);
110
- },
93
+ let c = A[extendedSymbol];
94
+ if (!c) {
95
+ c = class IntermediateRequest extends A implements IWrappedRequest{
111
96
 
112
- path(this: WrappedRequest) {
113
- return this.URL.pathname;
114
- },
97
+ scope: ServiceProvider;
98
+ disposables: Disposable[];
115
99
 
116
- host(this: UnwrappedRequest) {
117
- return this.headers[":authority"] ?? this.headers["host"];
118
- },
119
- query(this: WrappedRequest) {
120
- const u = this.URL;
121
- const items = {};
122
- for (const [key, value] of u.searchParams.entries()) {
123
- items[key] = value;
124
- }
125
- return items;
126
- },
127
- cookies(this: UnwrappedRequest) {
128
- const cookie = this.headers.cookie;
129
- const cookies = parse(cookie);
130
- return cookies;
131
- },
100
+ get host(): string {
101
+ const r = this as any as (Http2ServerRequest | IncomingMessage);
102
+ const host = (r as Http2ServerRequest).authority || r.headers[":authority"] || r.headers.host || null;
103
+ return CacheProperty.value(this, "host", host);
104
+ }
105
+ get path(): string {
106
+ return this.URL.pathname;
107
+ }
108
+ get cookies(): { [key: string]: string; } {
109
+ const cookie = (this as any as UnwrappedRequest).headers.cookie;
110
+ const cookies = parse(cookie);
111
+ return CacheProperty.value(this, "cookies", cookies);
112
+ }
113
+ get URL(): URL {
114
+ const r = this as any as (Http2ServerRequest | IncomingMessage);
115
+ const url = new URL(r.url, `https:${this.host}`);
116
+ return CacheProperty.value(this, "URL", url);
117
+ }
118
+ get remoteIPAddress(): string {
119
+ const r = this as any as (Http2ServerRequest | IncomingMessage);
120
+ return CacheProperty.value(this, "remoteIPAddress", r.socket.remoteAddress);
121
+ }
132
122
 
133
- async asyncParams(this: WrappedRequest) {
134
- return {
135
- ... this.query,
136
- ... this.asyncJsonBody,
137
- ... this.asyncForm
138
- };
139
- },
123
+ accepts(... types: string[]): any {
124
+ const h = this as any as IncomingMessage;
125
+ const accepts = (h.headers.accept ?? "").split(";");
126
+ const value = (...types: string[]) => {
127
+ if (types.length > 0) {
128
+ for (const type of types) {
129
+ for (const iterator of accepts) {
130
+ if (iterator.includes(type)) {
131
+ return true;
132
+ }
133
+ }
134
+ }
135
+ return false;
136
+ }
137
+ return accepts;
138
+ };
140
139
 
141
- async asyncJsonBody(this: WrappedRequest) {
142
- const req = this;
143
- let buffer = null as Buffer;
144
- let encoding = this.headers["content-encoding"] ?? "utf-8";
145
- const contentType = this.headers["content-type"];
146
- if (!/\/json/i.test(contentType)) {
147
- return {};
148
- }
149
- await new Promise<void>((resolve, reject) => {
150
- req.pipe(new Writable({
151
- write(chunk, enc, callback) {
152
- encoding ||= enc;
153
- let b = typeof chunk === "string"
154
- ? Buffer.from(chunk)
155
- : chunk as Buffer;
156
- buffer = buffer
157
- ? Buffer.concat([buffer, b])
158
- : b;
159
- callback();
160
- },
161
- final(callback) {
162
- resolve();
163
- callback();
164
- },
165
- }), { end: true });
166
- });
167
- const text = buffer.toString(encoding as any);
168
- return JSON.parse(text);
169
- },
140
+ Object.defineProperty(this, "accepts", {
141
+ value,
142
+ enumerable: true,
143
+ configurable: true
144
+ });
170
145
 
171
- async asyncSessionUser(this: WrappedRequest) {
172
- try {
173
- const cookieService = this.scope.resolve(CookieService);
174
- const tokenService = this.scope.resolve(TokenService);
175
- const cookie = this.cookies[tokenService.authCookieName];
176
- const sessionUser = await cookieService.createSessionUserFromCookie(cookie, this.remoteIPAddress);
177
- sessionUser.resp = this.response;
178
- return sessionUser;
179
- } catch (error) {
180
- console.error(error);
181
- const su = this.scope.resolve(SessionUser);
182
- su.resp = this.response;
183
- return su;
146
+ return value( ... types);
147
+ }
148
+
149
+ get query(): any {
150
+ throw new Error("Please decorate `Ensure.parseQuery` callee or call `await Ensure.parseQuery(this)` before accessing this member");
151
+ }
152
+
153
+ get body(): any {
154
+ throw new Error("Please decorate `Ensure.parseBody` callee or call `await Ensure.parseBody(this)` before accessing this member");
155
+ }
156
+
157
+ get form(): any {
158
+ throw new Error("Please decorate `Ensure.parseForm` callee or call `await Ensure.parseForm(this)` before accessing this member");
159
+ }
160
+
161
+ get params(): any {
162
+ throw new Error("Please decorate `Ensure.parseAll` callee or call `await Ensure.parseAll(this)` before accessing this member");
163
+ }
164
+
165
+ get sessionUser(): any {
166
+ throw new Error("Please decorate `Ensure.authorize` callee or call `await Ensure.authorize(this)` before accessing this member");
167
+ }
168
+
184
169
  }
185
- },
186
-
187
- async asyncForm(this: WrappedRequest) {
188
- let tempFolder: TempFolder;
189
- const result: IFormData = {
190
- fields: {},
191
- files: []
192
- };
193
- const req = this;
194
- const bb = busboy({ headers: req.headers, defParamCharset: "utf8" });
195
- const tasks = [];
196
- await new Promise((resolve, reject) => {
197
-
198
- bb.on("field", (name, value) => {
199
- result.fields[name] = value;
200
- });
201
-
202
- bb.on("file", (name, file, info) => {
203
- if (!tempFolder) {
204
- tempFolder = new TempFolder();
205
- this.disposables.push(tempFolder);
206
- }
207
- const tf = tempFolder.get(info.filename, info.mimeType);
208
- tasks.push(tf.writeAll(file).then(() => {
209
- result.files.push(tf);
210
- }));
211
- });
212
- bb.on("error", reject);
213
- bb.on("close", resolve);
214
- req.pipe(bb);
215
- });
216
- await Promise.all(tasks);
217
- return result;
170
+ A[extendedSymbol] = c;
218
171
  }
172
+ return c;
219
173
  };
220
174
 
221
- const responseMethods: { [P in keyof IWrappedResponse]: (this: WrappedResponse) => any} = {
175
+ const extendResponse = (A: typeof ServerResponse | typeof Http2ServerResponse) => {
176
+ let c = A[extendedSymbol];
177
+ if (!c) {
178
+ c = class WrappedResponse extends A implements IWrappedResponse {
222
179
 
223
- asyncEnd() {
224
- return () => new Promise<void>((resolve) => this.end(resolve));
225
- },
226
-
227
- asyncWrite() {
228
- return (buffer: Buffer, start?: number, length?: number) => {
229
- return new Promise((resolve) =>
230
- this.write(buffer, resolve)
231
- );
232
- };
233
- },
180
+ statusCode: number;
234
181
 
235
- cookie() {
236
- return (name: string, value: string, options = {}) => {
237
- const cv = this.getHeaders()["set-cookie"];
238
- const cookies = Array.isArray(cv) ? cv : [cv];
239
- const nk = cookies.filter((x) => x.startsWith(name + "="));
240
- nk.push(serialize(name, value, options));
241
- this.setHeader("set-cookie", nk);
242
- }
243
- },
244
-
245
- send(this: WrappedResponse) {
246
- return async (data: Buffer | string, status: number = 200) => {
247
- try {
248
- this.statusCode = status;
249
- this.writeHead(this.statusCode, this.getHeaders());
250
- await new Promise<void>((resolve, reject) => {
251
- this.write(data, (error) => error ? reject(error) : resolve());
252
- });
253
- return this.asyncEnd();
254
- } catch (error) {
255
- console.error(error);
182
+ asyncEnd(this: UnwrappedResponse) {
183
+ return new Promise<void>((resolve) => this.end(resolve));
256
184
  }
257
- };
258
- },
259
- sendRedirect() {
260
- return (location: string, permanent = false) => {
261
- this.statusCode = 301;
262
- this.writeHead(this.statusCode, {
263
- location
264
- });
265
- return this.asyncEnd();
266
- }
267
- },
268
- sendFile() {
269
- return async (filePath: string, options?: {
270
- acceptRanges?: boolean,
271
- cacheControl?: boolean,
272
- maxAge?: number,
273
- etag?: boolean,
274
- immutable?: boolean,
275
- headers?: { [key: string]: string},
276
- lastModified?: boolean
277
- }) => {
278
- /** Calculate Size of file */
279
- const { size } = await stat(filePath);
280
- const range = this.request.headers.range;
281
-
282
- const lf = new LocalFile(filePath);
283
-
284
- /** Check for Range header */
285
- if (!range) {
286
- this.writeHead(200, {
287
- "Content-Length": size,
288
- "Content-Type": "video/mp4"
289
- });
290
-
291
- await lf.writeTo(this);
292
-
293
- return this.asyncEnd();
185
+
186
+ asyncWrite(this: UnwrappedResponse, buffer: Buffer, start?: number, length?: number) {
187
+ return new Promise<void>((resolve, reject) =>
188
+ this.write(buffer, (error) => error ? reject(error) : resolve())
189
+ );
294
190
  }
295
-
296
- /** Extracting Start and End value from Range Header */
297
- let [start, end] = range.replace(/bytes=/, "").split("-") as any[];
298
- start = parseInt(start, 10);
299
- end = end ? parseInt(end, 10) : size - 1;
300
-
301
- if (!isNaN(start) && isNaN(end)) {
302
- start = start;
303
- end = size - 1;
191
+
192
+ cookie(this: UnwrappedResponse, name: string, value: string, options = {}) {
193
+ const cv = this.getHeaders()["set-cookie"];
194
+ const cookies = Array.isArray(cv) ? cv : [cv];
195
+ const nk = cookies.filter((x) => !x.startsWith(name + "="));
196
+ nk.push(serialize(name, value, options));
197
+ this.setHeader("set-cookie", nk);
304
198
  }
305
- if (isNaN(start) && !isNaN(end)) {
306
- start = size - end;
307
- end = size - 1;
199
+
200
+ async send(this: UnwrappedResponse, data: Buffer | string, status: number = 200) {
201
+ try {
202
+ const wrapped = (this as any as WrappedResponse);
203
+ wrapped.statusCode = status;
204
+ this.writeHead(wrapped.statusCode, this.getHeaders());
205
+ await new Promise<void>((resolve, reject) => {
206
+ this.write(data, (error) => error ? reject(error) : resolve());
207
+ });
208
+ return (this as any).asyncEnd();
209
+ } catch (error) {
210
+ console.error(error);
211
+ }
308
212
  }
309
213
 
310
- // Handle unavailable range request
311
- if (start >= size || end >= size) {
312
- // Return the 416 Range Not Satisfiable.
313
- this.writeHead(416, {
314
- "Content-Range": `bytes */${size}`
214
+ async sendRedirect(this: UnwrappedResponse, location: string, permanent = false) {
215
+ this.statusCode = 301;
216
+ this.writeHead(this.statusCode, {
217
+ location
315
218
  });
316
- return this.asyncEnd();
219
+ return (this as any as IWrappedResponse).asyncEnd();
317
220
  }
318
221
 
319
- /** Sending Partial Content With HTTP Code 206 */
320
- this.writeHead(206, {
321
- "Content-Range": `bytes ${start}-${end}/${size}`,
322
- "Accept-Ranges": "bytes",
323
- "Content-Length": end - start + 1,
324
- "Content-Type": "video/mp4"
325
- });
326
-
327
- await lf.writeTo(this, start, end);
328
-
222
+ async sendFile(this: UnwrappedResponse, filePath: string, options?: {
223
+ acceptRanges?: boolean,
224
+ cacheControl?: boolean,
225
+ maxAge?: number,
226
+ etag?: boolean,
227
+ immutable?: boolean,
228
+ headers?: { [key: string]: string},
229
+ lastModified?: boolean
230
+ }) {
231
+ /** Calculate Size of file */
232
+ const { size } = await stat(filePath);
233
+ const range = (this as any as IWrappedResponse).request.headers.range;
234
+
235
+ const lf = new LocalFile(filePath);
236
+
237
+ /** Check for Range header */
238
+ if (!range) {
239
+ this.writeHead(200, {
240
+ "Content-Length": size,
241
+ "Content-Type": "video/mp4"
242
+ });
243
+
244
+ await lf.writeTo(this);
245
+
246
+ return (this as any).asyncEnd();
247
+ }
248
+
249
+ /** Extracting Start and End value from Range Header */
250
+ let [start, end] = range.replace(/bytes=/, "").split("-") as any[];
251
+ start = parseInt(start, 10);
252
+ end = end ? parseInt(end, 10) : size - 1;
253
+
254
+ if (!isNaN(start) && isNaN(end)) {
255
+ start = start;
256
+ end = size - 1;
257
+ }
258
+ if (isNaN(start) && !isNaN(end)) {
259
+ start = size - end;
260
+ end = size - 1;
261
+ }
262
+
263
+ // Handle unavailable range request
264
+ if (start >= size || end >= size) {
265
+ // Return the 416 Range Not Satisfiable.
266
+ this.writeHead(416, {
267
+ "Content-Range": `bytes */${size}`
268
+ });
269
+ return (this as any).asyncEnd();
270
+ }
271
+
272
+ /** Sending Partial Content With HTTP Code 206 */
273
+ this.writeHead(206, {
274
+ "Content-Range": `bytes ${start}-${end}/${size}`,
275
+ "Accept-Ranges": "bytes",
276
+ "Content-Length": end - start + 1,
277
+ "Content-Type": "video/mp4"
278
+ });
279
+
280
+ await lf.writeTo(this, start, end);
281
+
282
+ }
329
283
  }
330
- },
331
- };
284
+ }
285
+ return c;
286
+ }
287
+
332
288
 
333
289
  export const Wrapped = {
334
290
  request: (req: UnwrappedRequest) => {
335
- for (const key in requestMethods) {
336
- if (Object.prototype.hasOwnProperty.call(requestMethods, key)) {
337
- const element = requestMethods[key];
338
- Object.defineProperty(req, key, {
339
- get() {
340
- const value = element.call(this);
341
- Object.defineProperty(this, key, { value, enumerable: true, writable: false });
342
- return value;
343
- },
344
- enumerable: true,
345
- configurable: true
346
- });
347
- }
348
- }
291
+ let prototype = Object.getPrototypeOf(req);
292
+ const { constructor } = prototype;
293
+ prototype = extendRequest(constructor);
294
+ Object.setPrototypeOf(req, prototype);
349
295
  const wr = req as WrappedRequest;
350
296
  wr.disposables = [];
351
- return wr;
297
+ return req;
352
298
  },
353
299
 
354
300
  response: (req: WrappedRequest, res: UnwrappedResponse) => {
355
- for (const key in responseMethods) {
356
- if (Object.prototype.hasOwnProperty.call(responseMethods, key)) {
357
- const element = responseMethods[key];
358
- Object.defineProperty(res, key, {
359
- get() {
360
- const value = element.call(this);
361
- Object.defineProperty(this, key, { value, enumerable: true, writable: false });
362
- return value;
363
- },
364
- enumerable: true,
365
- configurable: true
366
- });
367
- }
368
- }
301
+ let prototype = Object.getPrototypeOf(res);
302
+ const { constructor } = prototype;
303
+ prototype = extendResponse(constructor);
304
+ Object.setPrototypeOf(res, prototype);
369
305
  const wr = res as WrappedResponse;
370
306
  wr.request = req;
371
307
  req.response = wr;
372
- return wr;
308
+ return res;
373
309
  }
374
310
  }
@@ -0,0 +1,132 @@
1
+ import busboy from "busboy";
2
+ import { Writable } from "node:stream";
3
+ import Page from "../Page.js";
4
+ import TempFolder from "../core/TempFolder.js";
5
+ import { LocalFile } from "../core/LocalFile.js";
6
+
7
+ export const prepareSymbol = Symbol("Parse");
8
+
9
+
10
+ export interface IFormData {
11
+ fields: { [key: string]: string};
12
+ files: LocalFile[];
13
+ }
14
+
15
+ const parseJsonBody = (page?): any => {
16
+
17
+ if (!page) {
18
+ return (target) => parseJsonBody(target);
19
+ }
20
+
21
+ if (!(page instanceof Page)) {
22
+ ((page as any)[prepareSymbol] ??= []).push(parseJsonBody);
23
+ return;
24
+ }
25
+
26
+ if (page.body) {
27
+ return;
28
+ }
29
+
30
+ return (async () => {
31
+
32
+ try {
33
+ let buffer = null as Buffer;
34
+ let encoding = page.request.headers["content-encoding"] ?? "utf-8";
35
+ const contentType = page.request.headers["content-type"];
36
+ if (!/\/json/i.test(contentType)) {
37
+ return {};
38
+ }
39
+ await new Promise<void>((resolve, reject) => {
40
+ page.request.pipe(new Writable({
41
+ write(chunk, enc, callback) {
42
+ encoding ||= enc;
43
+ let b = typeof chunk === "string"
44
+ ? Buffer.from(chunk)
45
+ : chunk as Buffer;
46
+ buffer = buffer
47
+ ? Buffer.concat([buffer, b])
48
+ : b;
49
+ callback();
50
+ },
51
+ final(callback) {
52
+ resolve();
53
+ callback();
54
+ },
55
+ }), { end: true });
56
+ });
57
+ const text = buffer.toString(encoding as any);
58
+ (page as any).body = (page.request as any).body = JSON.parse(text);
59
+ } catch (error) {
60
+ page.reportError(error);
61
+ (page as any).body = (page.request as any).body = {};
62
+ }
63
+ })();
64
+ };
65
+
66
+ const authorize = (page?): any => {
67
+ if (!page) {
68
+ return (target) => authorize(target);
69
+ }
70
+
71
+ if (!(page instanceof Page)) {
72
+ ((page as any)[prepareSymbol] ??= []).push(authorize);
73
+ return;
74
+ }
75
+
76
+ return page.sessionUser.authorize();
77
+ };
78
+
79
+ const parseForm = (page?): any => {
80
+ if (!page) {
81
+ return (target) => parseForm(target);
82
+ }
83
+
84
+ if (!(page instanceof Page)) {
85
+ ((page as any)[prepareSymbol] ??= []).push(parseForm);
86
+ return;
87
+ }
88
+
89
+ return (async () => {
90
+ const req = page.request;
91
+
92
+ let tempFolder: TempFolder;
93
+ const result: IFormData = {
94
+ fields: {},
95
+ files: []
96
+ };
97
+ try {
98
+ const bb = busboy({ headers: req.headers, defParamCharset: "utf8" });
99
+ const tasks = [];
100
+ await new Promise((resolve, reject) => {
101
+
102
+ bb.on("field", (name, value) => {
103
+ result.fields[name] = value;
104
+ });
105
+
106
+ bb.on("file", (name, file, info) => {
107
+ if (!tempFolder) {
108
+ tempFolder = new TempFolder();
109
+ req.disposables.push(tempFolder);
110
+ }
111
+ const tf = tempFolder.get(info.filename, info.mimeType);
112
+ tasks.push(tf.writeAll(file).then(() => {
113
+ result.files.push(tf);
114
+ }));
115
+ });
116
+ bb.on("error", reject);
117
+ bb.on("close", resolve);
118
+ req.pipe(bb);
119
+ });
120
+ await Promise.all(tasks);
121
+ } catch (error) {
122
+ page.reportError(error);
123
+ }
124
+ (page as any).form = (req as any).form = result;
125
+ })();
126
+ };
127
+
128
+ export const Prepare = {
129
+ parseJsonBody,
130
+ authorize,
131
+ parseForm
132
+ };