@webqit/webflo 0.11.61 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/{Context.js → AbstractContext.js} +1 -9
- package/src/deployment-pi/origins/index.js +1 -1
- package/src/index.js +1 -9
- package/src/runtime-pi/HttpEvent.js +101 -81
- package/src/runtime-pi/HttpUser.js +126 -0
- package/src/runtime-pi/MessagingOverBroadcast.js +9 -0
- package/src/runtime-pi/MessagingOverChannel.js +85 -0
- package/src/runtime-pi/MessagingOverSocket.js +106 -0
- package/src/runtime-pi/MultiportMessagingAPI.js +81 -0
- package/src/runtime-pi/WebfloCookieStorage.js +27 -0
- package/src/runtime-pi/WebfloEventTarget.js +39 -0
- package/src/runtime-pi/WebfloMessageEvent.js +58 -0
- package/src/runtime-pi/WebfloMessagingAPI.js +69 -0
- package/src/runtime-pi/{Router.js → WebfloRouter.js} +3 -34
- package/src/runtime-pi/WebfloRuntime.js +52 -0
- package/src/runtime-pi/WebfloStorage.js +109 -0
- package/src/runtime-pi/client/ClientMessaging.js +5 -0
- package/src/runtime-pi/client/Context.js +2 -6
- package/src/runtime-pi/client/CookieStorage.js +17 -0
- package/src/runtime-pi/client/Router.js +3 -13
- package/src/runtime-pi/client/SessionStorage.js +33 -0
- package/src/runtime-pi/client/Url.js +24 -72
- package/src/runtime-pi/client/WebfloClient.js +544 -0
- package/src/runtime-pi/client/WebfloRootClient1.js +179 -0
- package/src/runtime-pi/client/WebfloRootClient2.js +109 -0
- package/src/runtime-pi/client/WebfloSubClient.js +165 -0
- package/src/runtime-pi/client/Workport.js +89 -161
- package/src/runtime-pi/client/generate.js +1 -1
- package/src/runtime-pi/client/index.js +13 -18
- package/src/runtime-pi/client/worker/ClientMessaging.js +5 -0
- package/src/runtime-pi/client/worker/Context.js +2 -6
- package/src/runtime-pi/client/worker/CookieStorage.js +17 -0
- package/src/runtime-pi/client/worker/SessionStorage.js +13 -0
- package/src/runtime-pi/client/worker/WebfloWorker.js +294 -0
- package/src/runtime-pi/client/worker/Workport.js +13 -73
- package/src/runtime-pi/client/worker/index.js +7 -18
- package/src/runtime-pi/index.js +1 -8
- package/src/runtime-pi/server/ClientMessaging.js +18 -0
- package/src/runtime-pi/server/ClientMessagingRegistry.js +57 -0
- package/src/runtime-pi/server/Context.js +2 -6
- package/src/runtime-pi/server/CookieStorage.js +17 -0
- package/src/runtime-pi/server/Router.js +2 -68
- package/src/runtime-pi/server/SessionStorage.js +53 -0
- package/src/runtime-pi/server/WebfloServer.js +755 -0
- package/src/runtime-pi/server/index.js +7 -18
- package/src/runtime-pi/util-http.js +268 -32
- package/src/runtime-pi/xURL.js +25 -22
- package/src/runtime-pi/xfetch.js +2 -2
- package/src/runtime-pi/Application.js +0 -29
- package/src/runtime-pi/Cookies.js +0 -82
- package/src/runtime-pi/Runtime.js +0 -21
- package/src/runtime-pi/client/Application.js +0 -76
- package/src/runtime-pi/client/Runtime.js +0 -525
- package/src/runtime-pi/client/createStorage.js +0 -58
- package/src/runtime-pi/client/worker/Application.js +0 -44
- package/src/runtime-pi/client/worker/Runtime.js +0 -275
- package/src/runtime-pi/server/Application.js +0 -101
- package/src/runtime-pi/server/Runtime.js +0 -558
- package/src/runtime-pi/xFormData.js +0 -24
- package/src/runtime-pi/xHeaders.js +0 -146
- package/src/runtime-pi/xRequest.js +0 -46
- package/src/runtime-pi/xRequestHeaders.js +0 -109
- package/src/runtime-pi/xResponse.js +0 -33
- package/src/runtime-pi/xResponseHeaders.js +0 -117
- package/src/runtime-pi/xxHttpMessage.js +0 -102
|
@@ -1,525 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @imports
|
|
4
|
-
*/
|
|
5
|
-
import { _before, _toTitle } from '@webqit/util/str/index.js';
|
|
6
|
-
import createStorage from './createStorage.js';
|
|
7
|
-
import Url from './Url.js';
|
|
8
|
-
import Workport from './Workport.js';
|
|
9
|
-
import xRequest from "../xRequest.js";
|
|
10
|
-
import xResponse from "../xResponse.js";
|
|
11
|
-
import xfetch from '../xfetch.js';
|
|
12
|
-
import HttpEvent from '../HttpEvent.js';
|
|
13
|
-
import _Runtime from '../Runtime.js';
|
|
14
|
-
import { params } from '../util-url.js';
|
|
15
|
-
|
|
16
|
-
const { Observer } = webqit;
|
|
17
|
-
|
|
18
|
-
export {
|
|
19
|
-
HttpEvent,
|
|
20
|
-
Observer,
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export default class Runtime extends _Runtime {
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Runtime
|
|
27
|
-
*
|
|
28
|
-
* @param Object cx
|
|
29
|
-
* @param Function applicationInstance
|
|
30
|
-
*
|
|
31
|
-
* @return void
|
|
32
|
-
*/
|
|
33
|
-
constructor(cx, applicationInstance) {
|
|
34
|
-
super(cx, applicationInstance);
|
|
35
|
-
// -----------------------
|
|
36
|
-
// Initialize location
|
|
37
|
-
Observer.set(this, 'location', new Url(window.document.location));
|
|
38
|
-
Observer.set(this, 'network', {});
|
|
39
|
-
window.addEventListener('online', () => Observer.set(this.network, 'connectivity', 'online'));
|
|
40
|
-
window.addEventListener('offline', () => Observer.set(this.network, 'connectivity', 'offline'));
|
|
41
|
-
this.useNavigationAPI = window.navigation;
|
|
42
|
-
// -----------------------
|
|
43
|
-
// Initialise API
|
|
44
|
-
if (this.useNavigationAPI) {
|
|
45
|
-
this.home = window.navigation.currentEntry;
|
|
46
|
-
this._initNavigationAPI();
|
|
47
|
-
} else { this._initLegacyAPI(); }
|
|
48
|
-
// -----------------------
|
|
49
|
-
// Service Worker && COMM
|
|
50
|
-
if (this.cx.params.service_worker?.filename) {
|
|
51
|
-
const { public_base_url: base, service_worker: { filename, ...serviceWorkerParams }, env } = this.cx.params;
|
|
52
|
-
const workport = new Workport(base + filename, { ...serviceWorkerParams, startMessages: true }, env);
|
|
53
|
-
Observer.set(this, 'workport', workport);
|
|
54
|
-
}
|
|
55
|
-
// -----------------------
|
|
56
|
-
// Initialize and Hydration
|
|
57
|
-
(async () => {
|
|
58
|
-
let shouldHydrate;
|
|
59
|
-
if (this.app.init) {
|
|
60
|
-
const request = this.generateRequest(this.location);
|
|
61
|
-
const httpEvent = new HttpEvent(request, { navigationType: 'init' }, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
|
|
62
|
-
shouldHydrate = await this.app.init(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
|
|
63
|
-
}
|
|
64
|
-
if (shouldHydrate !== false) {
|
|
65
|
-
this.go(this.location, {}, { navigationType: 'startup', navigationOrigins: [], });
|
|
66
|
-
}
|
|
67
|
-
})();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
_initLegacyAPI() {
|
|
71
|
-
const updateLocation = (navigationOrigins, newHref) => {
|
|
72
|
-
const scrollContainer = this._getScrollContainer();
|
|
73
|
-
try { window.history.replaceState({
|
|
74
|
-
...(this.currentEntry().getState() || {}),
|
|
75
|
-
navigationOrigins: this._serializeOrigins(navigationOrigins),
|
|
76
|
-
scrollPosition: scrollContainer === window ? [] : [ scrollContainer.scrollLeft, scrollContainer.scrollTop, ],
|
|
77
|
-
}, '', window.location.href); } catch(e) {}
|
|
78
|
-
try { window.history.pushState({}, '', newHref); } catch(e) {}
|
|
79
|
-
};
|
|
80
|
-
// -----------------------
|
|
81
|
-
// Capture all link-clicks
|
|
82
|
-
// and fire to this router.
|
|
83
|
-
window.addEventListener('click', e => {
|
|
84
|
-
if (!this._canIntercept(e)) return;
|
|
85
|
-
var anchorEl = e.target.closest('a');
|
|
86
|
-
if (!anchorEl || !anchorEl.href || anchorEl.target || anchorEl.download || !this.isSpaRoute(anchorEl)) return;
|
|
87
|
-
if (this.isHashChange(anchorEl)) {
|
|
88
|
-
Observer.set(this.location, 'href', anchorEl.href);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
// ---------------
|
|
92
|
-
// Handle now
|
|
93
|
-
e.preventDefault();
|
|
94
|
-
this._abortController?.abort();
|
|
95
|
-
this._abortController = new AbortController();
|
|
96
|
-
// Note the order of calls below
|
|
97
|
-
const detail = {
|
|
98
|
-
navigationType: 'push',
|
|
99
|
-
navigationOrigins: [ anchorEl ],
|
|
100
|
-
destination: this._asEntry(null),
|
|
101
|
-
source: this.currentEntry(), // this
|
|
102
|
-
userInitiated: true,
|
|
103
|
-
};
|
|
104
|
-
updateLocation([ anchorEl ], anchorEl.href); // this
|
|
105
|
-
this.go(
|
|
106
|
-
Url.copy(anchorEl),
|
|
107
|
-
{ signal: this._abortController.signal, },
|
|
108
|
-
detail,
|
|
109
|
-
); // this
|
|
110
|
-
});
|
|
111
|
-
// -----------------------
|
|
112
|
-
// Capture all form-submit
|
|
113
|
-
// and fire to this router.
|
|
114
|
-
window.addEventListener('submit', e => {
|
|
115
|
-
if (!this._canIntercept(e)) return;
|
|
116
|
-
// ---------------
|
|
117
|
-
// Declare form submission modifyers
|
|
118
|
-
const form = e.target.closest('form'), submitter = e.submitter;
|
|
119
|
-
const submitParams = [ 'action', 'enctype', 'method', 'noValidate', 'target' ].reduce((params, prop) => {
|
|
120
|
-
params[prop] = submitter && submitter.hasAttribute(`form${prop.toLowerCase()}`) ? submitter[`form${_toTitle(prop)}`] : form[prop];
|
|
121
|
-
return params;
|
|
122
|
-
}, {});
|
|
123
|
-
submitParams.method = (submitter && submitter.dataset.formmethod) || form.dataset.method || submitParams.method;
|
|
124
|
-
if (submitParams.target || !this.isSpaRoute(submitParams.action)) return;
|
|
125
|
-
var actionEl = window.document.createElement('a');
|
|
126
|
-
actionEl.href = submitParams.action;
|
|
127
|
-
if (this.isHashChange(anchorEl)) {
|
|
128
|
-
Observer.set(this.location, 'href', anchorEl.href);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
// ---------------
|
|
132
|
-
// Handle now
|
|
133
|
-
var formData = new FormData(form);
|
|
134
|
-
if ((submitter || {}).name) { formData.set(submitter.name, submitter.value); }
|
|
135
|
-
if (submitParams.method.toUpperCase() === 'GET') {
|
|
136
|
-
var query = {};
|
|
137
|
-
Array.from(formData.entries()).forEach(_entry => {
|
|
138
|
-
params.set(query, _entry[0], _entry[1]);
|
|
139
|
-
});
|
|
140
|
-
actionEl.search = params.stringify(query);
|
|
141
|
-
formData = null;
|
|
142
|
-
}
|
|
143
|
-
e.preventDefault();
|
|
144
|
-
this._abortController?.abort();
|
|
145
|
-
this._abortController = new AbortController();
|
|
146
|
-
// Note the order of calls below
|
|
147
|
-
const detail = {
|
|
148
|
-
navigationType: 'push',
|
|
149
|
-
navigationOrigins: [ submitter, form ],
|
|
150
|
-
destination: this._asEntry(null),
|
|
151
|
-
source: this.currentEntry(), // this
|
|
152
|
-
userInitiated: true,
|
|
153
|
-
};
|
|
154
|
-
updateLocation([ submitter, form ], actionEl.href); // this
|
|
155
|
-
this.go(
|
|
156
|
-
Url.copy(actionEl),
|
|
157
|
-
{
|
|
158
|
-
method: submitParams.method,
|
|
159
|
-
body: formData,
|
|
160
|
-
signal: this._abortController.signal,
|
|
161
|
-
},
|
|
162
|
-
detail,
|
|
163
|
-
); // this
|
|
164
|
-
});
|
|
165
|
-
// -----------------------
|
|
166
|
-
// This event is triggered by
|
|
167
|
-
// either the browser back button,
|
|
168
|
-
// the window.history.back(),
|
|
169
|
-
// the window.history.forward(),
|
|
170
|
-
// or the window.history.go() action.
|
|
171
|
-
window.addEventListener('popstate', e => {
|
|
172
|
-
if (this.isHashChange(location)) {
|
|
173
|
-
Observer.set(this.location, 'href', location.href);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
// Navigation details
|
|
177
|
-
const detail = {
|
|
178
|
-
navigationType: 'traverse',
|
|
179
|
-
navigationOrigins: [],
|
|
180
|
-
destination: this._asEntry(e.state),
|
|
181
|
-
source: this.currentEntry(),
|
|
182
|
-
userInitiated: true,
|
|
183
|
-
};
|
|
184
|
-
// Traversal?
|
|
185
|
-
// Push
|
|
186
|
-
const url = location.href;
|
|
187
|
-
this.go(url, {}, detail);
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
_initNavigationAPI() {
|
|
192
|
-
// -----------------------
|
|
193
|
-
// Detect source elements
|
|
194
|
-
let navigationOrigins = [];
|
|
195
|
-
window.addEventListener('click', e => {
|
|
196
|
-
if (!this._canIntercept(e)) return;
|
|
197
|
-
let anchorEl = e.target.closest('a');
|
|
198
|
-
if (!anchorEl || !anchorEl.href || anchorEl.target) return;
|
|
199
|
-
navigationOrigins = [ anchorEl ];
|
|
200
|
-
});
|
|
201
|
-
window.addEventListener('submit', e => {
|
|
202
|
-
if (!this._canIntercept(e)) return;
|
|
203
|
-
navigationOrigins = [ e.submitter, e.target.closest('form') ];
|
|
204
|
-
});
|
|
205
|
-
// -----------------------
|
|
206
|
-
// Handle navigation event which happens after the above
|
|
207
|
-
window.navigation.addEventListener('navigate', e => {
|
|
208
|
-
if (!e.canIntercept || e.downloadRequest !== null) return;
|
|
209
|
-
if (e.hashChange) {
|
|
210
|
-
Observer.set(this.location, 'href', e.destination.url);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
const { navigationType, destination, signal, formData, info, userInitiated } = e;
|
|
214
|
-
if (formData && navigationOrigins[1]?.hasAttribute('webflo-no-intercept')) return;
|
|
215
|
-
// Navigation details
|
|
216
|
-
const detail = {
|
|
217
|
-
navigationType,
|
|
218
|
-
navigationOrigins,
|
|
219
|
-
destination,
|
|
220
|
-
source: this.currentEntry(),
|
|
221
|
-
userInitiated,
|
|
222
|
-
info,
|
|
223
|
-
};
|
|
224
|
-
const scrollContainer = this._getScrollContainer();
|
|
225
|
-
this.updateCurrentEntry({ state: {
|
|
226
|
-
...(this.currentEntry().getState() || {}),
|
|
227
|
-
navigationOrigins: this._serializeOrigins(navigationOrigins),
|
|
228
|
-
scrollPosition: scrollContainer === window ? [] : [ scrollContainer.scrollLeft, scrollContainer.scrollTop, ],
|
|
229
|
-
} });
|
|
230
|
-
navigationOrigins = [];
|
|
231
|
-
// Traversal?
|
|
232
|
-
// Push
|
|
233
|
-
const url = destination.url;
|
|
234
|
-
const init = {
|
|
235
|
-
method: formData && 'POST' || 'GET',
|
|
236
|
-
body: formData,
|
|
237
|
-
signal,
|
|
238
|
-
};
|
|
239
|
-
const nav = this;
|
|
240
|
-
e.intercept({
|
|
241
|
-
scroll: (scrollContainer !== window) && 'manual' || 'after-transition',
|
|
242
|
-
focusReset: (scrollContainer !== window) && 'manual' || 'after-transition',
|
|
243
|
-
async handler() { await nav.go(url, init, detail); },
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
_asEntry(state) { return { getState() { return state; } }; }
|
|
249
|
-
_canIntercept(e) { return !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey); }
|
|
250
|
-
_uniqueId() { return ( 0 | Math.random() * 9e6 ).toString( 36 ); }
|
|
251
|
-
|
|
252
|
-
_serializeOrigins(origins) {
|
|
253
|
-
return origins.map(node => {
|
|
254
|
-
let originId = node.getAttribute(this._webfloOriginIdAttr);
|
|
255
|
-
if (!originId) {
|
|
256
|
-
originId = this._uniqueId() + ':auto';
|
|
257
|
-
node.setAttribute(this._webfloOriginIdAttr, originId);
|
|
258
|
-
}
|
|
259
|
-
return originId;
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
_deserializeOrigins(origins) {
|
|
264
|
-
return origins.map(originId => {
|
|
265
|
-
const node = document.querySelector(`[${ this._webfloOriginIdAttr }="${ originId }"]`);
|
|
266
|
-
if (node && originId.endsWith(':auto')) { node.toggleAttribute(this._webfloOriginIdAttr, false); }
|
|
267
|
-
return node;
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
_transitOrigins(origins, state, autoRevert = 0) {
|
|
272
|
-
if (!window.webqit?.oohtml?.configs) return;
|
|
273
|
-
const { BINDINGS_API: { api: bindingsConfig } = {}, } = window.webqit.oohtml.configs;
|
|
274
|
-
origins.forEach(node => { node && (node[bindingsConfig.bindings].active = state); });
|
|
275
|
-
if (!autoRevert) return;
|
|
276
|
-
setTimeout(() => {
|
|
277
|
-
origins.forEach(node => { node && (node[bindingsConfig.bindings].active = !state); });
|
|
278
|
-
}, autoRevert);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
_getScrollContainer() {
|
|
282
|
-
if (!window.webqit?.oohtml?.configs) return;
|
|
283
|
-
const { CONTEXT_API: { attr: contextConfig } = {}, } = window.webqit.oohtml.configs;
|
|
284
|
-
return window.document.body.querySelector(`[${ window.CSS.escape( contextConfig.contextname ) }="app"]`) || window;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
_webfloOriginIdAttr = 'webflo-navigation-origin-id';
|
|
288
|
-
_xRedirectCode = 200;
|
|
289
|
-
|
|
290
|
-
// Check is-hash-action
|
|
291
|
-
isHashChange(urlObj) { return _before(this.location.href, '#') === _before(urlObj.href, '#') && (this.location.href.includes('#') || urlObj.href.includes('#')); }
|
|
292
|
-
|
|
293
|
-
// Check is-spa-route
|
|
294
|
-
isSpaRoute(url) {
|
|
295
|
-
url = typeof url === 'string' ? new URL(url, this.location.origin) : url;
|
|
296
|
-
if (url.origin && url.origin !== this.location.origin) return false;
|
|
297
|
-
if (!this.cx.params.routing) return true;
|
|
298
|
-
if (this.cx.params.routing.targets === false/** explicit false means disabled */) return false;
|
|
299
|
-
let b = url.pathname.split('/').filter(s => s);
|
|
300
|
-
const match = a => {
|
|
301
|
-
a = a.split('/').filter(s => s);
|
|
302
|
-
return a.reduce((prev, s, i) => prev && (s === b[i] || [s, b[i]].includes('-')), true);
|
|
303
|
-
};
|
|
304
|
-
return match(this.cx.params.routing.root) && this.cx.params.routing.subroots.reduce((prev, subroot) => {
|
|
305
|
-
return prev && !match(subroot);
|
|
306
|
-
}, true);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Initiates remote fetch and sets the status
|
|
310
|
-
async remoteFetch(request, ...args) {
|
|
311
|
-
let href = request;
|
|
312
|
-
if (request instanceof Request) {
|
|
313
|
-
href = request.url;
|
|
314
|
-
} else if (request instanceof URL) {
|
|
315
|
-
href = request.href;
|
|
316
|
-
}
|
|
317
|
-
Observer.set(this.network, 'remote', href, { diff: true });
|
|
318
|
-
let _response = xfetch(request, ...args);
|
|
319
|
-
// Return xResponse
|
|
320
|
-
return _response.then(async response => {
|
|
321
|
-
// Stop loading status
|
|
322
|
-
Observer.set(this.network, 'remote', null, { diff: true });
|
|
323
|
-
return xResponse.compat(response);
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Generates request object
|
|
328
|
-
generateRequest(href, init = {}) {
|
|
329
|
-
return new xRequest(href, {
|
|
330
|
-
...init,
|
|
331
|
-
headers: {
|
|
332
|
-
'Accept': 'application/json',
|
|
333
|
-
'X-Redirect-Policy': 'manual-when-cross-spa',
|
|
334
|
-
'X-Redirect-Code': this._xRedirectCode,
|
|
335
|
-
'X-Powered-By': '@webqit/webflo',
|
|
336
|
-
...(init.headers || {}),
|
|
337
|
-
},
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Generates session object
|
|
342
|
-
createStorage(e, id = null, persistent = false) {
|
|
343
|
-
return createStorage(id, persistent);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// -----------------------------------------------
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* reload()
|
|
350
|
-
*/
|
|
351
|
-
reload(params) {
|
|
352
|
-
if (this.useNavigationAPI) { return window.navigation.reload(params); }
|
|
353
|
-
return window.history.reload();
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* back()
|
|
358
|
-
*/
|
|
359
|
-
back() {
|
|
360
|
-
if (this.useNavigationAPI) { return window.navigation.canGoBack && window.navigation.back(); }
|
|
361
|
-
return window.history.back();
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* forward()
|
|
366
|
-
*/
|
|
367
|
-
forward() {
|
|
368
|
-
if (this.useNavigationAPI) { return window.navigation.canGoForward && window.navigation.forward(); }
|
|
369
|
-
return window.history.forward();
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* go()
|
|
374
|
-
*/
|
|
375
|
-
traverseTo(...args) {
|
|
376
|
-
if (this.useNavigationAPI) { return window.navigation.traverseTo(...args); }
|
|
377
|
-
return window.history.go(...args);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* entries()
|
|
382
|
-
*/
|
|
383
|
-
entries() {
|
|
384
|
-
if (this.useNavigationAPI) { return window.navigation.entries(); }
|
|
385
|
-
return history;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* currentEntry()
|
|
390
|
-
*/
|
|
391
|
-
currentEntry() {
|
|
392
|
-
if (window.navigation) return window.navigation.currentEntry;
|
|
393
|
-
return this._asEntry(history.state);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* updateCurrentEntry()
|
|
398
|
-
*/
|
|
399
|
-
async updateCurrentEntry(params, url = null) {
|
|
400
|
-
if (this.useNavigationAPI) {
|
|
401
|
-
if (!url || url === window.navigation.currentEntry.url) {
|
|
402
|
-
window.navigation.updateCurrentEntry(params);
|
|
403
|
-
} else { await window.navigation.navigate(url, { ...params, history: 'replace' }).committed; }
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
window.history.replaceState(params.state, '', url);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* push()
|
|
411
|
-
*/
|
|
412
|
-
async push(url, state = {}) {
|
|
413
|
-
if (typeof url === 'string' && url.startsWith('&')) { url = this.location.href.split('#')[0] + (this.location.href.includes('?') ? url : url.replace('&', '?')); }
|
|
414
|
-
url = new URL(url, this.location.href);
|
|
415
|
-
if (this.useNavigationAPI) {
|
|
416
|
-
await window.navigation.navigate(url.href, state).committed;
|
|
417
|
-
} else { window.history.pushState(state, '', url.href); }
|
|
418
|
-
Observer.set(this.location, 'href', url.href);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Performs a request.
|
|
423
|
-
*
|
|
424
|
-
* @param object|string href
|
|
425
|
-
* @param object|Request init
|
|
426
|
-
* @param object src
|
|
427
|
-
*
|
|
428
|
-
* @return Response
|
|
429
|
-
*/
|
|
430
|
-
async go(url, init = {}, detail = {}) {
|
|
431
|
-
// ------------
|
|
432
|
-
// Resolve inputs
|
|
433
|
-
// ------------
|
|
434
|
-
url = typeof url === 'string' ? new URL(url, this.location.origin) : url;
|
|
435
|
-
if (!(init instanceof Request) && !init.referrer) { init = { referrer: this.location.href, ...init }; }
|
|
436
|
-
if (![ 'startup', 'rdr' ].includes(detail.navigationType) && (_before(url.href, '#') === _before(init.referrer, '#') && (init.method || 'GET').toUpperCase() === 'GET')) return;
|
|
437
|
-
|
|
438
|
-
// ------------
|
|
439
|
-
// Pre-request states
|
|
440
|
-
// ------------
|
|
441
|
-
Observer.set(this.network, 'error', null, { diff: true });
|
|
442
|
-
Observer.set(this.network, 'requesting', { init, detail });
|
|
443
|
-
if (detail.navigationType !== 'traverse') { this._transitOrigins(detail.navigationOrigins, true); }
|
|
444
|
-
|
|
445
|
-
// ------------
|
|
446
|
-
// Request
|
|
447
|
-
// ------------
|
|
448
|
-
const request = this.generateRequest(url.href, init);
|
|
449
|
-
const httpEvent = new HttpEvent(request, detail, (id = null, persistent = false) => this.createStorage(httpEvent, id, persistent));
|
|
450
|
-
let response;
|
|
451
|
-
try {
|
|
452
|
-
// Fire request and obtain response
|
|
453
|
-
response = await this.app.handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
|
|
454
|
-
if (typeof response === 'undefined') { response = new xResponse(null, { status: 404 }); }
|
|
455
|
-
else if (!(response instanceof xResponse)) { response = xResponse.compat(response); }
|
|
456
|
-
} catch(e) {
|
|
457
|
-
console.error(e);
|
|
458
|
-
Observer.set(this.network, 'error', { ...e, retry: () => this.go(url, init, detail) });
|
|
459
|
-
response = new xResponse(e.message, { status: 500 });
|
|
460
|
-
}
|
|
461
|
-
// Handle redirection
|
|
462
|
-
const location = response.headers.get('Location');
|
|
463
|
-
if (location) {
|
|
464
|
-
const xActualRedirectCode = parseInt(response.headers.get('X-Redirect-Code'));
|
|
465
|
-
if (xActualRedirectCode && response.status === this._xRedirectCode) {
|
|
466
|
-
response.attrs.status = xActualRedirectCode; // @NOTE 1
|
|
467
|
-
}
|
|
468
|
-
if ([ 302,301 ].includes(response.status)) {
|
|
469
|
-
Observer.set(this.network, 'redirecting', location, { diff: true });
|
|
470
|
-
if (this.isSpaRoute(location)) {
|
|
471
|
-
this.go(location, {}, { navigationType: 'rdr', navigationOrigins: [] });
|
|
472
|
-
} else { window.location = location; }
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// ------------
|
|
478
|
-
// // Post-request states
|
|
479
|
-
// ------------
|
|
480
|
-
const update = async () => {
|
|
481
|
-
//Observer.set(this.location, newLocation);
|
|
482
|
-
// Reset states
|
|
483
|
-
Observer.set(this.network, 'requesting', null, { diff: true });
|
|
484
|
-
Observer.set(this.network, 'redirecting', null, { diff: true });
|
|
485
|
-
if ([ 404, 500 ].includes(response.status)) {
|
|
486
|
-
Observer.set(this.network, 'error', new Error(response.statusText, { code: response.status }));
|
|
487
|
-
}
|
|
488
|
-
// Update location and render
|
|
489
|
-
const finalUrl = response.url || request.url;
|
|
490
|
-
Observer.set(this.location, 'href', finalUrl);
|
|
491
|
-
const extraDetail = (await this.app.render?.(httpEvent, response), {});
|
|
492
|
-
// Transit origins
|
|
493
|
-
const scrollContainer = this._getScrollContainer();
|
|
494
|
-
if (detail.navigationType === 'traverse') {
|
|
495
|
-
const destinationState = detail.destination?.getState() || {};
|
|
496
|
-
this._transitOrigins(this._deserializeOrigins(destinationState.navigationOrigins || []), true, 110);
|
|
497
|
-
// Manual scrolling?
|
|
498
|
-
if (scrollContainer !== window && destinationState.scrollPosition?.length) {
|
|
499
|
-
scrollContainer.scroll(...destinationState.scrollPosition);
|
|
500
|
-
(document.querySelector('[autofocus]') || document.body).focus();
|
|
501
|
-
}
|
|
502
|
-
} else {
|
|
503
|
-
this._transitOrigins(detail.navigationOrigins, false);
|
|
504
|
-
const stateData = { ...(this.currentEntry().getState() || {}), ...extraDetail, redirected: response.redirected, };
|
|
505
|
-
await this.updateCurrentEntry({ state: stateData }, finalUrl);
|
|
506
|
-
// Manual scrolling?
|
|
507
|
-
if (scrollContainer !== window && httpEvent.url.hash) {
|
|
508
|
-
document.querySelector(httpEvent.url.hash)?.scrollIntoView({ behavior: 'smooth' });
|
|
509
|
-
(document.querySelector('[autofocus]') || document.body).focus();
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
await new Promise(res => setTimeout(res, 100));
|
|
513
|
-
};
|
|
514
|
-
if (document.startViewTransition && detail.navigationType !== 'startup') {
|
|
515
|
-
const synthesizeWhile = window.webqit?.realdom?.synthesizeWhile || ( callback => callback() );
|
|
516
|
-
return synthesizeWhile(async () => {
|
|
517
|
-
document.documentElement.classList.toggle('transiting', true);
|
|
518
|
-
try { await document.startViewTransition( update ).ready; } catch(e) { console.log(e); }
|
|
519
|
-
document.documentElement.classList.toggle('transiting', false);
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
return await update();
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @imports
|
|
5
|
-
*/
|
|
6
|
-
import { _isString, _isUndefined } from '@webqit/util/js/index.js';
|
|
7
|
-
import { Observer } from './Runtime.js';
|
|
8
|
-
|
|
9
|
-
export default function(namespace = null, persistent = false) {
|
|
10
|
-
const storeType = persistent ? 'localStorage' : 'sessionStorage';
|
|
11
|
-
if (!window[storeType]) {
|
|
12
|
-
throw new Error(`The specified Web Storage API ${storeType} is invalid or not supported`);
|
|
13
|
-
}
|
|
14
|
-
const _storage = {}, key = e => namespace ? `${namespace}.${e.key}` : e.key;
|
|
15
|
-
Observer.intercept(_storage, {
|
|
16
|
-
get: (event, received, next) => {
|
|
17
|
-
if (!_isString(event.key)) return;
|
|
18
|
-
const value = window[storeType].getItem(key(event));
|
|
19
|
-
return next(!_isUndefined(value) ? JSON.parse(value) : value);
|
|
20
|
-
},
|
|
21
|
-
set: (event, received, next) => {
|
|
22
|
-
if (!_isString(event.key)) return;
|
|
23
|
-
window[storeType].setItem(key(event), !_isUndefined(event.value) ? JSON.stringify(event.value) : event.value);
|
|
24
|
-
return next(true);
|
|
25
|
-
},
|
|
26
|
-
deleteProperty: (event, received, next) => {
|
|
27
|
-
if (!_isString(event.key)) return;
|
|
28
|
-
window[storeType].removeItem(key(event));
|
|
29
|
-
return next(true);
|
|
30
|
-
},
|
|
31
|
-
has: (event, received, next) => {
|
|
32
|
-
if (!_isString(event.key)) return;
|
|
33
|
-
const _key = key(event);
|
|
34
|
-
for(let i = 0; i < window[storeType].length; i ++){
|
|
35
|
-
if (window[storeType].key(i) === _key) {
|
|
36
|
-
return next(true);
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
return next(false);
|
|
40
|
-
},
|
|
41
|
-
ownKeys: (event, received, next) => {
|
|
42
|
-
const keys = [];
|
|
43
|
-
for(let i = 0; i < window[storeType].length; i ++){
|
|
44
|
-
keys.push(window[storeType].key(i));
|
|
45
|
-
};
|
|
46
|
-
return next(keys);
|
|
47
|
-
},
|
|
48
|
-
getOwnPropertyDescriptor: (event, received, next) => {
|
|
49
|
-
return next({ enumerable: true, configurable: true });
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
return Observer.proxy(_storage);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export {
|
|
57
|
-
Observer,
|
|
58
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @imports
|
|
4
|
-
*/
|
|
5
|
-
import Router from '../Router.js';
|
|
6
|
-
import _Application from '../../Application.js';
|
|
7
|
-
|
|
8
|
-
export default class Application extends _Application {
|
|
9
|
-
|
|
10
|
-
// Returns router class
|
|
11
|
-
get Router() {
|
|
12
|
-
return Router;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Handles HTTP events.
|
|
17
|
-
*
|
|
18
|
-
* @param HttpEvent httpEvent
|
|
19
|
-
* @param Function remoteFetch
|
|
20
|
-
*
|
|
21
|
-
* @return Response
|
|
22
|
-
*/
|
|
23
|
-
async handle(httpEvent, remoteFetch) {
|
|
24
|
-
// The app router
|
|
25
|
-
const router = new this.Router(this.cx, httpEvent.url.pathname);
|
|
26
|
-
const handle = async () => {
|
|
27
|
-
// --------
|
|
28
|
-
// ROUTE FOR DATA
|
|
29
|
-
// --------
|
|
30
|
-
return router.route([httpEvent.request.method, 'default'], httpEvent, {}, async event => {
|
|
31
|
-
if (event !== httpEvent) {
|
|
32
|
-
// This was nexted()
|
|
33
|
-
if (!event.request.headers.has('Accept')) {
|
|
34
|
-
event.request.headers.set('Accept', 'application/json');
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return remoteFetch(event.request);
|
|
38
|
-
}, remoteFetch);
|
|
39
|
-
};
|
|
40
|
-
return handle();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|