@entity-access/server-pages 1.0.43 → 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.
- package/dist/Page.d.ts +6 -13
- package/dist/Page.d.ts.map +1 -1
- package/dist/Page.js +10 -44
- package/dist/Page.js.map +1 -1
- package/dist/ServerPages.d.ts.map +1 -1
- package/dist/ServerPages.js +14 -2
- package/dist/ServerPages.js.map +1 -1
- package/dist/core/CacheProperty.d.ts +5 -0
- package/dist/core/CacheProperty.d.ts.map +1 -0
- package/dist/core/CacheProperty.js +19 -0
- package/dist/core/CacheProperty.js.map +1 -0
- package/dist/core/Executor.d.ts +5 -0
- package/dist/core/Executor.d.ts.map +1 -0
- package/dist/core/Executor.js +13 -0
- package/dist/core/Executor.js.map +1 -0
- package/dist/core/RouteTree.d.ts.map +1 -1
- package/dist/core/RouteTree.js +15 -6
- package/dist/core/RouteTree.js.map +1 -1
- package/dist/core/SessionUser.d.ts +7 -5
- package/dist/core/SessionUser.d.ts.map +1 -1
- package/dist/core/SessionUser.js +36 -0
- package/dist/core/SessionUser.js.map +1 -1
- package/dist/core/Wrapped.d.ts +13 -16
- package/dist/core/Wrapped.d.ts.map +1 -1
- package/dist/core/Wrapped.js +151 -235
- package/dist/core/Wrapped.js.map +1 -1
- package/dist/decorators/Prepare.d.ts +14 -0
- package/dist/decorators/Prepare.d.ts.map +1 -0
- package/dist/decorators/Prepare.js +111 -0
- package/dist/decorators/Prepare.js.map +1 -0
- package/dist/routes/api/entity/index.d.ts.map +1 -1
- package/dist/routes/api/entity/index.js +9 -3
- package/dist/routes/api/entity/index.js.map +1 -1
- package/dist/routes/api/entity/model/get.d.ts.map +1 -1
- package/dist/routes/api/entity/model/get.js.map +1 -1
- package/dist/routes/api/entity/query/get.d.ts.map +1 -1
- package/dist/routes/api/entity/query/get.js +9 -3
- package/dist/routes/api/entity/query/get.js.map +1 -1
- package/dist/services/CookieService.d.ts.map +1 -1
- package/dist/services/CookieService.js +8 -0
- package/dist/services/CookieService.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/Page.tsx +11 -53
- package/src/ServerPages.ts +14 -2
- package/src/core/CacheProperty.ts +18 -0
- package/src/core/Executor.ts +16 -0
- package/src/core/RouteTree.ts +7 -2
- package/src/core/SessionUser.ts +21 -5
- package/src/core/Wrapped.ts +206 -270
- package/src/decorators/Prepare.ts +132 -0
- package/src/routes/api/entity/index.tsx +4 -1
- package/src/routes/api/entity/model/get.tsx +1 -1
- package/src/routes/api/entity/query/get.ts +4 -1
- package/src/services/CookieService.ts +9 -1
- package/dist/decorators/Authorize.d.ts +0 -2
- package/dist/decorators/Authorize.d.ts.map +0 -1
- package/dist/decorators/Authorize.js +0 -3
- package/dist/decorators/Authorize.js.map +0 -1
- package/src/decorators/Authorize.ts +0 -3
package/src/core/Wrapped.ts
CHANGED
|
@@ -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
|
|
39
|
+
get sessionUser(): SessionUser;
|
|
29
40
|
|
|
30
|
-
get
|
|
41
|
+
get body(): any;
|
|
31
42
|
|
|
32
|
-
get
|
|
43
|
+
get form(): IFormData;
|
|
33
44
|
|
|
34
|
-
get
|
|
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 =
|
|
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
|
-
|
|
86
|
-
remoteIPAddress(this: UnwrappedRequest) {
|
|
87
|
-
return this.socket?.remoteAddress;
|
|
88
|
-
},
|
|
89
|
+
export type WrappedResponse = IWrappedResponse & UnwrappedResponse;
|
|
89
90
|
|
|
90
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
},
|
|
93
|
+
let c = A[extendedSymbol];
|
|
94
|
+
if (!c) {
|
|
95
|
+
c = class IntermediateRequest extends A implements IWrappedRequest{
|
|
111
96
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
},
|
|
97
|
+
scope: ServiceProvider;
|
|
98
|
+
disposables: Disposable[];
|
|
115
99
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
236
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
|
297
|
+
return req;
|
|
352
298
|
},
|
|
353
299
|
|
|
354
300
|
response: (req: WrappedRequest, res: UnwrappedResponse) => {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
|
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
|
+
};
|