@webqit/webflo 0.10.4 → 0.11.1
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/README.md +1490 -52
- package/bundle.html.json +1665 -0
- package/package.json +2 -2
- package/src/Context.js +1 -1
- package/src/config-pi/runtime/Client.js +37 -9
- package/src/config-pi/runtime/Server.js +24 -8
- package/src/config-pi/runtime/client/Worker.js +30 -12
- package/src/runtime-pi/Router.js +1 -1
- package/src/runtime-pi/client/Runtime.js +116 -62
- package/src/runtime-pi/client/RuntimeClient.js +28 -43
- package/src/runtime-pi/client/Workport.js +163 -0
- package/src/runtime-pi/client/generate.js +282 -74
- package/src/runtime-pi/client/{generate.oohtml.js → oohtml/full.js} +0 -0
- package/src/runtime-pi/client/oohtml/namespacing.js +7 -0
- package/src/runtime-pi/client/oohtml/scripting.js +8 -0
- package/src/runtime-pi/client/oohtml/templating.js +8 -0
- package/src/runtime-pi/client/worker/Worker.js +58 -24
- package/src/runtime-pi/client/worker/Workport.js +80 -0
- package/src/runtime-pi/server/Router.js +2 -2
- package/src/runtime-pi/server/Runtime.js +30 -11
- package/src/runtime-pi/server/RuntimeClient.js +24 -14
- package/src/runtime-pi/util.js +2 -2
- package/test/site/package.json +9 -0
- package/test/site/public/bundle.html +6 -0
- package/test/site/public/bundle.html.json +4 -0
- package/test/site/public/bundle.js +2 -0
- package/test/site/public/bundle.js.gz +0 -0
- package/test/site/public/bundle.webflo.js +15 -0
- package/test/site/public/bundle.webflo.js.gz +0 -0
- package/test/site/public/index.html +30 -0
- package/test/site/public/index1.html +35 -0
- package/test/site/public/page-2/bundle.html +5 -0
- package/test/site/public/page-2/bundle.html.json +1 -0
- package/test/site/public/page-2/bundle.js +2 -0
- package/test/site/public/page-2/bundle.js.gz +0 -0
- package/test/site/public/page-2/index.html +46 -0
- package/test/site/public/page-2/logo-130x130.png +0 -0
- package/test/site/public/page-2/main.html +3 -0
- package/test/site/public/page-3/logo-130x130.png +0 -0
- package/test/site/public/page-4/subpage/bundle.html +0 -0
- package/test/site/public/page-4/subpage/bundle.html.json +1 -0
- package/test/site/public/page-4/subpage/bundle.js +2 -0
- package/test/site/public/page-4/subpage/bundle.js.gz +0 -0
- package/test/site/public/page-4/subpage/index.html +31 -0
- package/test/site/public/sparoots.json +5 -0
- package/test/site/public/worker.js +3 -0
- package/test/site/public/worker.js.gz +0 -0
- package/test/site/server/index.js +16 -0
- package/docker/Dockerfile +0 -26
- package/docker/README.md +0 -77
- package/src/runtime-pi/client/WorkerComm.js +0 -102
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.
|
|
15
|
+
"version": "0.11.1",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@octokit/webhooks": "^7.15.1",
|
|
39
39
|
"@webqit/backpack": "^0.1.0",
|
|
40
|
-
"@webqit/oohtml-ssr": "^1.0
|
|
40
|
+
"@webqit/oohtml-ssr": "^1.1.0",
|
|
41
41
|
"@webqit/util": "^0.8.9",
|
|
42
42
|
"client-sessions": "^0.8.0",
|
|
43
43
|
"esbuild": "^0.14.38",
|
package/src/Context.js
CHANGED
|
@@ -23,8 +23,10 @@ export default class Client extends Dotfile {
|
|
|
23
23
|
withDefaults(config) {
|
|
24
24
|
return _merge(true, {
|
|
25
25
|
bundle_filename: 'bundle.js',
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
public_base_url: '/',
|
|
27
|
+
spa_routing: true,
|
|
28
|
+
oohtml_support: 'full',
|
|
29
|
+
service_worker_support: true,
|
|
28
30
|
worker_scope: '/',
|
|
29
31
|
worker_filename: 'worker.js',
|
|
30
32
|
}, config);
|
|
@@ -32,6 +34,16 @@ export default class Client extends Dotfile {
|
|
|
32
34
|
|
|
33
35
|
// Questions generator
|
|
34
36
|
questions(config, choices = {}) {
|
|
37
|
+
// Choices
|
|
38
|
+
const CHOICES = _merge({
|
|
39
|
+
oohtml_support: [
|
|
40
|
+
{value: 'full', title: 'Full'},
|
|
41
|
+
{value: 'namespacing', title: 'namespacing'},
|
|
42
|
+
{value: 'scripting', title: 'scripting'},
|
|
43
|
+
{value: 'templating', title: 'templating'},
|
|
44
|
+
{value: 'none', title: 'none'},
|
|
45
|
+
],
|
|
46
|
+
}, choices);
|
|
35
47
|
// Questions
|
|
36
48
|
return [
|
|
37
49
|
{
|
|
@@ -41,30 +53,46 @@ export default class Client extends Dotfile {
|
|
|
41
53
|
initial: config.bundle_filename,
|
|
42
54
|
},
|
|
43
55
|
{
|
|
44
|
-
name: '
|
|
56
|
+
name: 'public_base_url',
|
|
57
|
+
type: 'text',
|
|
58
|
+
message: '[public_base_url]: Enter the base-URL for public resource URLs',
|
|
59
|
+
initial: DATA.public_base_url,
|
|
60
|
+
validation: ['important'],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'spa_routing',
|
|
45
64
|
type: 'toggle',
|
|
46
|
-
message: '
|
|
65
|
+
message: '[spa_routing]: Enable Single Page Routing Mode',
|
|
47
66
|
active: 'YES',
|
|
48
67
|
inactive: 'NO',
|
|
49
|
-
initial: config.
|
|
68
|
+
initial: config.spa_routing,
|
|
69
|
+
validation: ['important'],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'oohtml_support',
|
|
73
|
+
type: 'select',
|
|
74
|
+
message: '[oohtml_support]: (Adds OOHTML to your app\'s bundle.) Specify OOHTML support level',
|
|
75
|
+
choices: CHOICES.oohtml_support,
|
|
76
|
+
initial: this.indexOfInitial(CHOICES.oohtml_support, config.oohtml_support),
|
|
77
|
+
validation: ['important'],
|
|
50
78
|
},
|
|
51
79
|
{
|
|
52
|
-
name: '
|
|
80
|
+
name: 'service_worker_support',
|
|
53
81
|
type: 'toggle',
|
|
54
82
|
message: 'Support Service Worker?',
|
|
55
83
|
active: 'YES',
|
|
56
84
|
inactive: 'NO',
|
|
57
|
-
initial: config.
|
|
85
|
+
initial: config.service_worker_support,
|
|
58
86
|
},
|
|
59
87
|
{
|
|
60
88
|
name: 'worker_scope',
|
|
61
|
-
type: (prev, answers) => answers.
|
|
89
|
+
type: (prev, answers) => answers.service_worker_support ? 'text' : null,
|
|
62
90
|
message: 'Specify the Service Worker scope',
|
|
63
91
|
initial: config.worker_scope,
|
|
64
92
|
},
|
|
65
93
|
{
|
|
66
94
|
name: 'worker_filename',
|
|
67
|
-
type: (prev, answers) => answers.
|
|
95
|
+
type: (prev, answers) => answers.service_worker_support ? 'text' : null,
|
|
68
96
|
message: 'Specify the Service Worker filename',
|
|
69
97
|
initial: config.worker_filename,
|
|
70
98
|
},
|
|
@@ -29,6 +29,7 @@ export default class Server extends Dotfile {
|
|
|
29
29
|
force: false,
|
|
30
30
|
},
|
|
31
31
|
force_www: '',
|
|
32
|
+
oohtml_support: 'full',
|
|
32
33
|
shared: false,
|
|
33
34
|
}, config);
|
|
34
35
|
}
|
|
@@ -42,13 +43,20 @@ export default class Server extends Dotfile {
|
|
|
42
43
|
{value: 'add',},
|
|
43
44
|
{value: 'remove',},
|
|
44
45
|
],
|
|
46
|
+
oohtml_support: [
|
|
47
|
+
{value: 'full', title: 'full'},
|
|
48
|
+
{value: 'namespacing', title: 'namespacing'},
|
|
49
|
+
{value: 'scripting', title: 'scripting'},
|
|
50
|
+
{value: 'templating', title: 'templating'},
|
|
51
|
+
{value: 'none', title: 'none'},
|
|
52
|
+
],
|
|
45
53
|
}, choices);
|
|
46
54
|
// Questions
|
|
47
55
|
return [
|
|
48
56
|
{
|
|
49
57
|
name: 'port',
|
|
50
58
|
type: 'number',
|
|
51
|
-
message: 'Enter port number',
|
|
59
|
+
message: '[port]: Enter port number',
|
|
52
60
|
initial: config.port,
|
|
53
61
|
validation: ['important'],
|
|
54
62
|
},
|
|
@@ -62,31 +70,31 @@ export default class Server extends Dotfile {
|
|
|
62
70
|
{
|
|
63
71
|
name: 'port',
|
|
64
72
|
type: 'number',
|
|
65
|
-
message: 'Enter HTTPS port number',
|
|
73
|
+
message: '[port]: Enter HTTPS port number',
|
|
66
74
|
validation: ['important'],
|
|
67
75
|
},
|
|
68
76
|
{
|
|
69
77
|
name: 'keyfile',
|
|
70
78
|
type: 'text',
|
|
71
|
-
message: 'Enter SSL KEY file',
|
|
79
|
+
message: '[keyfile]: Enter SSL KEY file',
|
|
72
80
|
validation: ['important'],
|
|
73
81
|
},
|
|
74
82
|
{
|
|
75
83
|
name: 'certfile',
|
|
76
84
|
type: 'text',
|
|
77
|
-
message: 'Enter SSL CERT file',
|
|
85
|
+
message: '[certfile]: Enter SSL CERT file',
|
|
78
86
|
validation: ['important'],
|
|
79
87
|
},
|
|
80
88
|
{
|
|
81
89
|
name: 'certdoms',
|
|
82
90
|
type: 'list',
|
|
83
|
-
message: 'Enter the CERT domains (comma-separated)',
|
|
91
|
+
message: '[certdoms]: Enter the CERT domains (comma-separated)',
|
|
84
92
|
validation: ['important'],
|
|
85
93
|
},
|
|
86
94
|
{
|
|
87
95
|
name: 'force',
|
|
88
96
|
type: 'toggle',
|
|
89
|
-
message: 'Force HTTPS?',
|
|
97
|
+
message: '[force]: Force HTTPS?',
|
|
90
98
|
active: 'YES',
|
|
91
99
|
inactive: 'NO',
|
|
92
100
|
},
|
|
@@ -95,14 +103,22 @@ export default class Server extends Dotfile {
|
|
|
95
103
|
{
|
|
96
104
|
name: 'force_www',
|
|
97
105
|
type: 'select',
|
|
98
|
-
message: 'Force add/remove "www" on hostname?',
|
|
106
|
+
message: '[force_www]: Force add/remove "www" on hostname?',
|
|
99
107
|
choices: CHOICES.force_www,
|
|
100
108
|
initial: this.indexOfInitial(CHOICES.force_www, config.force_www),
|
|
101
109
|
},
|
|
110
|
+
{
|
|
111
|
+
name: 'oohtml_support',
|
|
112
|
+
type: 'select',
|
|
113
|
+
message: '[oohtml_support]: Specify OOHTML support level',
|
|
114
|
+
choices: CHOICES.oohtml_support,
|
|
115
|
+
initial: this.indexOfInitial(CHOICES.oohtml_support, config.oohtml_support),
|
|
116
|
+
validation: ['important'],
|
|
117
|
+
},
|
|
102
118
|
{
|
|
103
119
|
name: 'shared',
|
|
104
120
|
type: 'toggle',
|
|
105
|
-
message: 'Shared server?',
|
|
121
|
+
message: '[shared]: Shared server?',
|
|
106
122
|
active: 'YES',
|
|
107
123
|
inactive: 'NO',
|
|
108
124
|
initial: config.shared,
|
|
@@ -23,10 +23,11 @@ export default class Worker extends Dotfile {
|
|
|
23
23
|
withDefaults(config) {
|
|
24
24
|
return _merge(true, {
|
|
25
25
|
cache_name: 'cache_v0',
|
|
26
|
-
|
|
26
|
+
default_fetching_strategy: 'network-first',
|
|
27
|
+
network_first_urls: [],
|
|
27
28
|
cache_first_urls: [],
|
|
28
29
|
network_only_urls: [],
|
|
29
|
-
|
|
30
|
+
cache_only_urls: [ '/page-3/{*.json}' ],
|
|
30
31
|
skip_waiting: false,
|
|
31
32
|
// -----------------
|
|
32
33
|
support_push: false,
|
|
@@ -42,6 +43,15 @@ export default class Worker extends Dotfile {
|
|
|
42
43
|
if (config.cache_name && config.cache_name.indexOf('_v') > -1 && _isNumeric(_after(config.cache_name, '_v'))) {
|
|
43
44
|
config.cache_name = _before(config.cache_name, '_v') + '_v' + (parseInt(_after(config.cache_name, '_v')) + 1);
|
|
44
45
|
}
|
|
46
|
+
// Choices
|
|
47
|
+
const CHOICES = _merge({
|
|
48
|
+
default_fetching_strategy: [
|
|
49
|
+
{value: 'network-first', title: 'Network-first (Webflo default)'},
|
|
50
|
+
{value: 'cache-first', title: 'Cache-first'},
|
|
51
|
+
{value: 'network-only', title: 'Network-only'},
|
|
52
|
+
{value: 'cache-only', title: 'Cache-only'},
|
|
53
|
+
],
|
|
54
|
+
}, choices);
|
|
45
55
|
// Questions
|
|
46
56
|
return [
|
|
47
57
|
{
|
|
@@ -51,28 +61,36 @@ export default class Worker extends Dotfile {
|
|
|
51
61
|
initial: config.cache_name,
|
|
52
62
|
},
|
|
53
63
|
{
|
|
54
|
-
name: '
|
|
55
|
-
type: '
|
|
56
|
-
message: '
|
|
57
|
-
|
|
64
|
+
name: 'default_fetching_strategy',
|
|
65
|
+
type: 'select',
|
|
66
|
+
message: '[default_fetching_strategy]: Choose the default fetching strategy',
|
|
67
|
+
choices: CHOICES.default_fetching_strategy,
|
|
68
|
+
initial: this.indexOfInitial(CHOICES.default_fetching_strategy, config.default_fetching_strategy),
|
|
69
|
+
validation: ['important'],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'network_first_urls',
|
|
73
|
+
type: (prev, answers) => answers.default_fetching_strategy === 'network-first' ? null : 'list',
|
|
74
|
+
message: 'Specify URLs for a "network-first-then-cache" fetching strategy (comma-separated, globe supported)',
|
|
75
|
+
initial: (config.network_first_urls || []).join(', '),
|
|
58
76
|
},
|
|
59
77
|
{
|
|
60
78
|
name: 'cache_first_urls',
|
|
61
|
-
type: 'list',
|
|
79
|
+
type: (prev, answers) => answers.default_fetching_strategy === 'cache-first' ? null : 'list',
|
|
62
80
|
message: 'Specify URLs for a "cache-first-then-network" fetching strategy (comma-separated, globe supported)',
|
|
63
81
|
initial: (config.cache_first_urls || []).join(', '),
|
|
64
82
|
},
|
|
65
83
|
{
|
|
66
84
|
name: 'network_only_urls',
|
|
67
|
-
type: 'list',
|
|
85
|
+
type: (prev, answers) => answers.default_fetching_strategy === 'network-only' ? null : 'list',
|
|
68
86
|
message: 'Specify URLs for a "network-only" fetching strategy (comma-separated, globe supported)',
|
|
69
87
|
initial: (config.network_only_urls || []).join(', '),
|
|
70
88
|
},
|
|
71
89
|
{
|
|
72
|
-
name: '
|
|
73
|
-
type: 'list',
|
|
74
|
-
message: 'Specify URLs for a "
|
|
75
|
-
initial: (config.
|
|
90
|
+
name: 'cache_only_urls',
|
|
91
|
+
type: (prev, answers) => answers.default_fetching_strategy === 'cache-only' ? null : 'list',
|
|
92
|
+
message: 'Specify URLs for a "cache-only" fetching strategy (comma-separated, globe supported)',
|
|
93
|
+
initial: (config.cache_only_urls || []).join(', '),
|
|
76
94
|
},
|
|
77
95
|
{
|
|
78
96
|
name: 'skip_waiting',
|
package/src/runtime-pi/Router.js
CHANGED
|
@@ -46,7 +46,7 @@ export default class Router {
|
|
|
46
46
|
// The loop
|
|
47
47
|
// ----------------
|
|
48
48
|
const next = async function(thisTick) {
|
|
49
|
-
const thisContext = {};
|
|
49
|
+
const thisContext = { };
|
|
50
50
|
if (!thisTick.trail || thisTick.trail.length < thisTick.destination.length) {
|
|
51
51
|
thisTick = await $this.readTick(thisTick);
|
|
52
52
|
// -------------
|
|
@@ -16,6 +16,7 @@ import xRequest from "../xRequest.js";
|
|
|
16
16
|
import xResponse from "../xResponse.js";
|
|
17
17
|
import xfetch from '../xfetch.js';
|
|
18
18
|
import xHttpEvent from '../xHttpEvent.js';
|
|
19
|
+
import Workport from './Workport.js';
|
|
19
20
|
|
|
20
21
|
const URL = xURL(whatwag.URL);
|
|
21
22
|
const FormData = xFormData(whatwag.FormData);
|
|
@@ -98,8 +99,7 @@ export default class Runtime {
|
|
|
98
99
|
window.addEventListener('click', e => {
|
|
99
100
|
var anchor = e.target.closest('a');
|
|
100
101
|
if (!anchor || !anchor.href) return;
|
|
101
|
-
if (!anchor.target && !anchor.download && (
|
|
102
|
-
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
|
|
102
|
+
if (!anchor.target && !anchor.download && this.isSpaRoute(anchor, e)) {
|
|
103
103
|
// Publish everything, including hash
|
|
104
104
|
this.go(Url.copy(anchor), {}, { src: anchor, srcType: 'link', });
|
|
105
105
|
// URLs with # will cause a natural navigation
|
|
@@ -129,8 +129,7 @@ export default class Runtime {
|
|
|
129
129
|
actionEl.href = submitParams.action;
|
|
130
130
|
// ---------------
|
|
131
131
|
// If not targeted and same origin...
|
|
132
|
-
if (!submitParams.target && (
|
|
133
|
-
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
|
|
132
|
+
if (!submitParams.target && this.isSpaRoute(actionEl, e)) {
|
|
134
133
|
// Build data
|
|
135
134
|
var formData = new FormData(form);
|
|
136
135
|
if ((submitter || {}).name) {
|
|
@@ -164,6 +163,27 @@ export default class Runtime {
|
|
|
164
163
|
window.addEventListener('online', () => Observer.set(this.network, 'online', navigator.onLine));
|
|
165
164
|
window.addEventListener('offline', () => Observer.set(this.network, 'online', navigator.onLine));
|
|
166
165
|
|
|
166
|
+
// -----------------------
|
|
167
|
+
// Service Worker && COMM
|
|
168
|
+
if (this.cx.service_worker_support) {
|
|
169
|
+
let workport = new Workport(this.cx.worker_filename, { scope: this.cx.worker_scope, startMessages: true });
|
|
170
|
+
Observer.set(this, 'workport', workport);
|
|
171
|
+
workport.messaging.listen(async evt => {
|
|
172
|
+
let responsePort = evt.ports[0];
|
|
173
|
+
let client = this.clients.get('*');
|
|
174
|
+
let response = client.alert && await client.alert(evt);
|
|
175
|
+
if (responsePort) {
|
|
176
|
+
if (response instanceof Promise) {
|
|
177
|
+
response.then(data => {
|
|
178
|
+
responsePort.postMessage(data);
|
|
179
|
+
});
|
|
180
|
+
} else {
|
|
181
|
+
responsePort.postMessage(response);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
167
187
|
// ---------------
|
|
168
188
|
this.go(this.location, {}, { srcType: 'init' });
|
|
169
189
|
// ---------------
|
|
@@ -176,6 +196,43 @@ export default class Runtime {
|
|
|
176
196
|
return window.history;
|
|
177
197
|
}
|
|
178
198
|
|
|
199
|
+
// Check is-route
|
|
200
|
+
isSpaRoute(url, e) {
|
|
201
|
+
url = typeof url === 'string' ? new whatwag.URL(url) : url;
|
|
202
|
+
if (url.origin && url.origin !== this.location.origin) return false;
|
|
203
|
+
if (e && (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)) return false;
|
|
204
|
+
if (!this.cx.params.routing) return true;
|
|
205
|
+
if (this.cx.params.routing.targets === false/** explicit false means disabled */) return false;
|
|
206
|
+
let b = url.pathname.split('/').filter(s => s);
|
|
207
|
+
const match = a => {
|
|
208
|
+
a = a.split('/').filter(s => s);
|
|
209
|
+
return a.reduce((prev, s, i) => prev && (s === b[i] || [s, b[i]].includes('-')), true);
|
|
210
|
+
};
|
|
211
|
+
return match(this.cx.params.routing.root) && this.cx.params.routing.subroots.reduce((prev, subroot) => {
|
|
212
|
+
return prev && !match(subroot);
|
|
213
|
+
}, true);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Generates request object
|
|
217
|
+
generateRequest(href, init) {
|
|
218
|
+
return new Request(href, {
|
|
219
|
+
signal: this._abortController.signal,
|
|
220
|
+
...init,
|
|
221
|
+
headers: {
|
|
222
|
+
'Accept': 'application/json',
|
|
223
|
+
'X-Redirect-Policy': 'manual-when-cross-spa',
|
|
224
|
+
'X-Redirect-Code': this._xRedirectCode,
|
|
225
|
+
'X-Powered-By': '@webqit/webflo',
|
|
226
|
+
...(init.headers || {}),
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Generates session object
|
|
232
|
+
getSession(e, id = null, persistent = false) {
|
|
233
|
+
return Storage(id, persistent);
|
|
234
|
+
}
|
|
235
|
+
|
|
179
236
|
/**
|
|
180
237
|
* Performs a request.
|
|
181
238
|
*
|
|
@@ -192,7 +249,7 @@ export default class Runtime {
|
|
|
192
249
|
// Put his forward before instantiating a request and aborting previous
|
|
193
250
|
// Same-page hash-links clicks on chrome recurse here from histroy popstate
|
|
194
251
|
if (detail.srcType !== 'init' && (_before(url.href, '#') === _before(init.referrer, '#') && (init.method || 'GET').toUpperCase() === 'GET')) {
|
|
195
|
-
return;
|
|
252
|
+
return new Response(null, { status: 304 }); // Not Modified
|
|
196
253
|
}
|
|
197
254
|
// ------------
|
|
198
255
|
if (this._abortController) {
|
|
@@ -201,55 +258,77 @@ export default class Runtime {
|
|
|
201
258
|
this._abortController = new AbortController();
|
|
202
259
|
this._xRedirectCode = 200;
|
|
203
260
|
// ------------
|
|
261
|
+
// States
|
|
262
|
+
// ------------
|
|
263
|
+
Observer.set(this.network, 'error', null);
|
|
264
|
+
Observer.set(this.network, 'requesting', { ...init, ...detail });
|
|
204
265
|
if (['link', 'form'].includes(detail.srcType)) {
|
|
205
|
-
|
|
206
|
-
|
|
266
|
+
detail.src.state && (detail.src.state.active = true);
|
|
267
|
+
detail.submitter && detail.submitter.state && (detail.submitter.state.active = true);
|
|
207
268
|
}
|
|
208
269
|
// ------------
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// ------------
|
|
270
|
+
// Run
|
|
271
|
+
// ------------
|
|
212
272
|
// The request object
|
|
213
273
|
let request = this.generateRequest(url.href, init);
|
|
214
274
|
// The navigation event
|
|
215
275
|
let httpEvent = new HttpEvent(request, detail, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
|
|
216
276
|
// Response
|
|
217
|
-
let
|
|
218
|
-
|
|
277
|
+
let client = this.clients.get('*'), response, finalResponse;
|
|
278
|
+
try {
|
|
279
|
+
// ------------
|
|
280
|
+
// Response
|
|
281
|
+
// ------------
|
|
282
|
+
response = await client.handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
|
|
283
|
+
finalResponse = this.handleResponse(httpEvent, response);
|
|
284
|
+
// ------------
|
|
285
|
+
// Address bar
|
|
286
|
+
// ------------
|
|
287
|
+
if (response.redirected) {
|
|
288
|
+
Observer.set(this.location, { href: response.url }, { detail: { redirected: true }, });
|
|
289
|
+
} else if (![302, 301].includes(finalResponse.status)) {
|
|
290
|
+
Observer.set(this.location, url);
|
|
291
|
+
}
|
|
292
|
+
// ------------
|
|
293
|
+
// States
|
|
294
|
+
// ------------
|
|
295
|
+
Observer.set(this.network, 'requesting', null);
|
|
296
|
+
if (['link', 'form'].includes(detail.srcType)) {
|
|
297
|
+
detail.src.state && (detail.src.state.active = false);
|
|
298
|
+
detail.submitter && detail.submitter.state && (detail.submitter.state.active = false);
|
|
299
|
+
}
|
|
300
|
+
// ------------
|
|
301
|
+
// Rendering
|
|
302
|
+
// ------------
|
|
303
|
+
if (finalResponse.ok && finalResponse.headers.contentType === 'application/json') {
|
|
304
|
+
client.render && await client.render(httpEvent, finalResponse);
|
|
305
|
+
} else if (!finalResponse.ok) {
|
|
306
|
+
if ([404, 500].includes(finalResponse.status)) {
|
|
307
|
+
Observer.set(this.network, 'error', new Error(finalResponse.statusText, { cause: finalResponse.status }));
|
|
308
|
+
}
|
|
309
|
+
client.unrender && await client.unrender(httpEvent);
|
|
310
|
+
}
|
|
311
|
+
} catch(e) {
|
|
312
|
+
console.error(e);
|
|
313
|
+
Observer.set(this.network, 'error', { ...e, retry: () => this.go(url, init = {}, detail) });
|
|
314
|
+
finalResponse = new Response(null, { status: 500, statusText: e.message });
|
|
315
|
+
}
|
|
316
|
+
// ------------
|
|
219
317
|
// Return value
|
|
220
318
|
return finalResponse;
|
|
221
319
|
}
|
|
222
320
|
|
|
223
|
-
// Generates request object
|
|
224
|
-
generateRequest(href, init) {
|
|
225
|
-
return new Request(href, {
|
|
226
|
-
signal: this._abortController.signal,
|
|
227
|
-
...init,
|
|
228
|
-
headers: {
|
|
229
|
-
'Accept': 'application/json',
|
|
230
|
-
'X-Redirect-Policy': 'manual-when-cross-origin',
|
|
231
|
-
'X-Redirect-Code': this._xRedirectCode,
|
|
232
|
-
'X-Powered-By': '@webqit/webflo',
|
|
233
|
-
...(init.headers || {}),
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Generates session object
|
|
239
|
-
getSession(e, id = null, persistent = false) {
|
|
240
|
-
return Storage(id, persistent);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
321
|
// Initiates remote fetch and sets the status
|
|
244
322
|
remoteFetch(request, ...args) {
|
|
245
|
-
|
|
323
|
+
let href = typeof request === 'string' ? request : (request.url || request.href);
|
|
324
|
+
Observer.set(this.network, 'remote', href);
|
|
246
325
|
let _response = fetch(request, ...args);
|
|
247
326
|
// This catch() is NOT intended to handle failure of the fetch
|
|
248
|
-
_response.catch(e => Observer.set(this.network, 'error', e
|
|
327
|
+
_response.catch(e => Observer.set(this.network, 'error', e));
|
|
249
328
|
// Return xResponse
|
|
250
329
|
return _response.then(async response => {
|
|
251
330
|
// Stop loading status
|
|
252
|
-
Observer.set(this.network, 'remote',
|
|
331
|
+
Observer.set(this.network, 'remote', null);
|
|
253
332
|
return new Response(response);
|
|
254
333
|
});
|
|
255
334
|
}
|
|
@@ -257,19 +336,10 @@ export default class Runtime {
|
|
|
257
336
|
// Handles response object
|
|
258
337
|
handleResponse(e, response) {
|
|
259
338
|
if (!(response instanceof Response)) { response = new Response(response); }
|
|
260
|
-
|
|
261
|
-
Observer.set(this.network, 'error', null);
|
|
262
|
-
if (['link', 'form'].includes(e.detail.srcType)) {
|
|
263
|
-
Observer.set(e.detail.src, 'active', false);
|
|
264
|
-
Observer.set(e.detail.submitter || {}, 'active', false);
|
|
265
|
-
}
|
|
266
|
-
if (response.redirected && this.isSameOrigin(response.url)) {
|
|
267
|
-
Observer.set(this.location, { href: response.url }, {
|
|
268
|
-
detail: { isRedirect: true },
|
|
269
|
-
});
|
|
270
|
-
} else {
|
|
339
|
+
if (!response.redirected) {
|
|
271
340
|
let location = response.headers.get('Location');
|
|
272
341
|
if (location && response.status === this._xRedirectCode) {
|
|
342
|
+
response.attrs.status = parseInt(response.headers.get('X-Redirect-Code'));
|
|
273
343
|
Observer.set(this.network, 'redirecting', location);
|
|
274
344
|
window.location = location;
|
|
275
345
|
}
|
|
@@ -277,20 +347,4 @@ export default class Runtime {
|
|
|
277
347
|
return response;
|
|
278
348
|
}
|
|
279
349
|
|
|
280
|
-
/**
|
|
281
|
-
* Checks if an URL is same origin.
|
|
282
|
-
*
|
|
283
|
-
* @param object|string url
|
|
284
|
-
*
|
|
285
|
-
* @return Bool
|
|
286
|
-
*/
|
|
287
|
-
isSameOrigin(url) {
|
|
288
|
-
if (typeof url === 'string') {
|
|
289
|
-
let href = url;
|
|
290
|
-
url = window.document.createElement('a');
|
|
291
|
-
url.href = href
|
|
292
|
-
}
|
|
293
|
-
return !url.origin || url.origin === this.location.origin;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
350
|
}
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @imports
|
|
4
4
|
*/
|
|
5
|
-
import { Observer } from './Runtime.js';
|
|
6
|
-
import WorkerComm from './WorkerComm.js';
|
|
7
5
|
import Router from './Router.js';
|
|
8
6
|
|
|
9
7
|
export default class RuntimeClient {
|
|
@@ -15,12 +13,6 @@ export default class RuntimeClient {
|
|
|
15
13
|
*/
|
|
16
14
|
constructor(cx) {
|
|
17
15
|
this.cx = cx;
|
|
18
|
-
if (this.cx.support_service_worker) {
|
|
19
|
-
const workerComm = new WorkerComm(this.cx.worker_filename, { scope: this.cx.worker_scope, startMessages: true });
|
|
20
|
-
Observer.observe(workerComm, changes => {
|
|
21
|
-
//console.log('SERVICE_WORKER_STATE_CHANGE', changes[0].name, changes[0].value);
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
16
|
}
|
|
25
17
|
|
|
26
18
|
/**
|
|
@@ -39,60 +31,53 @@ export default class RuntimeClient {
|
|
|
39
31
|
// ROUTE FOR DATA
|
|
40
32
|
// --------
|
|
41
33
|
let httpMethodName = httpEvent.request.method.toLowerCase();
|
|
42
|
-
|
|
34
|
+
return router.route([httpMethodName === 'delete' ? 'del' : httpMethodName, 'default'], httpEvent, {}, async event => {
|
|
43
35
|
return remoteFetch(event.request);
|
|
44
36
|
}, remoteFetch);
|
|
45
|
-
if (!(response instanceof httpEvent.Response)) {
|
|
46
|
-
response = new httpEvent.Response(response);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// --------
|
|
50
|
-
// Rendering
|
|
51
|
-
// --------
|
|
52
|
-
if (response.ok && response.headers.contentType === 'application/json') {
|
|
53
|
-
await this.render(httpEvent, response, router);
|
|
54
|
-
await this.scrollIntoView(httpEvent);
|
|
55
|
-
} else if (!response.ok) {
|
|
56
|
-
await this.unrender();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return response;
|
|
60
37
|
};
|
|
61
|
-
|
|
62
38
|
// --------
|
|
63
39
|
// PIPE THROUGH MIDDLEWARES
|
|
64
40
|
// --------
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
41
|
+
return await (this.cx.middlewares || []).concat(handle).reverse().reduce((next, fn) => {
|
|
42
|
+
return () => fn.call(this.cx, httpEvent, router, next);
|
|
43
|
+
}, null)();
|
|
68
44
|
}
|
|
69
45
|
|
|
70
46
|
// Renderer
|
|
71
|
-
async render(httpEvent, response
|
|
47
|
+
async render(httpEvent, response) {
|
|
72
48
|
let data = await response.json();
|
|
49
|
+
const router = new Router(this.cx, httpEvent.url.pathname);
|
|
73
50
|
return router.route('render', httpEvent, data, async (httpEvent, data) => {
|
|
74
51
|
// --------
|
|
75
52
|
// OOHTML would waiting for DOM-ready in order to be initialized
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
window.document.setState({
|
|
79
|
-
env: 'client',
|
|
80
|
-
onHydration: (httpEvent.detail || {}).srcType === 'init',
|
|
81
|
-
network: this.cx.runtime.network,
|
|
82
|
-
url: this.cx.runtime.location,
|
|
83
|
-
}, { update: true });
|
|
53
|
+
if (window.WebQit.DOM) {
|
|
54
|
+
await new Promise(res => window.WebQit.DOM.ready(res));
|
|
84
55
|
}
|
|
85
|
-
window.document.
|
|
86
|
-
|
|
87
|
-
|
|
56
|
+
if (window.document.state) {
|
|
57
|
+
if (!window.document.state.env) {
|
|
58
|
+
window.document.setState({
|
|
59
|
+
env: 'client',
|
|
60
|
+
onHydration: (httpEvent.detail || {}).srcType === 'init',
|
|
61
|
+
network: this.cx.runtime.network,
|
|
62
|
+
url: this.cx.runtime.location,
|
|
63
|
+
}, { update: true });
|
|
64
|
+
}
|
|
65
|
+
window.document.setState({ data }, { update: 'merge' });
|
|
66
|
+
}
|
|
67
|
+
if (window.document.templates) {
|
|
68
|
+
window.document.body.setAttribute('template', 'routes/' + httpEvent.url.pathname.split('/').filter(a => a).map(a => a + '+-').join('/'));
|
|
69
|
+
await new Promise(res => (window.document.templatesReadyState === 'complete' && res(), window.document.addEventListener('templatesreadystatechange', res)));
|
|
70
|
+
}
|
|
71
|
+
await this.scrollIntoView(httpEvent);
|
|
88
72
|
return window;
|
|
89
73
|
});
|
|
90
74
|
}
|
|
91
75
|
|
|
92
76
|
// Unrender
|
|
93
|
-
async unrender() {
|
|
94
|
-
window.document.
|
|
95
|
-
|
|
77
|
+
async unrender(httpEvent) {
|
|
78
|
+
if (window.document.state) {
|
|
79
|
+
window.document.setState({ data: {} }, { update: 'merge' });
|
|
80
|
+
}
|
|
96
81
|
}
|
|
97
82
|
|
|
98
83
|
// Normalize scroll position
|