@webqit/webflo 0.11.61-0 → 1.0.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.
Files changed (118) hide show
  1. package/.gitignore +7 -7
  2. package/LICENSE +20 -20
  3. package/README.md +2079 -2074
  4. package/docker/Dockerfile +42 -42
  5. package/docker/README.md +91 -91
  6. package/docker/package.json +2 -2
  7. package/package.json +80 -81
  8. package/src/{Context.js → AbstractContext.js} +71 -79
  9. package/src/config-pi/deployment/Env.js +68 -68
  10. package/src/config-pi/deployment/Layout.js +63 -63
  11. package/src/config-pi/deployment/Origins.js +139 -139
  12. package/src/config-pi/deployment/Proxy.js +74 -74
  13. package/src/config-pi/deployment/index.js +17 -17
  14. package/src/config-pi/index.js +15 -15
  15. package/src/config-pi/runtime/Client.js +116 -98
  16. package/src/config-pi/runtime/Server.js +125 -125
  17. package/src/config-pi/runtime/client/Worker.js +109 -134
  18. package/src/config-pi/runtime/client/index.js +11 -11
  19. package/src/config-pi/runtime/index.js +17 -17
  20. package/src/config-pi/runtime/server/Headers.js +74 -74
  21. package/src/config-pi/runtime/server/Redirects.js +69 -69
  22. package/src/config-pi/runtime/server/index.js +13 -13
  23. package/src/config-pi/static/Manifest.js +319 -319
  24. package/src/config-pi/static/Ssg.js +49 -49
  25. package/src/config-pi/static/index.js +13 -13
  26. package/src/deployment-pi/index.js +10 -10
  27. package/src/deployment-pi/origins/index.js +216 -216
  28. package/src/index.js +11 -19
  29. package/src/runtime-pi/HttpEvent.js +126 -106
  30. package/src/runtime-pi/HttpUser.js +126 -0
  31. package/src/runtime-pi/MessagingOverBroadcast.js +9 -0
  32. package/src/runtime-pi/MessagingOverChannel.js +85 -0
  33. package/src/runtime-pi/MessagingOverSocket.js +106 -0
  34. package/src/runtime-pi/MultiportMessagingAPI.js +81 -0
  35. package/src/runtime-pi/WebfloCookieStorage.js +27 -0
  36. package/src/runtime-pi/WebfloEventTarget.js +39 -0
  37. package/src/runtime-pi/WebfloMessageEvent.js +58 -0
  38. package/src/runtime-pi/WebfloMessagingAPI.js +69 -0
  39. package/src/runtime-pi/{Router.js → WebfloRouter.js} +99 -130
  40. package/src/runtime-pi/WebfloRuntime.js +52 -0
  41. package/src/runtime-pi/WebfloStorage.js +109 -0
  42. package/src/runtime-pi/client/ClientMessaging.js +5 -0
  43. package/src/runtime-pi/client/Context.js +3 -7
  44. package/src/runtime-pi/client/CookieStorage.js +17 -0
  45. package/src/runtime-pi/client/Router.js +38 -48
  46. package/src/runtime-pi/client/SessionStorage.js +33 -0
  47. package/src/runtime-pi/client/Url.js +156 -205
  48. package/src/runtime-pi/client/WebfloClient.js +544 -0
  49. package/src/runtime-pi/client/WebfloRootClient1.js +179 -0
  50. package/src/runtime-pi/client/WebfloRootClient2.js +109 -0
  51. package/src/runtime-pi/client/WebfloSubClient.js +165 -0
  52. package/src/runtime-pi/client/Workport.js +118 -178
  53. package/src/runtime-pi/client/generate.js +480 -471
  54. package/src/runtime-pi/client/index.js +16 -21
  55. package/src/runtime-pi/client/worker/ClientMessaging.js +5 -0
  56. package/src/runtime-pi/client/worker/Context.js +3 -7
  57. package/src/runtime-pi/client/worker/CookieStorage.js +17 -0
  58. package/src/runtime-pi/client/worker/SessionStorage.js +13 -0
  59. package/src/runtime-pi/client/worker/WebfloWorker.js +294 -0
  60. package/src/runtime-pi/client/worker/Workport.js +17 -85
  61. package/src/runtime-pi/client/worker/index.js +10 -21
  62. package/src/runtime-pi/index.js +6 -13
  63. package/src/runtime-pi/server/ClientMessaging.js +18 -0
  64. package/src/runtime-pi/server/ClientMessagingRegistry.js +57 -0
  65. package/src/runtime-pi/server/Context.js +11 -15
  66. package/src/runtime-pi/server/CookieStorage.js +17 -0
  67. package/src/runtime-pi/server/Router.js +93 -159
  68. package/src/runtime-pi/server/SessionStorage.js +53 -0
  69. package/src/runtime-pi/server/WebfloServer.js +755 -0
  70. package/src/runtime-pi/server/index.js +10 -21
  71. package/src/runtime-pi/util-http.js +322 -86
  72. package/src/runtime-pi/util-url.js +146 -146
  73. package/src/runtime-pi/xURL.js +108 -105
  74. package/src/runtime-pi/xfetch.js +22 -22
  75. package/src/services-pi/cert/http-auth-hook.js +22 -22
  76. package/src/services-pi/cert/http-cleanup-hook.js +22 -22
  77. package/src/services-pi/cert/index.js +79 -79
  78. package/src/services-pi/index.js +8 -8
  79. package/src/static-pi/index.js +10 -10
  80. package/src/webflo.js +30 -30
  81. package/test/index.test.js +26 -26
  82. package/test/site/package.json +9 -9
  83. package/test/site/public/bundle.html +5 -5
  84. package/test/site/public/bundle.html.json +3 -3
  85. package/test/site/public/bundle.js +2 -2
  86. package/test/site/public/bundle.webflo.js +15 -15
  87. package/test/site/public/index.html +29 -29
  88. package/test/site/public/index1.html +34 -34
  89. package/test/site/public/page-2/bundle.html +4 -4
  90. package/test/site/public/page-2/bundle.js +2 -2
  91. package/test/site/public/page-2/index.html +45 -45
  92. package/test/site/public/page-2/main.html +2 -2
  93. package/test/site/public/page-4/subpage/bundle.js +2 -2
  94. package/test/site/public/page-4/subpage/index.html +30 -30
  95. package/test/site/public/sparoots.json +4 -4
  96. package/test/site/public/worker.js +3 -3
  97. package/test/site/server/index.js +15 -15
  98. package/src/runtime-pi/Application.js +0 -29
  99. package/src/runtime-pi/Cookies.js +0 -82
  100. package/src/runtime-pi/Runtime.js +0 -21
  101. package/src/runtime-pi/client/Application.js +0 -100
  102. package/src/runtime-pi/client/Runtime.js +0 -332
  103. package/src/runtime-pi/client/createStorage.js +0 -57
  104. package/src/runtime-pi/client/oohtml/full.js +0 -7
  105. package/src/runtime-pi/client/oohtml/namespacing.js +0 -7
  106. package/src/runtime-pi/client/oohtml/scripting.js +0 -8
  107. package/src/runtime-pi/client/oohtml/templating.js +0 -8
  108. package/src/runtime-pi/client/worker/Application.js +0 -44
  109. package/src/runtime-pi/client/worker/Runtime.js +0 -269
  110. package/src/runtime-pi/server/Application.js +0 -116
  111. package/src/runtime-pi/server/Runtime.js +0 -557
  112. package/src/runtime-pi/xFormData.js +0 -24
  113. package/src/runtime-pi/xHeaders.js +0 -146
  114. package/src/runtime-pi/xRequest.js +0 -46
  115. package/src/runtime-pi/xRequestHeaders.js +0 -109
  116. package/src/runtime-pi/xResponse.js +0 -33
  117. package/src/runtime-pi/xResponseHeaders.js +0 -117
  118. package/src/runtime-pi/xxHttpMessage.js +0 -102
