@webqit/webflo 0.8.72 → 0.8.75
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
|
@@ -45,22 +45,24 @@ export default class Http {
|
|
|
45
45
|
* @return void
|
|
46
46
|
*/
|
|
47
47
|
async go(url, options = {}) {
|
|
48
|
-
if (this.
|
|
49
|
-
this.
|
|
48
|
+
if (this.abortController) {
|
|
49
|
+
this.abortController.abort();
|
|
50
50
|
}
|
|
51
|
-
this.
|
|
51
|
+
this.abortController = new AbortController();
|
|
52
|
+
let xRedirectCode = 300;
|
|
52
53
|
// Generates request object
|
|
53
54
|
let generateRequest = (url, options) => {
|
|
54
55
|
return new StdRequest(url, {
|
|
55
56
|
...options,
|
|
56
57
|
headers: {
|
|
57
58
|
'Accept': 'application/json',
|
|
58
|
-
'X-
|
|
59
|
+
'X-Redirect-Policy': 'manual-when-cross-origin',
|
|
60
|
+
'X-Redirect-Code': xRedirectCode,
|
|
59
61
|
'X-Powered-By': '@webqit/webflo',
|
|
60
62
|
...(options.headers || {}),
|
|
61
63
|
},
|
|
62
64
|
referrer: window.document.location.href,
|
|
63
|
-
signal: this.
|
|
65
|
+
signal: this.abortController.signal,
|
|
64
66
|
});
|
|
65
67
|
};
|
|
66
68
|
// Handles response object
|
|
@@ -70,14 +72,14 @@ export default class Http {
|
|
|
70
72
|
Observer.set(this.location, { href: response.url }, {
|
|
71
73
|
detail: { isRedirect: true },
|
|
72
74
|
});
|
|
73
|
-
} else if (response.headers.get('
|
|
75
|
+
} else if (response.headers.get('Location') && response.status === xRedirectCode) {
|
|
74
76
|
window.location = response.headers.get('Location');
|
|
75
77
|
}
|
|
76
78
|
};
|
|
77
79
|
url = typeof url === 'string' ? { href: url } : url;
|
|
78
|
-
options = { referrer:
|
|
80
|
+
options = { referrer: this.location.href, ...options };
|
|
79
81
|
Observer.set(this.location, url, { detail: options, });
|
|
80
|
-
if (
|
|
82
|
+
if (!(_before(url.href, '#') === _before(options.referrer, '#') && (options.method || 'GET').toUpperCase() === 'GET')) {
|
|
81
83
|
handleResponse(await client.call(this, generateRequest(url.href, options)));
|
|
82
84
|
}
|
|
83
85
|
},
|
|
@@ -108,7 +110,13 @@ export default class Http {
|
|
|
108
110
|
};
|
|
109
111
|
|
|
110
112
|
// -----------------------
|
|
111
|
-
// Initialize
|
|
113
|
+
// Initialize network
|
|
114
|
+
Observer.set(instance, 'network', {});
|
|
115
|
+
window.addEventListener('online', () => Observer.set(instance.network, 'online', navigator.onLine));
|
|
116
|
+
window.addEventListener('offline', () => Observer.set(instance.network, 'online', navigator.onLine));
|
|
117
|
+
|
|
118
|
+
// -----------------------
|
|
119
|
+
// Initialize location
|
|
112
120
|
Observer.set(instance, 'location', new Url(window.document.location));
|
|
113
121
|
// -----------------------
|
|
114
122
|
// Syndicate changes to the browser;s location bar
|
|
@@ -169,14 +177,14 @@ export default class Http {
|
|
|
169
177
|
// Capture all form-submit
|
|
170
178
|
// and fire to this router.
|
|
171
179
|
window.addEventListener('submit', e => {
|
|
172
|
-
var form = e.target.closest('form'),
|
|
173
|
-
submits = [e.submitter]; //_arrFrom(form.elements).filter(el => el.matches('button,input[type="submit"],input[type="image"]'));
|
|
180
|
+
var form = e.target.closest('form'), submitter = e.submitter;
|
|
174
181
|
var submitParams = [ 'action', 'enctype', 'method', 'noValidate', 'target' ].reduce((params, prop) => {
|
|
175
|
-
params[prop] =
|
|
182
|
+
params[prop] = submitter && submitter.hasAttribute(`form${prop.toLowerCase()}`) ? submitter[`form${_toTitle(prop)}`] : form[prop];
|
|
176
183
|
return params;
|
|
177
184
|
}, {});
|
|
178
185
|
// We support method hacking
|
|
179
|
-
submitParams.method =
|
|
186
|
+
submitParams.method = (submitter && submitter.dataset.method) || form.dataset.method || submitParams.method;
|
|
187
|
+
submitParams.submitter = submitter;
|
|
180
188
|
// ---------------
|
|
181
189
|
var actionEl = window.document.createElement('a');
|
|
182
190
|
actionEl.href = submitParams.action;
|
|
@@ -186,8 +194,8 @@ export default class Http {
|
|
|
186
194
|
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
|
|
187
195
|
// Build data
|
|
188
196
|
var formData = new FormData(form);
|
|
189
|
-
if (
|
|
190
|
-
formData.set(
|
|
197
|
+
if ((submitter || {}).name) {
|
|
198
|
+
formData.set(submitter.name, submitter.value);
|
|
191
199
|
}
|
|
192
200
|
if (submitParams.method.toUpperCase() === 'GET') {
|
|
193
201
|
var query = wwwFormUnserialize(actionEl.search);
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @imports
|
|
4
|
+
*/
|
|
5
|
+
import _before from '@webqit/util/str/before.js';
|
|
6
|
+
import _toTitle from '@webqit/util/str/toTitle.js';
|
|
7
|
+
import { wwwFormUnserialize, wwwFormSet, wwwFormSerialize } from '../util.js';
|
|
8
|
+
import { Observer } from './Runtime.js';
|
|
9
|
+
import Url from './Url.js';
|
|
10
|
+
|
|
11
|
+
export default class Navigator {
|
|
12
|
+
|
|
13
|
+
constructor(client) {
|
|
14
|
+
this.client = client;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ----------------
|
|
18
|
+
* Navigator location
|
|
19
|
+
* ----------------
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// -----------------------
|
|
23
|
+
// Initialize location
|
|
24
|
+
Observer.set(this, 'location', new Url(window.document.location));
|
|
25
|
+
// -----------------------
|
|
26
|
+
// Syndicate changes to the browser;s location bar
|
|
27
|
+
Observer.observe(this.location, [[ 'href' ]], ([e]) => {
|
|
28
|
+
if (e.value === 'http:' || (e.detail || {}).src === window.document.location) {
|
|
29
|
+
// Already from a "popstate" event as above, so don't push again
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (e.value === window.document.location.href || e.value + '/' === window.document.location.href) {
|
|
33
|
+
window.history.replaceState(window.history.state, '', this.location.href);
|
|
34
|
+
} else {
|
|
35
|
+
try { window.history.pushState(window.history.state, '', this.location.href); } catch(e) {}
|
|
36
|
+
}
|
|
37
|
+
}, { diff: true });
|
|
38
|
+
|
|
39
|
+
// -----------------------
|
|
40
|
+
// This event is triggered by
|
|
41
|
+
// either the browser back button,
|
|
42
|
+
// the window.history.back(),
|
|
43
|
+
// the window.history.forward(),
|
|
44
|
+
// or the window.history.go() action.
|
|
45
|
+
window.addEventListener('popstate', e => {
|
|
46
|
+
// Needed to allow window.document.location
|
|
47
|
+
// to update to window.location
|
|
48
|
+
window.setTimeout(() => {
|
|
49
|
+
this.go(Url.copy(window.document.location), { src: window.document.location, srcType: 'history', });
|
|
50
|
+
}, 0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// -----------------------
|
|
54
|
+
// Capture all link-clicks
|
|
55
|
+
// and fire to this router.
|
|
56
|
+
window.addEventListener('click', e => {
|
|
57
|
+
var anchor = e.target.closest('a');
|
|
58
|
+
if (!anchor || !anchor.href) return;
|
|
59
|
+
if (!anchor.target && !anchor.download && (!anchor.origin || anchor.origin === this.location.origin)) {
|
|
60
|
+
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
|
|
61
|
+
// Publish everything, including hash
|
|
62
|
+
this.go(Url.copy(anchor), { src: anchor, srcType: 'link', });
|
|
63
|
+
// URLs with # will cause a natural navigation
|
|
64
|
+
// even if pointing to a different page, a natural navigation will still happen
|
|
65
|
+
// because with the Observer.set() above, window.document.location.href would have become
|
|
66
|
+
// the destination page, which makes it look like same page navigation
|
|
67
|
+
if (!anchor.href.includes('#')) {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// -----------------------
|
|
74
|
+
// Capture all form-submit
|
|
75
|
+
// and fire to this router.
|
|
76
|
+
window.addEventListener('submit', e => {
|
|
77
|
+
var form = e.target.closest('form'), submitter = e.submitter;
|
|
78
|
+
var submitParams = [ 'action', 'enctype', 'method', 'noValidate', 'target' ].reduce((params, prop) => {
|
|
79
|
+
params[prop] = submitter && submitter.hasAttribute(`form${prop.toLowerCase()}`) ? submitter[`form${_toTitle(prop)}`] : form[prop];
|
|
80
|
+
return params;
|
|
81
|
+
}, {});
|
|
82
|
+
// We support method hacking
|
|
83
|
+
submitParams.method = (submitter && submitter.dataset.method) || form.dataset.method || submitParams.method;
|
|
84
|
+
submitParams.submitter = submitter;
|
|
85
|
+
// ---------------
|
|
86
|
+
var actionEl = window.document.createElement('a');
|
|
87
|
+
actionEl.href = submitParams.action;
|
|
88
|
+
// ---------------
|
|
89
|
+
// If not targeted and same origin...
|
|
90
|
+
if (!submitParams.target && (!actionEl.origin || actionEl.origin === this.location.origin)) {
|
|
91
|
+
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
|
|
92
|
+
// Build data
|
|
93
|
+
var formData = new FormData(form);
|
|
94
|
+
if ((submitter || {}).name) {
|
|
95
|
+
formData.set(submitter.name, submitter.value);
|
|
96
|
+
}
|
|
97
|
+
if (submitParams.method.toUpperCase() === 'GET') {
|
|
98
|
+
var query = wwwFormUnserialize(actionEl.search);
|
|
99
|
+
Array.from(formData.entries()).forEach(_entry => {
|
|
100
|
+
wwwFormSet(query, _entry[0], _entry[1], false);
|
|
101
|
+
});
|
|
102
|
+
actionEl.search = wwwFormSerialize(query);
|
|
103
|
+
formData = null;
|
|
104
|
+
}
|
|
105
|
+
this.go(Url.copy(actionEl), { ...submitParams, body: formData, src: form, srcType: 'form', });
|
|
106
|
+
// URLs with # will cause a natural navigation
|
|
107
|
+
// even if pointing to a different page, a natural navigation will still happen
|
|
108
|
+
// because with the Observer.set() above, window.document.location.href would have become
|
|
109
|
+
// the destination page, which makes it look like same page navigation
|
|
110
|
+
if (!actionEl.hash) {
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* ----------------
|
|
118
|
+
* Navigator network
|
|
119
|
+
* ----------------
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
// -----------------------
|
|
123
|
+
// Initialize network
|
|
124
|
+
Observer.set(this, 'network', {});
|
|
125
|
+
window.addEventListener('online', () => Observer.set(this.network, 'online', navigator.onLine));
|
|
126
|
+
window.addEventListener('offline', () => Observer.set(this.network, 'online', navigator.onLine));
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* ----------------
|
|
130
|
+
* Initial navigation
|
|
131
|
+
* ----------------
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
this.go(this.location, { srcType: 'init' });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* History object
|
|
139
|
+
*/
|
|
140
|
+
get history() {
|
|
141
|
+
return window.history;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Performs a request.
|
|
146
|
+
*
|
|
147
|
+
* @param object|string href
|
|
148
|
+
* @param object params
|
|
149
|
+
*
|
|
150
|
+
* @return void
|
|
151
|
+
*/
|
|
152
|
+
async go(url, params = {}) {
|
|
153
|
+
|
|
154
|
+
// Generates request object
|
|
155
|
+
const generateRequest = (url, params) => {
|
|
156
|
+
return new Request(url, {
|
|
157
|
+
...params,
|
|
158
|
+
headers: {
|
|
159
|
+
'Accept': 'application/json',
|
|
160
|
+
'Cache-Control': 'no-store',
|
|
161
|
+
'X-Redirect-Policy': 'manual-when-cross-origin',
|
|
162
|
+
'X-Redirect-Code': xRedirectCode,
|
|
163
|
+
'X-Powered-By': '@webqit/webflo',
|
|
164
|
+
...(params.headers || {}),
|
|
165
|
+
},
|
|
166
|
+
referrer: window.document.location.href,
|
|
167
|
+
signal: this._abortController.signal,
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Initiates remote fetch and sets the status
|
|
172
|
+
const remoteRequest = request => {
|
|
173
|
+
Observer.set(this.network, 'remote', true);
|
|
174
|
+
let _response = fetch(request);
|
|
175
|
+
// This catch() is NOT intended to handle failure of the fetch
|
|
176
|
+
_response.catch(e => Observer.set(this.network, 'error', e.message));
|
|
177
|
+
// Save a reference to this
|
|
178
|
+
return _response.then(async response => {
|
|
179
|
+
// Stop loading status
|
|
180
|
+
Observer.set(this.network, 'remote', false);
|
|
181
|
+
return response;
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Handles response object
|
|
186
|
+
const handleResponse = async (response, params) => {
|
|
187
|
+
response = await response;
|
|
188
|
+
Observer.set(this.network, 'remote', false);
|
|
189
|
+
Observer.set(this.network, 'error', null);
|
|
190
|
+
if (['link', 'form'].includes(params.srcType)) {
|
|
191
|
+
Observer.set(params.src, 'active', false);
|
|
192
|
+
Observer.set(params.submitter || {}, 'active', false);
|
|
193
|
+
}
|
|
194
|
+
if (!response) return;
|
|
195
|
+
if (response.redirected && this.isSameOrigin(response.url)) {
|
|
196
|
+
Observer.set(this.location, { href: response.url }, {
|
|
197
|
+
detail: { isRedirect: true },
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
let location = response.headers.get('Location');
|
|
202
|
+
if (location && response.status === xRedirectCode) {
|
|
203
|
+
Observer.set(this.network, 'redirecting', location);
|
|
204
|
+
window.location = location;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// ------------
|
|
209
|
+
url = typeof url === 'string' ? { href: url } : url;
|
|
210
|
+
params = { referrer: this.location.href, ...params };
|
|
211
|
+
// ------------
|
|
212
|
+
Observer.set(this.location, url, { detail: params, });
|
|
213
|
+
Observer.set(this.network, 'redirecting', null);
|
|
214
|
+
// ------------
|
|
215
|
+
if (['link', 'form'].includes(params.srcType)) {
|
|
216
|
+
Observer.set(params.src, 'active', true);
|
|
217
|
+
Observer.set(params.submitter || {}, 'active', true);
|
|
218
|
+
}
|
|
219
|
+
// ------------
|
|
220
|
+
|
|
221
|
+
if (this._abortController) {
|
|
222
|
+
this._abortController.abort();
|
|
223
|
+
}
|
|
224
|
+
this._abortController = new AbortController();
|
|
225
|
+
let xRedirectCode = 300;
|
|
226
|
+
|
|
227
|
+
if (params.srcType === 'init' || !(_before(url.href, '#') === _before(params.referrer, '#') && (params.method || 'GET').toUpperCase() === 'GET')) {
|
|
228
|
+
handleResponse(this.client.call(this, generateRequest(url.href, params), params, remoteRequest), params);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return this._abortController;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Checks if an URL is same origin.
|
|
236
|
+
*
|
|
237
|
+
* @param object|string url
|
|
238
|
+
*
|
|
239
|
+
* @return Bool
|
|
240
|
+
*/
|
|
241
|
+
isSameOrigin(url) {
|
|
242
|
+
if (typeof url === 'string') {
|
|
243
|
+
let href = url;
|
|
244
|
+
url = window.document.createElement('a');
|
|
245
|
+
url.href = href
|
|
246
|
+
}
|
|
247
|
+
return !url.origin || url.origin === this.location.origin;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
}
|
|
@@ -10,7 +10,7 @@ import NavigationEvent from './NavigationEvent.js';
|
|
|
10
10
|
import WorkerClient from './WorkerClient.js';
|
|
11
11
|
import Storage from './Storage.js';
|
|
12
12
|
import Router from './Router.js';
|
|
13
|
-
import
|
|
13
|
+
import Navigator from './Navigator.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* ---------------------------
|
|
@@ -21,184 +21,106 @@ import Http from './Http.js';
|
|
|
21
21
|
export const { Observer } = window.WebQit;
|
|
22
22
|
export default function(layout, params) {
|
|
23
23
|
|
|
24
|
-
const session = Storage();
|
|
25
|
-
const workerClient = new WorkerClient('/worker.js', { startMessages: true });
|
|
26
|
-
Observer.observe(workerClient, changes => {
|
|
27
|
-
console.log('SERVICE_WORKER_STATE', changes[0].name, changes[0].value);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// Copy..
|
|
31
24
|
layout = {...layout};
|
|
32
25
|
params = {...params};
|
|
33
|
-
window.addEventListener('online', () => Observer.set(networkWatch, 'online', navigator.onLine));
|
|
34
|
-
window.addEventListener('offline', () => Observer.set(networkWatch, 'online', navigator.onLine));
|
|
35
|
-
var networkProgressOngoing;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* ----------------
|
|
39
|
-
* Apply routing
|
|
40
|
-
* ----------------
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
Http.createClient(async function(request, event = null) {
|
|
44
26
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// Resolve canonicity
|
|
49
|
-
// -------------------
|
|
50
|
-
|
|
51
|
-
// The srvice object
|
|
52
|
-
const $context = {
|
|
53
|
-
layout,
|
|
54
|
-
onHydration: !event && (await window.WebQit.OOHTML.meta.get('isomorphic')),
|
|
55
|
-
response: null,
|
|
56
|
-
}
|
|
27
|
+
const session = Storage();
|
|
28
|
+
const workerClient = new WorkerClient('/worker.js', { startMessages: true });
|
|
29
|
+
const navigator = new Navigator(async (request, params, remoteFetch) => {
|
|
57
30
|
|
|
58
|
-
// The
|
|
59
|
-
const clientNavigationEvent = new NavigationEvent(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
networkProgressOngoing.setActive(false);
|
|
64
|
-
networkProgressOngoing = null;
|
|
65
|
-
}
|
|
31
|
+
// The navigation event
|
|
32
|
+
const clientNavigationEvent = new NavigationEvent(
|
|
33
|
+
new NavigationEvent.Request(request),
|
|
34
|
+
session,
|
|
35
|
+
);
|
|
66
36
|
|
|
67
|
-
|
|
37
|
+
// The app router
|
|
38
|
+
const router = new Router(clientNavigationEvent.url.pathname, layout, {
|
|
39
|
+
layout,
|
|
40
|
+
onHydration: params.srcType === 'init',
|
|
41
|
+
});
|
|
68
42
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
statusText: response.statusText,
|
|
85
|
-
headers: response.headers,
|
|
86
|
-
_proxy: {
|
|
87
|
-
url: response.url,
|
|
88
|
-
ok: response.ok,
|
|
89
|
-
redirected: response.redirected
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
if (response.headers.get('Location')) {
|
|
93
|
-
networkProgress.redirecting(response.headers.get('Location'));
|
|
94
|
-
}
|
|
95
|
-
// Stop loading status
|
|
96
|
-
networkProgress.setActive(false);
|
|
97
|
-
return response;
|
|
43
|
+
// --------
|
|
44
|
+
// ROUTE FOR DATA
|
|
45
|
+
// --------
|
|
46
|
+
const httpMethodName = clientNavigationEvent.request.method.toLowerCase();
|
|
47
|
+
const response = await router.route([httpMethodName === 'delete' ? 'del' : httpMethodName, 'default'], clientNavigationEvent, document.state, async function(event) {
|
|
48
|
+
return remoteFetch(event.request).then(response => {
|
|
49
|
+
return new clientNavigationEvent.Response(response.body, {
|
|
50
|
+
status: response.status,
|
|
51
|
+
statusText: response.statusText,
|
|
52
|
+
headers: response.headers,
|
|
53
|
+
_proxy: {
|
|
54
|
+
url: response.url,
|
|
55
|
+
ok: response.ok,
|
|
56
|
+
redirected: response.redirected
|
|
57
|
+
},
|
|
98
58
|
});
|
|
99
59
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
60
|
+
}).catch(e => {
|
|
61
|
+
window.document.body.setAttribute('template', '');
|
|
62
|
+
throw e;
|
|
63
|
+
});
|
|
105
64
|
|
|
106
|
-
// --------
|
|
107
|
-
// Render
|
|
108
|
-
// --------
|
|
109
|
-
const rendering = await router.route('render', clientNavigationEvent, $context.data, async function(event, data) {
|
|
110
|
-
// --------
|
|
111
|
-
// OOHTML would waiting for DOM-ready in order to be initialized
|
|
112
|
-
await new Promise(res => window.WebQit.DOM.ready(res));
|
|
113
|
-
if (!window.document.state.env) {
|
|
114
|
-
window.document.setState({
|
|
115
|
-
env: 'client',
|
|
116
|
-
onHydration: $context.onHydration,
|
|
117
|
-
network: networkWatch,
|
|
118
|
-
url: httpInstance.location,
|
|
119
|
-
session,
|
|
120
|
-
}, { update: true });
|
|
121
|
-
}
|
|
122
|
-
window.document.setState({ page: data }, { update: 'merge' });
|
|
123
|
-
window.document.body.setAttribute('template', 'page/' + requestPath.split('/').filter(a => a).map(a => a + '+-').join('/'));
|
|
124
|
-
return new Promise(res => {
|
|
125
|
-
window.document.addEventListener('templatesreadystatechange', () => res(window));
|
|
126
|
-
if (window.document.templatesReadyState === 'complete') {
|
|
127
|
-
res(window);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
65
|
|
|
66
|
+
// --------
|
|
67
|
+
// Render
|
|
68
|
+
// --------
|
|
69
|
+
const data = response instanceof clientNavigationEvent.Response ? await response.data() : response;
|
|
70
|
+
await router.route('render', clientNavigationEvent, data, async function(event, data) {
|
|
132
71
|
// --------
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
} else {
|
|
144
|
-
document.documentElement.classList.add('scroll-reset');
|
|
145
|
-
document.body.scrollIntoView();
|
|
146
|
-
setTimeout(() => {
|
|
147
|
-
document.documentElement.classList.remove('scroll-reset');
|
|
148
|
-
}, 600);
|
|
149
|
-
}
|
|
150
|
-
}, 0);
|
|
72
|
+
// OOHTML would waiting for DOM-ready in order to be initialized
|
|
73
|
+
await new Promise(res => window.WebQit.DOM.ready(res));
|
|
74
|
+
if (!window.document.state.env) {
|
|
75
|
+
window.document.setState({
|
|
76
|
+
env: 'client',
|
|
77
|
+
onHydration: params.srcType === 'init',
|
|
78
|
+
network: navigator.network,
|
|
79
|
+
url: navigator.location,
|
|
80
|
+
session,
|
|
81
|
+
}, { update: true });
|
|
151
82
|
}
|
|
83
|
+
window.document.setState({ page: data }, { update: 'merge' });
|
|
84
|
+
window.document.body.setAttribute('template', 'page/' + clientNavigationEvent.url.pathname.split('/').filter(a => a).map(a => a + '+-').join('/'));
|
|
85
|
+
return new Promise(res => {
|
|
86
|
+
window.document.addEventListener('templatesreadystatechange', () => res(window));
|
|
87
|
+
if (window.document.templatesReadyState === 'complete') {
|
|
88
|
+
res(window);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
152
92
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
93
|
+
// --------
|
|
94
|
+
// Render...
|
|
95
|
+
// --------
|
|
96
|
+
|
|
97
|
+
if (params.src instanceof Element) {
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
let viewportTop;
|
|
100
|
+
if (clientNavigationEvent.url.hash && (urlTarget = document.querySelector(clientNavigationEvent.url.hash))) {
|
|
101
|
+
urlTarget.scrollIntoView();
|
|
102
|
+
} else if (viewportTop = Array.from(document.querySelectorAll('[data-viewport-top]')).pop()) {
|
|
103
|
+
viewportTop.focus();
|
|
104
|
+
} else {
|
|
105
|
+
document.documentElement.classList.add('scroll-reset');
|
|
106
|
+
document.body.scrollIntoView();
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
document.documentElement.classList.remove('scroll-reset');
|
|
109
|
+
}, 600);
|
|
110
|
+
}
|
|
111
|
+
}, 0);
|
|
158
112
|
}
|
|
159
113
|
|
|
160
|
-
return
|
|
114
|
+
return response;
|
|
161
115
|
});
|
|
162
116
|
|
|
117
|
+
Observer.observe(session, changes => {
|
|
118
|
+
//console.log('SESSION_STATE_CHANGE', changes[0].name, changes[0].value);
|
|
119
|
+
});
|
|
120
|
+
Observer.observe(workerClient, changes => {
|
|
121
|
+
//console.log('SERVICE_WORKER_STATE_CHANGE', changes[0].name, changes[0].value);
|
|
122
|
+
});
|
|
123
|
+
Observer.observe(navigator, changes => {
|
|
124
|
+
//console.log('NAVIGATORSTATE_CHANGE', changes[0].name, changes[0].value);
|
|
125
|
+
});
|
|
163
126
|
};
|
|
164
|
-
|
|
165
|
-
const networkWatch = { progress: {}, online: navigator.onLine };
|
|
166
|
-
class RequestHandle {
|
|
167
|
-
setActive(state, method = '') {
|
|
168
|
-
if (this.active === false) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
this.active = state;
|
|
172
|
-
Observer.set(networkWatch, {
|
|
173
|
-
method,
|
|
174
|
-
error: '',
|
|
175
|
-
progress: {
|
|
176
|
-
active: state,
|
|
177
|
-
determinate: false,
|
|
178
|
-
valuenow: 0,
|
|
179
|
-
valuetotal: NaN,
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
updateProgress(phase, valuenow, valuetotal) {
|
|
184
|
-
if (this.active === false) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
Observer.set(networkWatch.progress, {
|
|
188
|
-
phase,
|
|
189
|
-
determinate: !isNaN(valuetotal),
|
|
190
|
-
valuenow,
|
|
191
|
-
valuetotal,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
redirecting(location) {
|
|
195
|
-
Observer.set(networkWatch, 'redirecting', location);
|
|
196
|
-
}
|
|
197
|
-
throw(message) {
|
|
198
|
-
if (this.active === false) {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
this.error = true;
|
|
202
|
-
Observer.set(networkWatch, 'error', message);
|
|
203
|
-
}
|
|
204
|
-
};
|
|
@@ -208,7 +208,6 @@ export async function run(hostSetup, request, response, Ui, flags = {}, protocol
|
|
|
208
208
|
// --------
|
|
209
209
|
|
|
210
210
|
const fullUrl = protocol + '://' + request.headers.host + request.url;
|
|
211
|
-
const _request = new NavigationEvent.Request(fullUrl, requestInit);
|
|
212
211
|
const _sessionFactory = function(id, params = {}, callback = null) {
|
|
213
212
|
let factory, secret = hostSetup.variables.entries.SESSION_KEY;
|
|
214
213
|
Sessions({
|
|
@@ -229,8 +228,8 @@ export async function run(hostSetup, request, response, Ui, flags = {}, protocol
|
|
|
229
228
|
return !callback ? factory : undefined;
|
|
230
229
|
};
|
|
231
230
|
const serverNavigationEvent = new NavigationEvent(
|
|
232
|
-
|
|
233
|
-
_sessionFactory('_session', {duration: 60 * 60
|
|
231
|
+
new NavigationEvent.Request(fullUrl, requestInit),
|
|
232
|
+
_sessionFactory('_session', { duration: 60 * 60, activeDuration: 60 * 60 }).get(),
|
|
234
233
|
_sessionFactory
|
|
235
234
|
);
|
|
236
235
|
|
|
@@ -464,15 +463,17 @@ export async function run(hostSetup, request, response, Ui, flags = {}, protocol
|
|
|
464
463
|
// Send
|
|
465
464
|
// -------------------
|
|
466
465
|
if ($context.response.headers.redirect) {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
response.
|
|
466
|
+
let xRedirectPolicy = serverNavigationEvent.request.headers.get('X-Redirect-Policy');
|
|
467
|
+
let xRedirectCode = serverNavigationEvent.request.headers.get('X-Redirect-Code') || 300;
|
|
468
|
+
let isSameOriginRedirect = (new serverNavigationEvent.globals.URL($context.response.headers.location)).origin === serverNavigationEvent.url.origin;
|
|
469
|
+
if (xRedirectPolicy === 'manual' || (!isSameOriginRedirect && xRedirectPolicy === 'manual-when-cross-origin') || (isSameOriginRedirect && xRedirectPolicy === 'manual-when-same-origin')) {
|
|
470
|
+
response.statusCode = xRedirectCode;
|
|
471
|
+
response.setHeader('X-Redirect-Code', $context.response.status);
|
|
472
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
472
473
|
} else {
|
|
473
474
|
response.statusCode = $context.response.status;
|
|
474
|
-
response.end();
|
|
475
475
|
}
|
|
476
|
+
response.end();
|
|
476
477
|
} else if ($context.response.original !== undefined && $context.response.original !== null) {
|
|
477
478
|
response.statusCode = $context.response.status;
|
|
478
479
|
response.statusMessage = $context.response.statusText;
|
|
@@ -553,9 +554,9 @@ export async function run(hostSetup, request, response, Ui, flags = {}, protocol
|
|
|
553
554
|
|
|
554
555
|
if (flags.logs !== false) {
|
|
555
556
|
let errorCode = [ 404, 500 ].includes(response.statusCode) ? response.statusCode : 0;
|
|
556
|
-
let
|
|
557
|
-
let redirectCode =
|
|
558
|
-
let statusCode =
|
|
557
|
+
let xRedirectCode = response.getHeader('X-Redirect-Code');
|
|
558
|
+
let redirectCode = xRedirectCode || ((response.statusCode + '').startsWith('3') ? response.statusCode : 0);
|
|
559
|
+
let statusCode = xRedirectCode || response.statusCode;
|
|
559
560
|
Ui.log(''
|
|
560
561
|
+ '[' + (hostSetup.vh ? Ui.style.keyword(hostSetup.vh.host) + '][' : '') + Ui.style.comment((new Date).toUTCString()) + '] '
|
|
561
562
|
+ Ui.style.keyword(protocol.toUpperCase() + ' ' + serverNavigationEvent.request.method) + ' '
|