@webqit/webflo 0.20.15 → 0.20.17

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": "0.20.15",
15
+ "version": "0.20.17",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
@@ -15,13 +15,13 @@ export class WebfloRuntime {
15
15
 
16
16
  static get Router() { return WebfloRouter; }
17
17
 
18
- static get HttpEvent() { return HttpEvent; }
18
+ static get HttpEvent() { return HttpEvent; }
19
19
 
20
- static get HttpThread() { return HttpThread; }
20
+ static get HttpThread() { return HttpThread; }
21
21
 
22
- static get HttpSession() { return HttpSession; }
22
+ static get HttpSession() { return HttpSession; }
23
23
 
24
- static get HttpUser() { return HttpUser; }
24
+ static get HttpUser() { return HttpUser; }
25
25
 
26
26
  static create(bootstrap) { return new this(bootstrap); }
27
27
 
@@ -109,7 +109,7 @@ export class WebfloRuntime {
109
109
 
110
110
  // Dispatch event
111
111
  const router = new this.constructor.Router(this, httpEvent.url.pathname);
112
- await router.route(['SETUP'], httpEvent.extend());
112
+ await router.route(['SETUP'], httpEvent.spawn());
113
113
 
114
114
  // Do proper routing for respone
115
115
  const response = await new Promise(async (resolve) => {
@@ -141,8 +141,8 @@ export class WebfloRuntime {
141
141
  : responseShim.from.value(response);
142
142
  }
143
143
 
144
- // Any "carry" data?
145
- await this.handleCarries(httpEvent, response);
144
+ // Any "status" in thread?
145
+ await this.handleThreadStatus(httpEvent, response);
146
146
 
147
147
  // Resolve now...
148
148
  if (autoLiveResponse) {
@@ -192,8 +192,11 @@ export class WebfloRuntime {
192
192
 
193
193
  // On ROOT event complete:
194
194
  // Close httpEvent.client
195
- httpEvent.lifeCycleComplete(true).then(() => {
195
+ httpEvent.lifeCycleComplete(true).then(async () => {
196
196
  httpEvent.client.close();
197
+ if (!httpEvent.thread.extended) {
198
+ await httpEvent.thread.clear();
199
+ }
197
200
  });
198
201
  }
199
202
 
@@ -205,16 +208,17 @@ export class WebfloRuntime {
205
208
  return response;
206
209
  }
207
210
 
208
- async handleCarries(httpEvent, response) {
211
+ async handleThreadStatus(httpEvent, response) {
209
212
  if (!response.headers.get('Location')) {
210
- const status = await httpEvent.thread.consume('status');
211
- await httpEvent.thread.clear();
212
- if (!status) return;
213
+ const status = await httpEvent.thread.consume('status', true);
214
+ if (!status.length) return;
213
215
  // Fire redirect message?
214
216
  httpEvent.waitUntil(new Promise((resolve) => {
215
217
  httpEvent.client.wqLifecycle.open.then(async () => {
216
- httpEvent.client.postMessage(status, { wqEventOptions: { type: 'alert' } });
217
- resolve();
218
+ setTimeout(() => {
219
+ httpEvent.client.postMessage(status, { wqEventOptions: { type: 'alert' } });
220
+ resolve();
221
+ }, 500); // half a sec
218
222
  }, { once: true });
219
223
  }));
220
224
  }
@@ -58,20 +58,24 @@ export class WebfloClient extends WebfloRuntime {
58
58
  const instanceController = await super.initialize();
59
59
  // Bind prompt handlers
60
60
  const promptsHandler = (e) => {
61
- const message = e.data?.message
62
- ? e.data.message
63
- : e.data;
64
- const execPromp = () => {
61
+ window.queueMicrotask(() => {
65
62
  if (e.defaultPrevented) return;
66
63
  if (e.type === 'confirm') {
67
- e.wqRespondWith(confirm(message));
64
+ if (e.data?.message) {
65
+ e.wqRespondWith(confirm(e.data.message));
66
+ }
68
67
  } else if (e.type === 'prompt') {
69
- e.wqRespondWith(prompt(message));
68
+ if (e.data?.message) {
69
+ e.wqRespondWith(prompt(e.data.message));
70
+ }
70
71
  } else if (e.type === 'alert') {
71
- alert(message);
72
+ for (const item of [].concat(e.data)) {
73
+ if (item?.message) {
74
+ alert(item.message);
75
+ }
76
+ }
72
77
  }
73
- };
74
- window.queueMicrotask(execPromp);
78
+ });
75
79
  };
76
80
  this.background.addEventListener('confirm', promptsHandler, { signal: instanceController.signal });
77
81
  this.background.addEventListener('prompt', promptsHandler, { signal: instanceController.signal });
@@ -374,22 +378,41 @@ export class WebfloClient extends WebfloRuntime {
374
378
  }
375
379
 
376
380
  async dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB, originalRequestInit, processObj = {} }) {
377
- const response = await super.dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB });
378
- // Obtain and connect clientPortB as first thing
379
- const backgroundPort = LiveResponse.getBackground(response);
380
- if (backgroundPort) {
381
- this.background.addPort(backgroundPort);
382
- }
381
+ let response = await super.dispatchNavigationEvent({ httpEvent, crossLayerFetch, clientPortB });
382
+
383
+ // Extract interactive. mode handling
384
+ const handleInteractiveMode = async (resolve) => {
385
+ const liveResponse = await LiveResponse.from(response);
386
+ this.background.addPort(liveResponse.background);
387
+ liveResponse.addEventListener('replace', () => {
388
+ if (liveResponse.headers.get('Location')) {
389
+ this.processRedirect(liveResponse);
390
+ } else {
391
+ resolve?.(liveResponse);
392
+ }
393
+ }, { signal: httpEvent.signal });
394
+ return liveResponse;
395
+ };
396
+
383
397
  // Await a response with an "Accepted" or redirect status
384
398
  const statusCode = responseShim.prototype.status.get.call(response);
385
- if (statusCode === 202 || (response.headers.get('Location') && this.processRedirect(response))) {
399
+ if (statusCode === 202 && LiveResponse.hasBackground(response)) {
400
+ return new Promise(handleInteractiveMode);
401
+ }
402
+
403
+ // Handle redirects
404
+ if (response.headers.get('Location')) {
405
+ // Never resolves...
386
406
  return new Promise(async (resolve) => {
387
- if (LiveResponse.hasBackground(response)) {
388
- const liveResponse = await LiveResponse.from(response);
389
- liveResponse.addEventListener('replace', () => resolve(liveResponse), { once: true, signal: httpEvent.signal });
390
- } // Never resolves otherwise
407
+ const redirectHandlingMode = this.processRedirect(response);
408
+ // ...except processRedirect() says keep-alive
409
+ if (redirectHandlingMode === 3/* keep-alive */
410
+ && LiveResponse.hasBackground(response)) {
411
+ await handleInteractiveMode(resolve);
412
+ }
391
413
  });
392
414
  }
415
+
393
416
  // Handle "retry" directives
394
417
  if (response.headers.has('Retry-After')) {
395
418
  if (!processObj.recurseController) {
@@ -400,13 +423,21 @@ export class WebfloClient extends WebfloRuntime {
400
423
  // Ensure a previous recursion hasn't aborted the process
401
424
  if (!processObj.recurseController.signal.aborted) {
402
425
  await new Promise((res) => setTimeout(res, parseInt(response.headers.get('Retry-After')) * 1000));
403
- const eventClone = httpEvent.cloneWith({ request: this.createRequest(httpEvent.url, originalRequestInit) });
426
+ const eventClone = httpEvent.clone({ request: this.createRequest(httpEvent.url, originalRequestInit) });
404
427
  return await this.dispatchNavigationEvent({ httpEvent: eventClone, crossLayerFetch, clientPortB, originalRequestInit, processObj });
405
428
  }
406
- } else if (processObj.recurseController) {
407
- // Abort the signal. This is the end of the loop
408
- processObj.recurseController.abort();
429
+ } else {
430
+ if (processObj.recurseController) {
431
+ // Abort the signal. This is the end of the loop
432
+ processObj.recurseController.abort();
433
+ }
434
+
435
+ // Obtain and connect clientPortB as first thing
436
+ if (LiveResponse.hasBackground(response)) {
437
+ response = await handleInteractiveMode();
438
+ }
409
439
  }
440
+
410
441
  return response;
411
442
  }
412
443
 
@@ -419,24 +450,23 @@ export class WebfloClient extends WebfloRuntime {
419
450
  responseMeta.set('status', xActualRedirectCode); // @NOTE 1
420
451
  statusCode = xActualRedirectCode;
421
452
  }
453
+
422
454
  // Trigger redirect
423
455
  if ([302, 301].includes(statusCode)) {
424
456
  const location = new URL(response.headers.get('Location'), this.location.origin);
425
457
  if (this.isSpaRoute(location)) {
426
458
  this.navigate(location, {}, { navigationType: 'rdr' });
427
- } else {
428
- this.redirect(location, LiveResponse.getBackground(response));
459
+ return 1;
429
460
  }
430
- return true;
461
+ return this.redirect(location, response);
431
462
  }
463
+
464
+ return 0; // No actual redirect
432
465
  }
433
466
 
434
- redirect(location, responseBackground) {
435
- if (responseBackground) {
436
- // Redundant as this is a window reload anyways
437
- responseBackground.close();
438
- }
467
+ redirect(location) {
439
468
  window.location = location;
469
+ return 2; // Window reload
440
470
  }
441
471
 
442
472
  async transitionUI(updateCallback) {
@@ -482,8 +512,9 @@ export class WebfloClient extends WebfloRuntime {
482
512
  transition: this.transition,
483
513
  }, { diff: true, merge });
484
514
  $response.addEventListener('replace', (e) => {
485
- if ($response.headers.get('Location') && this.processRedirect($response)) return;
486
- this.host[bindingsConfig.bindings].data = $response.body;
515
+ if (!$response.headers.get('Location')) {
516
+ this.host[bindingsConfig.bindings].data = $response.body;
517
+ }
487
518
  });
488
519
  }
489
520
  if (modulesContextAttrs) {
@@ -90,16 +90,18 @@ export class WebfloSubClient extends WebfloClient {
90
90
  (this.host.querySelector('[autofocus]') || this.host).focus();
91
91
  }
92
92
 
93
- redirect(location, responseBackground = null) {
93
+ redirect(location, response = null) {
94
94
  location = typeof location === 'string' ? new URL(location, this.location.origin) : location;
95
95
  const width = Math.min(800, window.innerWidth);
96
96
  const height = Math.min(600, window.innerHeight);
97
97
  const left = (window.outerWidth - width) / 2;
98
98
  const top = (window.outerHeight - height) / 2;
99
99
  const popup = window.open(location, '_blank', `popup=true,width=${width},height=${height},left=${left},top=${top}`);
100
- if (responseBackground) {
100
+ if (response && LiveResponse.hasBackground(response)) {
101
101
  Observer.set(this.navigator, 'redirecting', new Url/*NOT URL*/(location), { diff: true });
102
- responseBackground.addEventListener('close', (e) => {
102
+ const backgroundPort = LiveResponse.getBackground(response);
103
+ backgroundPort.postMessage('keep-alive');
104
+ backgroundPort.addEventListener('close', (e) => {
103
105
  window.removeEventListener('message', windowMessageHandler);
104
106
  Observer.set(this.navigator, 'redirecting', null);
105
107
  popup.postMessage('timeout:5');
@@ -109,10 +111,12 @@ export class WebfloSubClient extends WebfloClient {
109
111
  }, { once: true });
110
112
  const windowMessageHandler = (e) => {
111
113
  if (e.source === popup && e.data === 'close') {
112
- responseBackground.close();
114
+ backgroundPort.close();
113
115
  }
114
116
  };
115
117
  window.addEventListener('message', windowMessageHandler);
116
118
  }
119
+
120
+ return 3; // keep-alive
117
121
  }
118
122
  }
@@ -50,7 +50,8 @@ export class LiveResponse extends EventTarget {
50
50
  throw new Error('Argument must be a Response instance.');
51
51
  }
52
52
 
53
- const body = await responseShim.prototype.parse.value.call(response);
53
+ let body = null;
54
+ try { body = await responseShim.prototype.parse.value.call(response); } catch(e) {}
54
55
 
55
56
  // Instance
56
57
  const instance = new this(body, {
@@ -87,11 +87,6 @@ export const request = {
87
87
  }
88
88
  },
89
89
  prototype: {
90
- carries: {
91
- get: function () {
92
- return _wq(this, 'meta').get('carries') || [];
93
- }
94
- },
95
90
  parse: { value: async function () { return await parseHttpMessage(this); } },
96
91
  clone: {
97
92
  value: function (init = {}) {
@@ -149,7 +144,6 @@ export const response = {
149
144
  : this.status);
150
145
  }
151
146
  },
152
- carry: { get: function () { return _wq(this, 'meta').get('carry'); } },
153
147
  parse: { value: async function () { return await parseHttpMessage(this); } },
154
148
  clone: {
155
149
  value: function (init = {}) {
@@ -132,7 +132,7 @@ export class HttpEvent {
132
132
  }
133
133
  //-----
134
134
  const urlRewrite = new URL(url, this.request.url);
135
- const newThread = this.thread.extend(urlRewrite.searchParams.get('_thread'));
135
+ const newThread = this.thread.spawn(urlRewrite.searchParams.get('_thread'));
136
136
  urlRewrite.searchParams.set('_thread', newThread.threadID);
137
137
  await newThread.append('back', this.request.url.replace(urlRewrite.origin, ''));
138
138
  for (const [key, value] of Object.entries(data)) {
@@ -146,9 +146,8 @@ export class HttpEvent {
146
146
  return this.constructor.create(this.#parentEvent, { ...this.#init, ...init });
147
147
  }
148
148
 
149
- extend(init = {}) {
150
- const instance = this.constructor.create(this/*Main difference from clone*/, { ...this.#init, ...(init || {}) });
151
- return instance;
149
+ spawn(init = {}) {
150
+ return this.constructor.create(this/*Main difference from clone*/, { ...this.#init, ...(init || {}) });
152
151
  }
153
152
 
154
153
  abort() {
@@ -140,7 +140,7 @@ export class HttpState {
140
140
  return new Promise(() => { });
141
141
  }
142
142
  const urlRewrite = new URL(handler.url, this.#request.url);
143
- const newThread = this.#thread.extend(urlRewrite.searchParams.get('_thread'));
143
+ const newThread = this.#thread.spawn(urlRewrite.searchParams.get('_thread')/* show */);
144
144
  urlRewrite.searchParams.set('_thread', newThread.threadID);
145
145
  await newThread.append('back', this.#request.url.replace(urlRewrite.origin, ''));
146
146
  if (handler.with) {
@@ -10,6 +10,7 @@ export class HttpThread {
10
10
  #store;
11
11
  #threadID;
12
12
  #realm;
13
+ #extended = false;
13
14
 
14
15
  get threadID() { return this.#threadID; }
15
16
 
@@ -19,7 +20,7 @@ export class HttpThread {
19
20
  this.#realm = realm;
20
21
  }
21
22
 
22
- extend(_threadID = null) {
23
+ spawn(_threadID = null) {
23
24
  return this.constructor.create({
24
25
  store: this.#store,
25
26
  threadID: _threadID,
@@ -78,4 +79,8 @@ export class HttpThread {
78
79
  await this.#store.delete(this.#threadID);
79
80
  return this;
80
81
  }
82
+
83
+ get extended() { return this.#extended; }
84
+
85
+ extend(set = true) { this.#extended = !!set; }
81
86
  }
@@ -132,7 +132,7 @@ export class WebfloRouter {
132
132
 
133
133
  // Set context parameters
134
134
  nextTick.method = request.method;
135
- nextTick.event = await thisTick.event.extend({ request });
135
+ nextTick.event = await thisTick.event.spawn({ request });
136
136
  nextTick.source = thisTick.destination.join('/');
137
137
  nextTick.destination = url.pathname.split('/').map((a) => a.trim()).filter((a) => a);
138
138
  nextTick.trail = !urlStr_isRelative ? [] : thisTick.trail.reduce((_commonRoot, _seg, i) => _commonRoot.length === i && _seg === nextTick.destination[i] ? _commonRoot.concat(_seg) : _commonRoot, []);
@@ -141,7 +141,7 @@ export class WebfloRouter {
141
141
  if (isFetch) {
142
142
  throw new Error('fetch() cannot be called without arguments!');
143
143
  }
144
- nextTick.event = thisTick.event.extend();
144
+ nextTick.event = thisTick.event.spawn();
145
145
  }
146
146
  const result = await next(nextTick);
147
147
  if (asResponse) {