@webqit/webflo 1.0.14 → 1.0.16

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/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "vanila-javascript"
13
13
  ],
14
14
  "homepage": "https://webqit.io/tooling/webflo",
15
- "version": "1.0.14",
15
+ "version": "1.0.16",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
@@ -10,7 +10,6 @@ export class HttpEvent {
10
10
  #parentEvent;
11
11
  #init;
12
12
  #url;
13
- #requestCloneCallback;
14
13
 
15
14
  constructor(parentEvent, init = {}) {
16
15
  this.#parentEvent = parentEvent;
@@ -32,56 +31,48 @@ export class HttpEvent {
32
31
 
33
32
  get client() { return this.#init.client; }
34
33
 
34
+ #requestCloneCallback;
35
35
  set onRequestClone(callback) {
36
36
  this.#requestCloneCallback = callback;
37
37
  }
38
38
 
39
+ #responseHandler;
40
+ set onRespondWith(callback) {
41
+ this.#responseHandler = callback;
42
+ }
43
+
44
+ get onRespondWith() {
45
+ return this.#responseHandler;
46
+ }
47
+
39
48
  clone() {
40
49
  const request = this.#requestCloneCallback?.() || this.request;
41
50
  const init = { ...this.#init, request };
42
- const instance = this.constructor.create(init);
51
+ const instance = this.constructor.create(this.#parentEvent, init);
43
52
  instance.#requestCloneCallback = this.#requestCloneCallback;
44
53
  return instance;
45
54
  }
46
55
 
56
+ waitUntil(promise) {
57
+ if (this.#parentEvent) {
58
+ this.#parentEvent.waitUntil(promise);
59
+ }
60
+ }
61
+
47
62
  #response = null;
48
63
  get response() { return this.#response; }
49
64
 
50
- #responseOrigin = null;
51
-
52
- async #respondWith(response) {
53
- /*
54
- if (this.#response) {
55
- throw new Error(`Event has already been responded to! (${this.#responseOrigin})`);
56
- }
57
- */
65
+ async respondWith(response) {
58
66
  this.#response = response;
59
- /*
60
- if (!this.#responseOrigin) {
61
- const stack = new Error().stack;
62
- const stackLines = stack.split('\n');
63
- this.#responseOrigin = stackLines[3].trim();
64
- }
65
- */
66
- if (this.#parentEvent instanceof HttpEvent) {
67
- /*
68
- // Set responseOrigin first to prevent parent from repeating the work
69
- this.#parentEvent.#responseOrigin = this.#responseOrigin;
70
- */
71
- // Ensure the respondWith() method is how we propagate response
67
+ if (this.#responseHandler) {
68
+ await this.#responseHandler(this.#response);
69
+ } else if (this.#parentEvent) {
72
70
  await this.#parentEvent.respondWith(this.#response);
73
- } else {
74
- // The callback passed at root
75
- await this.#parentEvent?.(response);
76
71
  }
77
72
  }
78
73
 
79
- async respondWith(response) {
80
- await this.#respondWith(response);
81
- }
82
-
83
74
  async defer() {
84
- await this.#respondWith(new Response(null, { status: 202/*Accepted*/ }));
75
+ await this.respondWith(new Response(null, { status: 202/*Accepted*/ }));
85
76
  }
86
77
 
87
78
  deferred() {
@@ -89,7 +80,7 @@ export class HttpEvent {
89
80
  }
90
81
 
91
82
  async redirect(url, status = 302) {
92
- await this.#respondWith(new Response(null, { status, headers: {
83
+ await this.respondWith(new Response(null, { status, headers: {
93
84
  Location: url
94
85
  } }));
95
86
  }
@@ -111,7 +102,7 @@ export class HttpEvent {
111
102
 
112
103
  async with(url, init = {}) {
113
104
  if (!this.request) {
114
- return new HttpEvent(this, { ...this.#init, url });
105
+ return new HttpEvent(this, { ...this.#init, url });
115
106
  }
116
107
  let request, _;
117
108
  if (url instanceof Request) {
@@ -122,6 +113,6 @@ export class HttpEvent {
122
113
  init = await Request.copy(this.request, init);
123
114
  request = new Request(url, { ...init, referrer: this.request.url });
124
115
  }
125
- return new HttpEvent(this, { ...this.#init, request });
116
+ return new HttpEvent(this, { ...this.#init, request });
126
117
  }
127
118
  }
@@ -63,6 +63,8 @@ export class WebfloRouter {
63
63
  nextTick.destination = newDestination.split('?').shift().split('/').map(a => a.trim()).filter(a => a);
64
64
  nextTick.trail = _args[1].startsWith('/') ? [] : thisTick.trail.reduce((_commonRoot, _seg, i) => _commonRoot.length === i && _seg === nextTick.destination[i] ? _commonRoot.concat(_seg) : _commonRoot, []);
65
65
  nextTick.trailOnFile = thisTick.trailOnFile.slice(0, nextTick.trail.length);
66
+ } else {
67
+ nextTick.event = thisTick.event.clone();
66
68
  }
67
69
  return next(nextTick);
68
70
  };
@@ -71,7 +73,21 @@ export class WebfloRouter {
71
73
  _next.pathname = nextPathname.join('/');
72
74
  _next.stepname = nextPathname[0];
73
75
  // -------------
74
- return await handler.call(thisContext, thisTick.event, thisTick.arg, _next/*next*/, remoteFetch);
76
+ return new Promise(async (res) => {
77
+ thisTick.event.onRespondWith = (response) => {
78
+ thisTick.event.onRespondWith = null;
79
+ res(response);
80
+ };
81
+ const $returnValue = handler.call(thisContext, thisTick.event, thisTick.arg, _next/*next*/, remoteFetch);
82
+ thisTick.event.waitUntil($returnValue);
83
+ const returnValue = await $returnValue;
84
+ if (thisTick.event.onRespondWith) {
85
+ thisTick.event.onRespondWith = null;
86
+ res(returnValue);
87
+ } else if (typeof returnValue !== 'undefined') {
88
+ await thisTick.event.respondWith(returnValue);
89
+ }
90
+ });
75
91
  }
76
92
  // Handler not found but exports found
77
93
  return next(thisTick);
@@ -313,83 +313,100 @@ export class WebfloClient extends WebfloRuntime {
313
313
  if (typeof scope.url === 'string') {
314
314
  scope.url = new URL(scope.url, self.location.origin);
315
315
  }
316
- // Ping any existing background process
317
- this.#backgroundMessaging.postMessage('navigation');
318
- // Process request...
319
- scope.response = await new Promise(async (resolveResponse) => {
320
- scope.handleRespondWith = async (response) => {
321
- if (scope.finalResponseSeen) {
316
+ // ---------------
317
+ // Event lifecycle
318
+ scope.eventLifecyclePromises = new Set;
319
+ scope.eventLifecycleHooks = {
320
+ waitUntil: (promise) => {
321
+ promise = Promise.resolve(promise);
322
+ scope.eventLifecyclePromises.add(promise);
323
+ scope.eventLifecyclePromises.dirty = true;
324
+ promise.then(() => scope.eventLifecyclePromises.delete(promise));
325
+ },
326
+ respondWith: async (response) => {
327
+ if (scope.eventLifecyclePromises.dirty && !scope.eventLifecyclePromises.size) {
322
328
  throw new Error('Final response already sent');
323
329
  }
324
- if (scope.initialResponseSeen) {
325
- return await this.execPush(scope.clientMessaging, response);
326
- }
327
- response = await this.normalizeResponse(scope.httpEvent, response, true);
328
- resolveResponse(response);
329
- };
330
- // Create and route request
331
- scope.request = this.createRequest(scope.url, scope.init);
332
- scope.cookies = this.constructor.CookieStorage.create(scope.request);
333
- scope.session = this.constructor.SessionStorage.create(scope.request);
334
- const messageChannel = new MessageChannel;
335
- this.backgroundMessaging.add(new MessagingOverChannel(null, messageChannel.port1));
336
- scope.clientMessaging = new ClientMessaging(this, messageChannel.port2);
337
- scope.user = this.constructor.HttpUser.create(
338
- scope.request,
339
- scope.session,
340
- scope.clientMessaging
341
- );
342
- scope.httpEvent = this.constructor.HttpEvent.create(scope.handleRespondWith, {
343
- request: scope.request,
344
- detail: scope.detail,
345
- cookies: scope.cookies,
346
- session: scope.session,
347
- user: scope.user,
348
- client: scope.clientMessaging
349
- });
350
- scope.httpEvent.onRequestClone = () => this.createRequest(scope.url, scope.init);
351
- // Ste pre-request states
330
+ return await this.execPush(scope.clientMessaging, response);
331
+ },
332
+ };
333
+ // Create and route request
334
+ scope.request = this.createRequest(scope.url, scope.init);
335
+ scope.cookies = this.constructor.CookieStorage.create(scope.request);
336
+ scope.session = this.constructor.SessionStorage.create(scope.request);
337
+ const messageChannel = new MessageChannel;
338
+ this.backgroundMessaging.add(new MessagingOverChannel(null, messageChannel.port1));
339
+ scope.clientMessaging = new ClientMessaging(this, messageChannel.port2);
340
+ scope.user = this.constructor.HttpUser.create(
341
+ scope.request,
342
+ scope.session,
343
+ scope.clientMessaging
344
+ );
345
+ scope.httpEvent = this.constructor.HttpEvent.create(scope.eventLifecycleHooks, {
346
+ request: scope.request,
347
+ detail: scope.detail,
348
+ cookies: scope.cookies,
349
+ session: scope.session,
350
+ user: scope.user,
351
+ client: scope.clientMessaging
352
+ });
353
+ scope.httpEvent.onRequestClone = () => this.createRequest(scope.url, scope.init);
354
+ // Ste pre-request states
355
+ Observer.set(this.navigator, {
356
+ requesting: new Url/*NOT URL*/(scope.url),
357
+ origins: scope.detail.navigationOrigins || [],
358
+ method: scope.request.method,
359
+ error: null
360
+ });
361
+ scope.resetStates = () => {
352
362
  Observer.set(this.navigator, {
353
- requesting: new Url/*NOT URL*/(scope.url),
354
- origins: scope.detail.navigationOrigins || [],
355
- method: scope.request.method,
356
- error: null
363
+ requesting: null,
364
+ remotely: false,
365
+ origins: [],
366
+ method: null
357
367
  });
358
- scope.resetStates = () => {
359
- Observer.set(this.navigator, {
360
- requesting: null,
361
- remotely: false,
362
- origins: [],
363
- method: null
364
- });
365
- };
366
- scope.context = {};
367
- if (window.webqit?.oohtml?.configs) {
368
- const { BINDINGS_API: { api: bindingsConfig } = {}, } = window.webqit.oohtml.configs;
369
- scope.context = this.host[bindingsConfig.bindings].data || {};
368
+ };
369
+ scope.context = {};
370
+ if (window.webqit?.oohtml?.configs) {
371
+ const { BINDINGS_API: { api: bindingsConfig } = {}, } = window.webqit.oohtml.configs;
372
+ scope.context = this.host[bindingsConfig.bindings].data || {};
373
+ }
374
+ if (scope.request.method === 'GET') {
375
+ // Ping any existing background process
376
+ this.#backgroundMessaging.postMessage('navigation');
377
+ }
378
+ // Dispatch for response
379
+ scope.response = await this.dispatch(scope.httpEvent, scope.context, async (event) => {
380
+ // Was this nexted()? Tell the next layer we're in JSON mode by default
381
+ if (event !== scope.httpEvent && !event.request.headers.has('Accept')) {
382
+ event.request.headers.set('Accept', 'application/json');
370
383
  }
371
- // Dispatch for response
372
- scope.$response = await this.dispatch(scope.httpEvent, scope.context, async (event) => {
373
- // Was this nexted()? Tell the next layer we're in JSON mode by default
374
- if (event !== scope.httpEvent && !event.request.headers.has('Accept')) {
375
- event.request.headers.set('Accept', 'application/json');
376
- }
377
- return await this.remoteFetch(event.request);
378
- });
379
- // Final reponse!!!
380
- scope.finalResponseSeen = true;
381
- if (scope.initialResponseSeen) {
382
- // Send via background port
383
- if (typeof scope.$response !== 'undefined') {
384
- await this.execPush(scope.clientMessaging, scope.$response);
385
- }
386
- return;
384
+ return await this.remoteFetch(event.request);
385
+ });
386
+ // ---------------
387
+ // Response processing
388
+ scope.hasBackgroundActivity = scope.eventLifecyclePromises.size || (scope.redirectMessage && !(scope.response instanceof Response && scope.response.headers.get('Location')));
389
+ scope.response = await this.normalizeResponse(scope.httpEvent, scope.response);
390
+ if (scope.response.headers.get('Location')) {
391
+ if (scope.redirectMessage) {
392
+ scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
387
393
  }
388
- // Send normally
389
- scope.$response = await this.normalizeResponse(scope.httpEvent, scope.$response);
390
- resolveResponse(scope.$response);
394
+ } else {
395
+ if (scope.redirectMessage) {
396
+ scope.eventLifecycleHooks.respondWith(scope.redirectMessage);
397
+ }
398
+ }
399
+ Promise.all([...scope.eventLifecyclePromises]).then(() => {
400
+ if (scope.clientMessaging.isMessaging()) {
401
+ scope.clientMessaging.on('connected', () => {
402
+ setTimeout(() => {
403
+ scope.clientMessaging.close();
404
+ }, 100);
405
+ });
406
+ } else scope.clientMessaging.close();
391
407
  });
392
- scope.initialResponseSeen = true;
408
+ // ---------------
409
+ // Decode response
393
410
  scope.finalUrl = scope.response.url || scope.request.url;
394
411
  if (scope.response.redirected || scope.detail.navigationType === 'rdr' || scope.detail.isHoisted) {
395
412
  const stateData = { ...(this.currentEntry()?.getState() || {}), redirected: true, };
@@ -131,84 +131,81 @@ export class WebfloWorker extends WebfloRuntime {
131
131
  if (typeof scope.url === 'string') {
132
132
  scope.url = new URL(scope.url, self.location.origin);
133
133
  }
134
- scope.response = await new Promise(async (resolveResponse) => {
135
- scope.handleRespondWith = async (response) => {
136
- if (scope.finalResponseSeen) {
134
+ // ---------------
135
+ // Event lifecycle
136
+ scope.eventLifecyclePromises = new Set;
137
+ scope.eventLifecycleHooks = {
138
+ waitUntil: (promise) => {
139
+ promise = Promise.resolve(promise);
140
+ scope.eventLifecyclePromises.add(promise);
141
+ scope.eventLifecyclePromises.dirty = true;
142
+ promise.then(() => scope.eventLifecyclePromises.delete(promise));
143
+ },
144
+ respondWith: async (response) => {
145
+ if (scope.eventLifecyclePromises.dirty && !scope.eventLifecyclePromises.size) {
137
146
  throw new Error('Final response already sent');
138
147
  }
139
- if (scope.initialResponseSeen) {
140
- return await this.execPush(scope.clientMessaging, response);
141
- }
142
- resolveResponse(response);
143
- };
144
- // Create and route request
145
- scope.request = this.createRequest(scope.url, scope.init);
146
- scope.cookies = this.constructor.CookieStorage.create(scope.request);
147
- scope.session = this.constructor.SessionStorage.create(scope.request);
148
- const portID = crypto.randomUUID();
149
- scope.clientMessaging = new ClientMessaging(this, portID, { isPrimary: true });
150
- scope.user = this.constructor.HttpUser.create(
151
- scope.request,
152
- scope.session,
153
- scope.clientMessaging
154
- );
155
- scope.httpEvent = this.constructor.HttpEvent.create(scope.handleRespondWith, {
156
- request: scope.request,
157
- detail: scope.detail,
158
- cookies: scope.cookies,
159
- session: scope.session,
160
- user: scope.user,
161
- client: scope.clientMessaging
162
- });
163
- // Restore session before dispatching
164
- if (scope.request.method === 'GET'
165
- && (scope.redirectMessageID = scope.httpEvent.url.query['redirect-message'])
166
- && (scope.redirectMessage = scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
167
- scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
148
+ return await this.execPush(scope.clientMessaging, response);
149
+ },
150
+ };
151
+ // Create and route request
152
+ scope.request = this.createRequest(scope.url, scope.init);
153
+ scope.cookies = this.constructor.CookieStorage.create(scope.request);
154
+ scope.session = this.constructor.SessionStorage.create(scope.request);
155
+ const portID = crypto.randomUUID();
156
+ scope.clientMessaging = new ClientMessaging(this, portID, { isPrimary: true });
157
+ scope.user = this.constructor.HttpUser.create(
158
+ scope.request,
159
+ scope.session,
160
+ scope.clientMessaging
161
+ );
162
+ scope.httpEvent = this.constructor.HttpEvent.create(scope.eventLifecycleHooks, {
163
+ request: scope.request,
164
+ detail: scope.detail,
165
+ cookies: scope.cookies,
166
+ session: scope.session,
167
+ user: scope.user,
168
+ client: scope.clientMessaging
169
+ });
170
+ // Restore session before dispatching
171
+ if (scope.request.method === 'GET'
172
+ && (scope.redirectMessageID = scope.httpEvent.url.query['redirect-message'])
173
+ && (scope.redirectMessage = scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
174
+ scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
175
+ }
176
+ // Dispatch for response
177
+ scope.response = await this.dispatch(scope.httpEvent, {}, async (event) => {
178
+ // Was this nexted()? Tell the next layer we're in JSON mode by default
179
+ if (event !== scope.httpEvent && !event.request.headers.has('Accept')) {
180
+ event.request.headers.set('Accept', 'application/json');
168
181
  }
169
- // Dispatch for response
170
- scope.$response = await this.dispatch(scope.httpEvent, {}, async (event) => {
171
- // Was this nexted()? Tell the next layer we're in JSON mode by default
172
- if (event !== scope.httpEvent && !event.request.headers.has('Accept')) {
173
- event.request.headers.set('Accept', 'application/json');
174
- }
175
- return await this.remoteFetch(event.request);
176
- });
177
- // Final reponse!!!
178
- scope.finalResponseSeen = true;
179
- if (scope.initialResponseSeen) {
180
- // Send via background port
181
- if (typeof scope.$response !== 'undefined') {
182
- await this.execPush(scope.clientMessaging, scope.$response);
183
- }
184
- scope.clientMessaging.close();
185
- return;
186
- }
187
- // Send normally
188
- resolveResponse(scope.$response);
182
+ return await this.remoteFetch(event.request);
189
183
  });
190
- scope.initialResponseSeen = true;
191
- scope.hasBackgroundActivity = !scope.finalResponseSeen || (scope.redirectMessage && !(scope.response instanceof Response && scope.response.headers.get('Location')));
184
+ // ---------------
185
+ // Response processing
186
+ scope.hasBackgroundActivity = scope.eventLifecyclePromises.size || (scope.redirectMessage && !(scope.response instanceof Response && scope.response.headers.get('Location')));
192
187
  scope.response = await this.normalizeResponse(scope.httpEvent, scope.response, scope.hasBackgroundActivity);
193
188
  if (scope.hasBackgroundActivity) {
194
189
  scope.response.headers.set('X-Background-Messaging', `ch:${scope.clientMessaging.port.name}`);
195
190
  }
196
- if (scope.response instanceof Response && scope.response.headers.get('Location')) {
191
+ if (scope.response.headers.get('Location')) {
197
192
  if (scope.redirectMessage) {
198
193
  scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
199
194
  }
200
195
  } else {
201
196
  if (scope.redirectMessage) {
202
- setTimeout(() => {
203
- this.execPush(scope.clientMessaging, scope.redirectMessage);
204
- if (scope.finalResponseSeen) {
205
- scope.clientMessaging.close();
206
- }
207
- }, 500);
208
- } else if (scope.finalResponseSeen) {
209
- scope.clientMessaging.close();
210
- }
197
+ scope.eventLifecycleHooks.respondWith(scope.redirectMessage);
198
+ }
211
199
  }
200
+ Promise.all([...scope.eventLifecyclePromises]).then(() => {
201
+ if (scope.clientMessaging.isMessaging()) {
202
+ scope.clientMessaging.on('connected', () => {
203
+ setTimeout(() => {
204
+ scope.clientMessaging.close();
205
+ }, 100);
206
+ });
207
+ } else scope.clientMessaging.close();
208
+ });
212
209
  return scope.response;
213
210
  }
214
211
 
@@ -477,98 +477,93 @@ export class WebfloServer extends WebfloRuntime {
477
477
  if (typeof scope.url === 'string') {
478
478
  scope.url = new URL(scope.url, 'http://localhost');
479
479
  }
480
- scope.response = await new Promise(async (resolveResponse) => {
481
- scope.handleRespondWith = async (response) => {
482
- if (scope.finalResponseSeen) {
480
+ // ---------------
481
+ // Event lifecycle
482
+ scope.eventLifecyclePromises = new Set;
483
+ scope.eventLifecycleHooks = {
484
+ waitUntil: (promise) => {
485
+ promise = Promise.resolve(promise);
486
+ scope.eventLifecyclePromises.add(promise);
487
+ scope.eventLifecyclePromises.dirty = true;
488
+ promise.then(() => scope.eventLifecyclePromises.delete(promise));
489
+ },
490
+ respondWith: async (response, isRedirectMessage = false) => {
491
+ if (!isRedirectMessage && scope.eventLifecyclePromises.dirty && !scope.eventLifecyclePromises.size) {
483
492
  throw new Error('Final response already sent');
484
493
  }
485
- if (scope.initialResponseSeen) {
486
- return await this.execPush(scope.clientMessaging, response);
487
- }
488
- resolveResponse(response);
489
- };
490
- // ---------------
491
- // Request processing
492
- scope.autoHeaders = this.#cx.config.runtime.server.Headers
493
- ? ((await (new this.#cx.config.runtime.server.Headers(this.#cx)).read()).entries || []).filter(entry => pattern(entry.url, url.origin).exec(url.href))
494
- : [];
495
- scope.request = this.createRequest(scope.url.href, scope.init, scope.autoHeaders.filter((header) => header.type === 'request'));
496
- scope.cookies = this.constructor.CookieStorage.create(scope.request);
497
- scope.session = this.constructor.SessionStorage.create(scope.request, { secret: this.#cx.env.entries.SESSION_KEY });
498
- const sessionID = scope.session.sessionID;
499
- if (!this.#globalMessagingRegistry.has(sessionID)) {
500
- this.#globalMessagingRegistry.set(sessionID, new ClientMessagingRegistry(this, sessionID));
501
- }
502
- scope.clientMessagingRegistry = this.#globalMessagingRegistry.get(sessionID);
503
- scope.clientMessaging = scope.clientMessagingRegistry.createPort();
504
- scope.user = this.constructor.HttpUser.create(
505
- scope.request,
506
- scope.session,
507
- scope.clientMessaging
508
- );
509
- scope.httpEvent = this.constructor.HttpEvent.create(scope.handleRespondWith, {
510
- request: scope.request,
511
- detail: scope.detail,
512
- cookies: scope.cookies,
513
- session: scope.session,
514
- user: scope.user,
515
- client: scope.clientMessaging
516
- });
517
- // Restore session before dispatching
518
- if (scope.request.method === 'GET'
519
- && (scope.redirectMessageID = scope.httpEvent.url.query['redirect-message'])
520
- && (scope.redirectMessage = scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
521
- scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
522
- }
523
- // Dispatch for response
524
- scope.$response = await this.dispatch(scope.httpEvent, {}, async (event) => {
525
- return await this.localFetch(event);
526
- });
527
- // Final reponse!!!
528
- scope.finalResponseSeen = true;
529
- if (scope.initialResponseSeen) {
530
- // Send via background port
531
- if (typeof scope.$response !== 'undefined') {
532
- await this.execPush(scope.clientMessaging, scope.$response);
533
- }
534
- scope.clientMessaging.close();
535
- return;
536
- }
537
- // Send normally
538
- resolveResponse(scope.$response);
494
+ return await this.execPush(scope.clientMessaging, response);
495
+ },
496
+ };
497
+ // ---------------
498
+ // Request processing
499
+ scope.autoHeaders = this.#cx.config.runtime.server.Headers
500
+ ? ((await (new this.#cx.config.runtime.server.Headers(this.#cx)).read()).entries || []).filter(entry => pattern(entry.url, url.origin).exec(url.href))
501
+ : [];
502
+ scope.request = this.createRequest(scope.url.href, scope.init, scope.autoHeaders.filter((header) => header.type === 'request'));
503
+ scope.cookies = this.constructor.CookieStorage.create(scope.request);
504
+ scope.session = this.constructor.SessionStorage.create(scope.request, { secret: this.#cx.env.entries.SESSION_KEY });
505
+ const sessionID = scope.session.sessionID;
506
+ if (!this.#globalMessagingRegistry.has(sessionID)) {
507
+ this.#globalMessagingRegistry.set(sessionID, new ClientMessagingRegistry(this, sessionID));
508
+ }
509
+ scope.clientMessagingRegistry = this.#globalMessagingRegistry.get(sessionID);
510
+ scope.clientMessaging = scope.clientMessagingRegistry.createPort();
511
+ scope.user = this.constructor.HttpUser.create(
512
+ scope.request,
513
+ scope.session,
514
+ scope.clientMessaging
515
+ );
516
+ scope.httpEvent = this.constructor.HttpEvent.create(scope.eventLifecycleHooks, {
517
+ request: scope.request,
518
+ detail: scope.detail,
519
+ cookies: scope.cookies,
520
+ session: scope.session,
521
+ user: scope.user,
522
+ client: scope.clientMessaging
539
523
  });
540
- scope.initialResponseSeen = true;
541
- scope.hasBackgroundActivity = !scope.finalResponseSeen || (scope.redirectMessage && !(scope.response instanceof Response && scope.response.headers.get('Location')));
524
+ // Restore session before dispatching
525
+ if (scope.request.method === 'GET'
526
+ && (scope.redirectMessageID = scope.httpEvent.url.query['redirect-message'])
527
+ && (scope.redirectMessage = scope.session.get(`redirect-message:${scope.redirectMessageID}`))) {
528
+ scope.session.delete(`redirect-message:${scope.redirectMessageID}`);
529
+ }
530
+ // Dispatch for response
531
+ scope.response = await this.dispatch(scope.httpEvent, {}, async (event) => {
532
+ return await this.localFetch(event);
533
+ });
534
+ // ---------------
535
+ // Response processing
536
+ scope.hasBackgroundActivity = scope.eventLifecyclePromises.size || (scope.redirectMessage && !(scope.response instanceof Response && scope.response.headers.get('Location')));
542
537
  scope.response = await this.normalizeResponse(scope.httpEvent, scope.response, scope.hasBackgroundActivity);
543
538
  if (scope.hasBackgroundActivity) {
544
539
  scope.response.headers.set('X-Background-Messaging', `ws:${scope.clientMessaging.portID}`);
545
540
  }
546
541
  // Reponse handlers
547
- if (scope.response instanceof Response && scope.response.headers.get('Location')) {
548
- this.writeRedirectHeaders(scope.httpEvent, scope.response);
542
+ if (scope.response.headers.get('Location')) {
549
543
  if (scope.redirectMessage) {
550
544
  scope.session.set(`redirect-message:${scope.redirectMessageID}`, scope.redirectMessage);
551
545
  }
546
+ this.writeRedirectHeaders(scope.httpEvent, scope.response);
552
547
  } else {
548
+ if (scope.redirectMessage) {
549
+ scope.eventLifecycleHooks.respondWith(scope.redirectMessage, true);
550
+ }
553
551
  this.writeAutoHeaders(scope.response.headers, scope.autoHeaders.filter((header) => header.type === 'response'));
554
552
  if (scope.httpEvent.request.method !== 'GET' && !scope.response.headers.get('Cache-Control')) {
555
553
  scope.response.headers.set('Cache-Control', 'no-store');
556
554
  }
557
555
  scope.response.headers.set('Accept-Ranges', 'bytes');
558
556
  scope.response = await this.satisfyRequestFormat(scope.httpEvent, scope.response);
559
- if (scope.redirectMessage) {
560
- this.execPush(scope.clientMessaging, scope.redirectMessage);
561
- if (scope.finalResponseSeen) {
562
- scope.clientMessaging.on('connected', () => {
563
- setTimeout(() => {
564
- scope.clientMessaging.close();
565
- }, 100);
566
- });
567
- }
568
- } else if (scope.finalResponseSeen) {
569
- scope.clientMessaging.close();
570
- }
571
557
  }
558
+ Promise.all([...scope.eventLifecyclePromises]).then(() => {
559
+ if (scope.clientMessaging.isMessaging()) {
560
+ scope.clientMessaging.on('connected', () => {
561
+ setTimeout(() => {
562
+ scope.clientMessaging.close();
563
+ }, 100);
564
+ });
565
+ } else scope.clientMessaging.close();
566
+ });
572
567
  return scope.response;
573
568
  }
574
569