@@ -1,557 +0,0 @@
1
-
2
- /**
3
- * @imports
4
- */
5
- import Fs from 'fs';
6
- import Path from 'path';
7
- import Http from 'http';
8
- import Https from 'https';
9
- import Sessions from 'client-sessions';
10
- import { Observer } from '@webqit/oohtml-ssr/apis.js';
11
- import { _each } from '@webqit/util/obj/index.js';
12
- import { _isEmpty } from '@webqit/util/js/index.js';
13
- import { _from as _arrFrom, _any } from '@webqit/util/arr/index.js';
14
- import { slice as _streamSlice } from 'stream-slice';
15
- import { Readable as _ReadableStream } from 'stream';
16
- import { pattern } from '../util-url.js';
17
- import xRequest from "../xRequest.js";
18
- import xResponse from "../xResponse.js";
19
- import xfetch from '../xfetch.js';
20
- import HttpEvent from '../HttpEvent.js';
21
- import _Runtime from '../Runtime.js';
22
-
23
- export {
24
- //fetch,
25
- HttpEvent,
26
- Observer,
27
- }
28
-
29
- export default class Runtime extends _Runtime {
30
-
31
- /**
32
- * Runtime
33
- *
34
- * @param Object cx
35
- * @param Function applicationInstance
36
- *
37
- * @return void
38
- */
39
- constructor(cx, applicationInstance) {
40
- super(cx, applicationInstance);
41
- // ---------------
42
- this.ready = (async () => {
43
- // ---------------
44
- const resolveContextObj = async (cx, force = false) => {
45
- if (_isEmpty(cx.layout) || force) { cx.layout = await (new cx.config.deployment.Layout(cx)).read(); }
46
- if (_isEmpty(cx.server) || force) { cx.server = await (new cx.config.runtime.Server(cx)).read(); }
47
- if (_isEmpty(cx.env) || force) { cx.env = await (new cx.config.deployment.Env(cx)).read(); }
48
- };
49
- await resolveContextObj(this.cx);
50
- if (this.cx.env.autoload !== false) {
51
- Object.keys(this.cx.env.entries).forEach(key => {
52
- if (!(key in process.env)) {
53
- process.env[key] = this.cx.env.entries[key];
54
- }
55
- });
56
- }
57
- // ---------------
58
- const parseDomains = domains => _arrFrom(domains).reduce((arr, str) => arr.concat(str.split(',')), []).map(str => str.trim()).filter(str => str);
59
- const selectDomains = (serverDefs, matchingPort = null) => serverDefs.reduce((doms, def) => doms.length ? doms : (((!matchingPort || def.port === matchingPort) && parseDomains(def.domains || def.hostnames)) || []), []);
60
- // ---------------
61
- this.proxied = new Map;
62
- if (this.cx.config.deployment.Proxy) {
63
- const proxied = await (new this.cx.config.deployment.Proxy(this.cx)).read();
64
- await Promise.all((proxied.entries || []).map(async vhost => {
65
- let cx, hostnames = parseDomains(vhost.hostnames), port = vhost.port, proto = vhost.proto;
66
- if (vhost.path) {
67
- cx = this.cx.constructor.create(this.cx, Path.join(this.cx.CWD, vhost.path));
68
- await resolveContextObj(cx, true);
69
- cx.dict.key = true;
70
- // From the server that's most likely to be active
71
- port || (port = cx.server.https.port || cx.server.port);
72
- // The domain list that corresponds to the specified resolved port
73
- hostnames.length || (hostnames = selectDomains([cx.server.https, cx.server], port));
74
- // Or anyone available... hoping that the remote configs can eventually be in sync
75
- hostnames.length || (hostnames = selectDomains([cx.server.https, cx.server]));
76
- // The corresponding proto
77
- proto || (proto = port === cx.server.https.port ? 'https' : 'http');
78
- }
79
- hostnames.length || (hostnames = ['*']);
80
- this.proxied.set(hostnames.sort().join('|'), { cx, hostnames, port, proto });
81
- }));
82
- }
83
- // ---------------
84
- this.servers = new Map;
85
- // ---------------
86
- if (!this.cx.flags['test-only'] && !this.cx.flags['https-only'] && this.cx.server.port) {
87
- const httpServer = Http.createServer((request, response) => handleRequest('http', request, response));
88
- httpServer.listen(this.cx.server.port);
89
- // -------
90
- let domains = parseDomains(this.cx.server.domains);
91
- if (!domains.length) { domains = ['*']; }
92
- this.servers.set('http', {
93
- instance: httpServer,
94
- port: this.cx.server.port,
95
- domains,
96
- });
97
- }
98
- // ---------------
99
- if (!this.cx.flags['test-only'] && !this.cx.flags['http-only'] && this.cx.server.https.port) {
100
- const httpsServer = Https.createServer((request, response) => handleRequest('https', request, response));
101
- httpsServer.listen(this.cx.server.https.port);
102
- // -------
103
- const addSSLContext = (serverConfig, domains) => {
104
- if (!Fs.existsSync(serverConfig.https.keyfile)) return;
105
- const cert = {
106
- key: Fs.readFileSync(serverConfig.https.keyfile),
107
- cert: Fs.readFileSync(serverConfig.https.certfile),
108
- };
109
- domains.forEach(domain => { httpsServer.addContext(domain, cert); });
110
- }
111
- // -------
112
- let domains = parseDomains(this.cx.server.https.domains);
113
- if (!domains.length) { domains = ['*']; }
114
- this.servers.set('https', {
115
- instance: httpsServer,
116
- port: this.cx.server.https.port,
117
- domains,
118
- });
119
- // -------
120
- addSSLContext(this.cx.server, domains);
121
- for (const [ /*id*/, vhost ] of this.proxied) {
122
- vhost.cx && addSSLContext(vhost.cx.server, vhost.hostnames);
123
- }
124
- }
125
- // ---------------
126
- const handleRequest = async (proto, request, response) => {
127
- request[Symbol.toStringTag] = 'ReadableStream';
128
- const [ fullUrl, requestInit ] = await this.parseNodeRequest(proto, request);
129
- let clientResponse = await this.go(fullUrl, requestInit, { request, response });
130
- if (response.headersSent) return;
131
- // --------
132
- _each(clientResponse.headers.json(), (name, value) => {
133
- response.setHeader(name, value);
134
- });
135
- // --------
136
- response.statusCode = clientResponse.status;
137
- response.statusMessage = clientResponse.statusText;
138
- if (clientResponse.headers.location) {
139
- return response.end();
140
- }
141
- if ((clientResponse.body instanceof _ReadableStream)) {
142
- return clientResponse.body.pipe(response);
143
- }
144
- if ((clientResponse.body instanceof ReadableStream)) {
145
- return _ReadableStream.from(clientResponse.body).pipe(response);
146
- }
147
- let body = clientResponse.body;
148
- if (clientResponse.headers.contentType === 'application/json') {
149
- body += '';
150
- }
151
- return response.end(body);
152
- };
153
- })();
154
-
155
- // ---------------
156
- Observer.set(this, 'location', {});
157
- Observer.set(this, 'network', {});
158
- // ---------------
159
-
160
- // -------------
161
- // Initialize
162
- (async () => {
163
- await this.ready;
164
- if (this.cx.logger) {
165
- if (this.servers.size) {
166
- this.cx.logger.info(`> Server running! (${this.cx.app.title || ''})`);
167
- for (let [ proto, def ] of this.servers) {
168
- this.cx.logger.info(`> ${ proto.toUpperCase() } / ${ def.domains.concat('').join(`:${ def.port } / `)}`);
169
- }
170
- } else {
171
- this.cx.logger.info(`> Server not running! No port specified.`);
172
- }
173
- if (this.proxied.size) {
174
- this.cx.logger.info(`> Reverse proxy active.`);
175
- for (let [ id, def ] of this.proxied) {
176
- this.cx.logger.info(`> ${ id } >>> ${ def.port }`);
177
- }
178
- }
179
- this.cx.logger.info(``);
180
- }
181
- if (this.app && this.app.init) {
182
- const request = this.generateRequest('http://localhost/');
183
- const httpEvent = new HttpEvent(request, { srcType: 'initialization' }, (id = 'session', options = { duration: 60 * 60 * 24, activeDuration: 60 * 60 * 24 }, callback = null) => {
184
- return this.getSession(this.cx, httpEvent, id, options, callback);
185
- });
186
- await this.app.init(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
187
- }
188
- })();
189
- // ---------------
190
-
191
- // ---------------
192
- this.mockSessionStore = {};
193
- // ---------------
194
-
195
- }
196
-
197
- /**
198
- * Parse Nodejs's IncomingMessage to WHATWAG request params.
199
- *
200
- * @param String proto
201
- * @param Http.IncomingMessage request
202
- *
203
- * @return Array
204
- */
205
- async parseNodeRequest(proto, request) {
206
- // Detected when using manual proxy setting in a browser
207
- if (request.url.startsWith(`http://${ request.headers.host }`) || request.url.startsWith(`https://${ request.headers.host }`)) {
208
- request.url = request.url.split(request.headers.host)[1];
209
- }
210
- const fullUrl = proto + '://' + request.headers.host + request.url;
211
- const requestInit = { method: request.method, headers: request.headers };
212
- if (!['GET', 'HEAD'].includes(request.method)) {
213
- requestInit.body = request;
214
- }
215
- return [ fullUrl, requestInit ];
216
- }
217
-
218
- /**
219
- * Performs a request.
220
- *
221
- * @param object|string url
222
- * @param object|Request init
223
- * @param object detail
224
- *
225
- * @return Response
226
- */
227
- async go(url, init = {}, detail = {}) {
228
- await this.ready;
229
-
230
- // ------------
231
- url = typeof url === 'string' ? new URL(url) : url;
232
- if (!(init instanceof Request) && !init.referrer) {
233
- init = { referrer: this.location.href, ...init };
234
- }
235
- // ------------
236
- const hosts = [];
237
- this.servers.forEach(server => hosts.push(...server.domains));
238
- // ------------
239
- for (const [ /*id*/, vhost ] of this.proxied) {
240
- if (vhost.hostnames.includes(url.hostname) || (vhost.hostnames.includes('*') && !hosts.includes('*'))) {
241
- return this.proxyGo(vhost, url, init);
242
- }
243
- }
244
- // ------------
245
- let exit, exitMessage;
246
- if (!hosts.includes(url.hostname) && !hosts.includes('*')) {
247
- exit = { status: 500 }, exitMessage = 'Unrecognized host';
248
- } else if (url.protocol === 'http:' && this.cx.server.https.force) {
249
- exit = { status: 302, headers: { Location: ( url.protocol = 'https:', url.href ) } };
250
- } else if (url.hostname.startsWith('www.') && this.cx.server.force_www === 'remove') {
251
- exit = { status: 302, headers: { Location: ( url.hostname = url.hostname.substr(4), url.href ) } };
252
- } else if (!url.hostname.startsWith('www.') && this.cx.server.force_www === 'add') {
253
- exit = { status: 302, headers: { Location: ( url.hostname = `www.${ url.hostname }`, url.href ) } };
254
- } else if (this.cx.config.runtime.server.Redirects) {
255
- exit = ((await (new this.cx.config.runtime.server.Redirects(this.cx)).read()).entries || []).reduce((_rdr, entry) => {
256
- return _rdr || ((_rdr = pattern(entry.from, url.origin).exec(url.href)) && { status: entry.code || 302, headers: { Location: _rdr.render(entry.to) } });
257
- }, null);
258
- }
259
- if (exit) { return new xResponse(exitMessage, exit); }
260
- // ------------
261
-
262
- // ------------
263
- Observer.set(this.location, url, { detail: { init, ...detail, } });
264
- Observer.set(this.network, 'redirecting', null);
265
- // ------------
266
-
267
- // ------------
268
- // Automatically-added headers
269
- const autoHeaders = this.cx.config.runtime.server.Headers
270
- ? ((await (new this.cx.config.runtime.server.Headers(this.cx)).read()).entries || []).filter(entry => pattern(entry.url, url.origin).exec(url.href))
271
- : [];
272
- // The request object
273
- const request = this.generateRequest(url.href, init, autoHeaders.filter(header => header.type === 'request'));
274
- // The navigation event
275
- const httpEvent = new HttpEvent(request, detail, (id = 'session', options = { duration: 60 * 60 * 24, activeDuration: 60 * 60 * 24 }, callback = null) => {
276
- return this.getSession(this.cx, httpEvent, id, options, callback);
277
- });
278
- // Response
279
- let response, finalResponse;
280
- try {
281
- response = await this.app.handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
282
- finalResponse = await this.handleResponse(this.cx, httpEvent, response, autoHeaders.filter(header => header.type === 'response'));
283
- } catch(e) {
284
- finalResponse = new xResponse(e.message, { status: 500 });
285
- console.error(e);
286
- }
287
- // Logging
288
- if (this.cx.logger) {
289
- const log = this.generateLog(httpEvent.request, finalResponse);
290
- this.cx.logger.log(log);
291
- }
292
- // ------------
293
- // Return value
294
- return finalResponse;
295
- }
296
-
297
- // Fetch from proxied host
298
- async proxyGo(vhost, url, init) {
299
- // ---------
300
- const url2 = new URL(url);
301
- url2.port = vhost.port;
302
- if (vhost.proto) { url2.protocol = vhost.proto; }
303
- // ---------
304
- let init2;
305
- if (init instanceof Request) {
306
- init2 = init.clone();
307
- init.headers.set('Host', url2.host);
308
- } else {
309
- init2 = { ...init, decompress: false/* honoured in xfetch() */ };
310
- if (!init2.headers) init2.headers = {};
311
- init2.headers.host = url2.host;
312
- delete init2.headers.connection;
313
- }
314
- // ---------
315
- let response;
316
- try {
317
- response = await this.remoteFetch(url2, init2);
318
- } catch(e) {
319
- response = new xResponse(`Reverse Proxy Error: ${e.message}`, { status: 500 });
320
- console.error(e);
321
- }
322
- if (this.cx.logger) {
323
- const log = this.generateLog({ url: url2.href, ...init2 }, response, true);
324
- this.cx.logger.log(log);
325
- }
326
- return response;
327
-
328
- }
329
-
330
-
331
- // Generates request object
332
- generateRequest(href, init = {}, autoHeaders = []) {
333
- const request = new xRequest(href, init);
334
- this._autoHeaders(request.headers, autoHeaders);
335
- return request;
336
- }
337
-
338
- // Generates session object
339
- getSession(cx, e, id, options = {}, callback = null) {
340
- let baseObject;
341
- if (!(e.detail.request && e.detail.response)) {
342
- baseObject = this.mockSessionStore;
343
- let cookieAvailability = e.request.headers.cookies.get(id); // We just want to know availability... not validity, as this is understood to be for testing purposes only
344
- if (!(this.mockSessionStore[id] && cookieAvailability)) {
345
- let cookieObj = {};
346
- Object.defineProperty(this.mockSessionStore, id, {
347
- get: () => cookieObj,
348
- set: value => (cookieObj = value),
349
- });
350
- }
351
- } else {
352
- Sessions({
353
- duration: 0, // how long the session will stay valid in ms
354
- activeDuration: 0, // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds
355
- ...options,
356
- cookieName: id, // cookie name dictates the key name added to the request object
357
- secret: cx.env.SESSION_KEY, // should be a large unguessable string
358
- })(e.detail.request, e.detail.response, e => {
359
- if (e) {
360
- if (!callback) throw e;
361
- callback(e);
362
- }
363
- });
364
- baseObject = e.detail.request;
365
- }
366
- // Where theres no error, instance is available
367
- let instance = Object.getOwnPropertyDescriptor(baseObject, id);
368
- if (!callback) return instance;
369
- if (instance) callback(null, instance);
370
- }
371
-
372
- // Initiates remote fetch and sets the status
373
- remoteFetch(request, ...args) {
374
- let href = request;
375
- if (request instanceof Request) {
376
- href = request.url;
377
- } else if (request instanceof URL) {
378
- href = request.href;
379
- }
380
- Observer.set(this.network, 'remote', href);
381
- const _response = xfetch(request, ...args);
382
- // This catch() is NOT intended to handle failure of the fetch
383
- _response.catch(e => Observer.set(this.network, 'error', e.message));
384
- // Save a reference to this
385
- return _response.then(async response => {
386
- // Stop loading status
387
- Observer.set(this.network, 'remote', false);
388
- return xResponse.compat(response);
389
- });
390
- }
391
-
392
- // Handles response object
393
- async handleResponse(cx, e, response, autoHeaders = []) {
394
- if (!(response instanceof xResponse)) { response = xResponse.compat(response); }
395
- Observer.set(this.network, 'remote', false);
396
- Observer.set(this.network, 'error', null);
397
-
398
- // ----------------
399
- // Mock-Cookies?
400
- if (!(e.detail.request && e.detail.response)) {
401
- for (let cookieName of Object.getOwnPropertyNames(this.mockSessionStore)) {
402
- response.headers.append('Set-Cookie', `${cookieName}=1`); // We just want to know availability... not validity, as this is understood to be for testing purposes only
403
- }
404
- }
405
-
406
- // ----------------
407
- // Auto-Headers
408
- response.headers.set('Accept-Ranges', 'bytes');
409
- this._autoHeaders(response.headers, autoHeaders);
410
-
411
- // ----------------
412
- // Redirects
413
- if (response.headers.location) {
414
- const xRedirectPolicy = e.request.headers.get('X-Redirect-Policy');
415
- const xRedirectCode = e.request.headers.get('X-Redirect-Code') || 300;
416
- const destinationUrl = new URL(response.headers.location, e.url.origin);
417
- const isSameOriginRedirect = destinationUrl.origin === e.url.origin;
418
- let isSameSpaRedirect, sparootsFile = Path.join(cx.CWD, cx.layout.PUBLIC_DIR, 'sparoots.json');
419
- if (isSameOriginRedirect && xRedirectPolicy === 'manual-when-cross-spa' && Fs.existsSync(sparootsFile)) {
420
- // Longest-first sorting
421
- const sparoots = _arrFrom(JSON.parse(Fs.readFileSync(sparootsFile))).sort((a, b) => a.length > b.length ? -1 : 1);
422
- const matchRoot = path => sparoots.reduce((prev, root) => prev || (`${path}/`.startsWith(`${root}/`) && root), null);
423
- isSameSpaRedirect = matchRoot(destinationUrl.pathname) === matchRoot(e.url.pathname);
424
- }
425
- if (xRedirectPolicy === 'manual' || (!isSameOriginRedirect && xRedirectPolicy === 'manual-when-cross-origin') || (!isSameSpaRedirect && xRedirectPolicy === 'manual-when-cross-spa')) {
426
- response.headers.json({
427
- 'X-Redirect-Code': response.status,
428
- 'Access-Control-Allow-Origin': '*',
429
- 'Cache-Control': 'no-store',
430
- });
431
- response.attrs.status = xRedirectCode;
432
- }
433
- return response;
434
- }
435
-
436
- // ----------------
437
- // 404
438
- if (!response.meta.body && response.meta.body !== 0) {
439
- if (response.status === 200 || response.status === 0) {
440
- response = new xResponse(response.body, { status: 404, headers: response.headers });
441
- }
442
- return response;
443
- }
444
-
445
- // ----------------
446
- // Not acceptable
447
- if (e.request.headers.get('Accept') && !e.request.headers.accept.match(response.headers.contentType)) {
448
- response = new xResponse(response.body, { status: 406, headers: response.headers });
449
- return response;
450
- }
451
-
452
- // ----------------
453
- // Important no-caching
454
- // for non-"get" requests
455
- if (e.request.method !== 'GET' && !response.headers.get('Cache-Control')) {
456
- response.headers.set('Cache-Control', 'no-store');
457
- }
458
-
459
- // ----------------
460
- // Body
461
- let rangeRequest, body = response.body;
462
- if ((rangeRequest = e.request.headers.range) && !response.headers.get('Content-Range')
463
- && ((body instanceof ReadableStream) || (ArrayBuffer.isView(body) && (body = _ReadableStream.from(body))))) {
464
- // ...in partials
465
- const totalLength = response.headers.contentLength || 0;
466
- const ranges = await rangeRequest.reduce(async (_ranges, range) => {
467
- _ranges = await _ranges;
468
- if (range[0] < 0 || (totalLength && range[0] > totalLength)
469
- || (range[1] > -1 && (range[1] <= range[0] || (totalLength && range[1] >= totalLength)))) {
470
- // The range is beyond upper/lower limits
471
- _ranges.error = true;
472
- }
473
- if (!totalLength && range[0] === undefined) {
474
- // totalLength is unknown and we cant read the trailing size specified in range[1]
475
- _ranges.error = true;
476
- }
477
- if (_ranges.error) return _ranges;
478
- if (totalLength) { range.clamp(totalLength); }
479
- const partLength = range[1] - range[0] + 1;
480
- _ranges.parts.push({
481
- body: body.pipe(_streamSlice(range[0], range[1] + 1)),
482
- range: range = `bytes ${range[0]}-${range[1]}/${totalLength || '*'}`,
483
- length: partLength,
484
- });
485
- _ranges.totalLength += partLength;
486
- return _ranges;
487
- }, { parts: [], totalLength: 0 });
488
- if (ranges.error) {
489
- response.attrs.status = 416;
490
- response.headers.json({
491
- 'Content-Range': `bytes */${totalLength || '*'}`,
492
- 'Content-Length': 0,
493
- });
494
- } else {
495
- // TODO: of ranges.parts is more than one, return multipart/byteranges
496
- response = new xResponse(ranges.parts[0].body, {
497
- status: 206,
498
- statusText: response.statusText,
499
- headers: response.headers,
500
- });
501
- response.headers.json({
502
- 'Content-Range': ranges.parts[0].range,
503
- 'Content-Length': ranges.totalLength,
504
- });
505
- }
506
- }
507
-
508
- return response;
509
- }
510
-
511
- // Generates log
512
- generateLog(request, response, isproxy = false) {
513
- let log = [];
514
- // ---------------
515
- const style = this.cx.logger.style || { keyword: str => str, comment: str => str, url: str => str, val: str => str, err: str => str, };
516
- const errorCode = [ 404, 500 ].includes(response.status) ? response.status : 0;
517
- const xRedirectCode = response.headers.get('X-Redirect-Code');
518
- const redirectCode = xRedirectCode || ((response.status + '').startsWith('3') ? response.status : 0);
519
- const statusCode = xRedirectCode || response.status;
520
- // ---------------
521
- log.push(`[${style.comment((new Date).toUTCString())}]`);
522
- log.push(style.keyword(request.method));
523
- if (isproxy) log.push(style.keyword('>>'));
524
- log.push(style.url(request.url));
525
- if (response.attrs.hint) log.push(`(${style.comment(response.attrs.hint)})`);
526
- const contentInfo = [response.headers.contentType, response.headers.contentLength].filter(x => x);
527
- if (contentInfo.length) log.push(`(${style.comment(contentInfo.join('; '))})`);
528
- if (response.headers.get('Content-Encoding')) log.push(`(${style.comment(response.headers.get('Content-Encoding'))})`);
529
- if (errorCode) log.push(style.err(`${errorCode} ${response.statusText}`));
530
- else log.push(style.val(`${statusCode} ${response.statusText}`));
531
- if (redirectCode) log.push(`- ${style.url(response.headers.location)}`);
532
-
533
- return log.join(' ');
534
- }
535
-
536
- // Applies auto headers
537
- _autoHeaders(headers, autoHeaders) {
538
- autoHeaders.forEach(header => {
539
- var headerName = header.name.toLowerCase(),
540
- headerValue = header.value,
541
- isAppend = headerName.startsWith('+') ? (headerName = headerName.substr(1), true) : false,
542
- isPrepend = headerName.endsWith('+') ? (headerName = headerName.substr(0, headerName.length - 1), true) : false;
543
- if (isAppend || isPrepend) {
544
- headerValue = [ headers.get(headerName) || '' , headerValue].filter(v => v);
545
- headerValue = isPrepend ? headerValue.reverse().join(',') : headerValue.join(',');
546
- }
547
- headers.set(headerName, headerValue);
548
- });
549
- }
550
-
551
- }
552
-
553
- const _streamRead = stream => new Promise(res => {
554
- let data = '';
555
- stream.on('data', chunk => data += chunk);
556
- stream.on('end', () => res(data));
557
- });
@@ -1,24 +0,0 @@
1
-
2
- /**
3
- * @imports
4
- */
5
- import { jsonfyFormData } from './util-http.js';
6
-
7
- /**
8
- * The _Headers Mixin
9
- */
10
- export default class xFormData extends FormData {
11
-
12
- async json(data = {}) {
13
- const result = await jsonfyFormData(this, ...arguments);
14
- return result[0];
15
- }
16
-
17
- static compat(formData) {
18
- if (formData instanceof this) return formData;
19
- if (formData instanceof FormData) {
20
- return Object.setPrototypeOf(formData, new this);
21
- }
22
- }
23
-
24
- }