@webqit/webflo 0.8.70-0 → 0.8.72
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/runtime/_MessageStream.js +18 -14
- package/src/runtime/_NavigationEvent.js +6 -4
- package/src/runtime/_ResponseHeaders.js +9 -0
- package/src/runtime/client/Http.js +101 -101
- package/src/runtime/client/NavigationEvent.js +1 -0
- package/src/runtime/client/Runtime.js +24 -32
- package/src/runtime/client/Worker.js +4 -5
- package/src/runtime/server/NavigationEvent.js +3 -2
- package/src/runtime/server/Runtime.js +85 -101
- package/src/runtime/server/index.js +1 -1
package/package.json
CHANGED
|
@@ -104,26 +104,30 @@ const _MessageStream = (NativeMessageStream, Headers, FormData) => {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
// Payload
|
|
107
|
-
|
|
108
|
-
if (!this._typedDataCache.
|
|
109
|
-
this._typedDataCache.
|
|
110
|
-
var
|
|
107
|
+
data(force = false) {
|
|
108
|
+
if (!this._typedDataCache.data || force) {
|
|
109
|
+
this._typedDataCache.data = new Promise(async (resolve, reject) => {
|
|
110
|
+
var messageInstance = this, data, contentType = messageInstance.headers.get('content-type') || '';
|
|
111
111
|
var type = contentType === 'application/json' || this._typedDataCache.json ? 'json' : (
|
|
112
|
-
contentType === 'application/x-www-form-urlencoded' || contentType.startsWith('multipart/') || this._typedDataCache.formData
|
|
112
|
+
contentType === 'application/x-www-form-urlencoded' || contentType.startsWith('multipart/') || this._typedDataCache.formData ? 'formData' : (
|
|
113
113
|
contentType === 'text/plain' ? 'plain' : 'other'
|
|
114
114
|
)
|
|
115
115
|
);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
type === '
|
|
121
|
-
|
|
116
|
+
try {
|
|
117
|
+
if (type === 'formData') {
|
|
118
|
+
data = (await messageInstance.formData()).json();
|
|
119
|
+
} else {
|
|
120
|
+
data = type === 'json' ? await messageInstance.json() : (
|
|
121
|
+
type === 'plain' ? await messageInstance.text() : messageInstance.body
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
resolve(data);
|
|
125
|
+
} catch(e) {
|
|
126
|
+
reject(e);
|
|
122
127
|
}
|
|
123
|
-
resolve(jsonBuild);
|
|
124
128
|
});
|
|
125
129
|
}
|
|
126
|
-
return this._typedDataCache.
|
|
130
|
+
return this._typedDataCache.data;
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
};
|
|
@@ -178,7 +182,7 @@ export function encodeBody(body, globals) {
|
|
|
178
182
|
contentLength: Buffer.byteLength(detailsObj.body, 'utf8'), // Buffer.from(string).length
|
|
179
183
|
};
|
|
180
184
|
}
|
|
181
|
-
detailsObj.
|
|
185
|
+
detailsObj.data = body;
|
|
182
186
|
}
|
|
183
187
|
return detailsObj;
|
|
184
188
|
}
|
|
@@ -22,12 +22,14 @@ const _NavigationEvent = globals => {
|
|
|
22
22
|
/**
|
|
23
23
|
* Initializes a new NavigationEvent instance.
|
|
24
24
|
*
|
|
25
|
-
* @param Request
|
|
26
|
-
* @param Object
|
|
25
|
+
* @param Request _request
|
|
26
|
+
* @param Object _session
|
|
27
|
+
* @param Function _sessionFactory
|
|
27
28
|
*/
|
|
28
|
-
constructor(_request, _session = null) {
|
|
29
|
+
constructor(_request, _session = {}, _sessionFactory = null) {
|
|
29
30
|
this._request = _request;
|
|
30
31
|
this._session = _session;
|
|
32
|
+
this.sessionFactory = _sessionFactory;
|
|
31
33
|
// -------
|
|
32
34
|
this.URL = URL;
|
|
33
35
|
// -------
|
|
@@ -73,7 +75,7 @@ const _NavigationEvent = globals => {
|
|
|
73
75
|
init._proxy.referrer = this.request.url;
|
|
74
76
|
request = new NavigationEvent.Request(this._request, init);
|
|
75
77
|
}
|
|
76
|
-
return new NavigationEvent(request, this._session);
|
|
78
|
+
return new NavigationEvent(request, this._session, this.sessionFactory);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
}
|
|
@@ -69,6 +69,14 @@ const _ResponseHeaders = NativeHeaders => class extends _Headers(NativeHeaders)
|
|
|
69
69
|
return value;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
get location() {
|
|
73
|
+
return this.get('Location');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
set location(value) {
|
|
77
|
+
return this.set('Location', value);
|
|
78
|
+
}
|
|
79
|
+
|
|
72
80
|
get redirect() {
|
|
73
81
|
return this.get('Location');
|
|
74
82
|
}
|
|
@@ -76,6 +84,7 @@ const _ResponseHeaders = NativeHeaders => class extends _Headers(NativeHeaders)
|
|
|
76
84
|
set redirect(value) {
|
|
77
85
|
return this.set('Location', value);
|
|
78
86
|
}
|
|
87
|
+
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
export default _ResponseHeaders;
|
|
@@ -39,25 +39,97 @@ export default class Http {
|
|
|
39
39
|
/**
|
|
40
40
|
* Performs a request.
|
|
41
41
|
*
|
|
42
|
-
* @param string href
|
|
43
|
-
* @param object
|
|
44
|
-
* @param object src
|
|
42
|
+
* @param object|string href
|
|
43
|
+
* @param object options
|
|
45
44
|
*
|
|
46
|
-
* @return
|
|
45
|
+
* @return void
|
|
47
46
|
*/
|
|
48
|
-
go(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
47
|
+
async go(url, options = {}) {
|
|
48
|
+
if (this.activeController) {
|
|
49
|
+
this.activeController.abort();
|
|
50
|
+
}
|
|
51
|
+
this.activeController = new AbortController();
|
|
52
|
+
// Generates request object
|
|
53
|
+
let generateRequest = (url, options) => {
|
|
54
|
+
return new StdRequest(url, {
|
|
55
|
+
...options,
|
|
56
|
+
headers: {
|
|
57
|
+
'Accept': 'application/json',
|
|
58
|
+
'X-No-Cors-Redirect': 'manual',
|
|
59
|
+
'X-Powered-By': '@webqit/webflo',
|
|
60
|
+
...(options.headers || {}),
|
|
61
|
+
},
|
|
62
|
+
referrer: window.document.location.href,
|
|
63
|
+
signal: this.activeController.signal,
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
// Handles response object
|
|
67
|
+
let handleResponse = (response) => {
|
|
68
|
+
if (!response) return;
|
|
69
|
+
if (response.redirected && this.isSameOrigin(response.url)) {
|
|
70
|
+
Observer.set(this.location, { href: response.url }, {
|
|
71
|
+
detail: { isRedirect: true },
|
|
72
|
+
});
|
|
73
|
+
} else if (response.headers.get('X-No-Cors-Redirect')) {
|
|
74
|
+
window.location = response.headers.get('Location');
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
url = typeof url === 'string' ? { href: url } : url;
|
|
78
|
+
options = { referrer: window.document.location.href, ...options };
|
|
79
|
+
Observer.set(this.location, url, { detail: options, });
|
|
80
|
+
if (options.srcType === 'history' || !(_before(url.href, '#') === _before(options.referrer, '#') && (options.method || 'GET').toUpperCase() === 'GET')) {
|
|
81
|
+
handleResponse(await client.call(this, generateRequest(url.href, options)));
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Checks if an URL is same origin.
|
|
87
|
+
*
|
|
88
|
+
* @param object|string url
|
|
89
|
+
*
|
|
90
|
+
* @return Bool
|
|
91
|
+
*/
|
|
92
|
+
isSameOrigin(url) {
|
|
93
|
+
if (typeof url === 'string') {
|
|
94
|
+
let href = url;
|
|
95
|
+
url = window.document.createElement('a');
|
|
96
|
+
url.href = href
|
|
97
|
+
}
|
|
98
|
+
return !url.origin || url.origin === this.location.origin;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* History object
|
|
103
|
+
*/
|
|
104
|
+
get history() {
|
|
105
|
+
return window.history;
|
|
52
106
|
}
|
|
107
|
+
|
|
53
108
|
};
|
|
54
109
|
|
|
110
|
+
// -----------------------
|
|
111
|
+
// Initialize instance
|
|
112
|
+
Observer.set(instance, 'location', new Url(window.document.location));
|
|
113
|
+
// -----------------------
|
|
114
|
+
// Syndicate changes to the browser;s location bar
|
|
115
|
+
Observer.observe(instance.location, [[ 'href' ]], ([e]) => {
|
|
116
|
+
if (e.value === 'http:' || (e.detail || {}).src === window.document.location) {
|
|
117
|
+
// Already from a "popstate" event as above, so don't push again
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (e.value === window.document.location.href || e.value + '/' === window.document.location.href) {
|
|
121
|
+
instance.history.replaceState(instance.history.state, '', instance.location.href);
|
|
122
|
+
} else {
|
|
123
|
+
try { instance.history.pushState(instance.history.state, '', instance.location.href); } catch(e) {}
|
|
124
|
+
}
|
|
125
|
+
}, { diff: true });
|
|
126
|
+
|
|
55
127
|
/**
|
|
56
128
|
* ----------------
|
|
57
|
-
*
|
|
129
|
+
* Navigation Interception
|
|
58
130
|
* ----------------
|
|
59
131
|
*/
|
|
60
|
-
|
|
132
|
+
|
|
61
133
|
// -----------------------
|
|
62
134
|
// This event is triggered by
|
|
63
135
|
// either the browser back button,
|
|
@@ -67,10 +139,9 @@ export default class Http {
|
|
|
67
139
|
window.addEventListener('popstate', e => {
|
|
68
140
|
// Needed to allow window.document.location
|
|
69
141
|
// to update to window.location
|
|
142
|
+
let referrer = window.document.location.href;
|
|
70
143
|
window.setTimeout(() => {
|
|
71
|
-
|
|
72
|
-
detail: { type: 'history', src: window.document.location },
|
|
73
|
-
});
|
|
144
|
+
instance.go(Url.copy(window.document.location), { referrer, src: window.document.location, srcType: 'history', });
|
|
74
145
|
}, 0);
|
|
75
146
|
});
|
|
76
147
|
|
|
@@ -78,24 +149,17 @@ export default class Http {
|
|
|
78
149
|
// Capture all link-clicks
|
|
79
150
|
// and fire to this router.
|
|
80
151
|
window.addEventListener('click', e => {
|
|
81
|
-
var anchor
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Same origin... but...
|
|
86
|
-
&& (!anchor.origin || anchor.origin === instance.location.origin)) {
|
|
87
|
-
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
152
|
+
var anchor = e.target.closest('a');
|
|
153
|
+
if (!anchor || !anchor.href) return;
|
|
154
|
+
if (!anchor.target && !anchor.download && (!anchor.origin || anchor.origin === instance.location.origin)) {
|
|
155
|
+
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
|
|
90
156
|
// Publish everything, including hash
|
|
91
|
-
|
|
92
|
-
detail: { type: 'link', src: anchor, },
|
|
93
|
-
});
|
|
157
|
+
instance.go(Url.copy(anchor), { src: anchor, srcType: 'link', });
|
|
94
158
|
// URLs with # will cause a natural navigation
|
|
95
159
|
// even if pointing to a different page, a natural navigation will still happen
|
|
96
160
|
// because with the Observer.set() above, window.document.location.href would have become
|
|
97
161
|
// the destination page, which makes it look like same page navigation
|
|
98
|
-
if (!href.includes('#')) {
|
|
162
|
+
if (!anchor.href.includes('#')) {
|
|
99
163
|
e.preventDefault();
|
|
100
164
|
}
|
|
101
165
|
}
|
|
@@ -105,25 +169,27 @@ export default class Http {
|
|
|
105
169
|
// Capture all form-submit
|
|
106
170
|
// and fire to this router.
|
|
107
171
|
window.addEventListener('submit', e => {
|
|
108
|
-
var
|
|
109
|
-
form = e.target.closest('form'),
|
|
172
|
+
var form = e.target.closest('form'),
|
|
110
173
|
submits = [e.submitter]; //_arrFrom(form.elements).filter(el => el.matches('button,input[type="submit"],input[type="image"]'));
|
|
111
174
|
var submitParams = [ 'action', 'enctype', 'method', 'noValidate', 'target' ].reduce((params, prop) => {
|
|
112
175
|
params[prop] = submits.reduce((val, el) => val || (el.hasAttribute(`form${prop.toLowerCase()}`) ? el[`form${_toTitle(prop)}`] : null), null) || form[prop];
|
|
113
176
|
return params;
|
|
114
177
|
}, {});
|
|
115
178
|
// We support method hacking
|
|
116
|
-
// ---------------
|
|
117
179
|
submitParams.method = e.submitter.dataset.method || form.dataset.method || submitParams.method;
|
|
118
180
|
// ---------------
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
181
|
+
var actionEl = window.document.createElement('a');
|
|
182
|
+
actionEl.href = submitParams.action;
|
|
183
|
+
// ---------------
|
|
184
|
+
// If not targeted and same origin...
|
|
185
|
+
if (!submitParams.target && (!actionEl.origin || actionEl.origin === instance.location.origin)) {
|
|
186
|
+
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
|
|
187
|
+
// Build data
|
|
122
188
|
var formData = new FormData(form);
|
|
123
189
|
if (e.submitter.name) {
|
|
124
190
|
formData.set(e.submitter.name, e.submitter.value);
|
|
125
191
|
}
|
|
126
|
-
if (submitParams.method === '
|
|
192
|
+
if (submitParams.method.toUpperCase() === 'GET') {
|
|
127
193
|
var query = wwwFormUnserialize(actionEl.search);
|
|
128
194
|
Array.from(formData.entries()).forEach(_entry => {
|
|
129
195
|
wwwFormSet(query, _entry[0], _entry[1], false);
|
|
@@ -131,13 +197,7 @@ export default class Http {
|
|
|
131
197
|
actionEl.search = wwwFormSerialize(query);
|
|
132
198
|
formData = null;
|
|
133
199
|
}
|
|
134
|
-
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
// Publish everything, including hash
|
|
138
|
-
Observer.set(instance.location, Url.copy(actionEl), {
|
|
139
|
-
detail: { type: 'form', src: form, submitParams, data: formData },
|
|
140
|
-
});
|
|
200
|
+
instance.go(Url.copy(actionEl), { ...submitParams, body: formData, src: form, srcType: 'form', });
|
|
141
201
|
// URLs with # will cause a natural navigation
|
|
142
202
|
// even if pointing to a different page, a natural navigation will still happen
|
|
143
203
|
// because with the Observer.set() above, window.document.location.href would have become
|
|
@@ -148,69 +208,9 @@ export default class Http {
|
|
|
148
208
|
}
|
|
149
209
|
});
|
|
150
210
|
|
|
151
|
-
/**
|
|
152
|
-
* ----------------
|
|
153
|
-
* instance.history
|
|
154
|
-
* ----------------
|
|
155
|
-
*/
|
|
156
|
-
|
|
157
|
-
instance.history = window.history;
|
|
158
211
|
// -----------------------
|
|
159
|
-
// Syndicate changes to
|
|
160
|
-
// the browser;s location bar
|
|
161
|
-
Observer.observe(instance.location, [[ 'href' ]], e => {
|
|
162
|
-
e = e[0];
|
|
163
|
-
if ((e.detail || {}).src === window.document.location) {
|
|
164
|
-
// Already from a "popstate" event as above, so don't push again
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
if (e.value === 'http:') return;
|
|
168
|
-
if (e.value === window.document.location.href || e.value + '/' === window.document.location.href) {
|
|
169
|
-
instance.history.replaceState(instance.history.state, '', instance.location.href);
|
|
170
|
-
} else {
|
|
171
|
-
try {
|
|
172
|
-
instance.history.pushState(instance.history.state, '', instance.location.href);
|
|
173
|
-
} catch(e) {}
|
|
174
|
-
}
|
|
175
|
-
}, { diff: true });
|
|
176
|
-
|
|
177
|
-
// ----------------------------------
|
|
178
|
-
const createRequest = (url, referrer, e = {}) => {
|
|
179
|
-
var detail = e.detail || {};
|
|
180
|
-
var options = {
|
|
181
|
-
method: (detail.submitParams || detail.src || {}).method || 'get',
|
|
182
|
-
body: detail.data,
|
|
183
|
-
headers: { ...(detail.headers || {}), 'X-Powered-By': '@webqit/webflo', },
|
|
184
|
-
referrer,
|
|
185
|
-
};
|
|
186
|
-
return new StdRequest(url, options);
|
|
187
|
-
};
|
|
188
|
-
const handleResponse = response => {
|
|
189
|
-
if (response && response.redirected) {
|
|
190
|
-
var actionEl = window.document.createElement('a');
|
|
191
|
-
if ((actionEl.href = response.url) && (!actionEl.origin || actionEl.origin === instance.location.origin)) {
|
|
192
|
-
Observer.set(instance.location, { href: response.url }, {
|
|
193
|
-
detail: { follow: false },
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
// ----------------------------------
|
|
199
|
-
|
|
200
|
-
// Observe location and route
|
|
201
|
-
Observer.observe(instance.location, [['href']], async e => {
|
|
202
|
-
e = e[0];
|
|
203
|
-
var detail = e.detail || {};
|
|
204
|
-
if (detail.follow === false) return;
|
|
205
|
-
var method = (detail.submitParams || detail.src || {}).method;
|
|
206
|
-
if ((_before(e.value, '#') !== _before(e.oldValue, '#')) || (method && method.toUpperCase() !== 'GET')) {
|
|
207
|
-
return handleResponse(await client.call(instance, createRequest(e.value, e.oldValue, e), e));
|
|
208
|
-
}
|
|
209
|
-
}, {diff: false /* method might be the difference */});
|
|
210
212
|
// Startup route
|
|
211
|
-
|
|
212
|
-
handleResponse(await client.call(instance, createRequest(window.document.location.href, document.referrer)));
|
|
213
|
-
|
|
213
|
+
instance.go(window.document.location.href, { referrer: document.referrer });
|
|
214
214
|
return instance;
|
|
215
215
|
}
|
|
216
216
|
|
|
@@ -74,50 +74,39 @@ export default function(layout, params) {
|
|
|
74
74
|
// -----------------
|
|
75
75
|
var networkProgress = networkProgressOngoing = new RequestHandle();
|
|
76
76
|
networkProgress.setActive(true, event.request._method || event.request.method);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// Sync session data to cache to be available to service-worker routers
|
|
87
|
-
const response = fetch(event.request, {}, networkProgress.updateProgress.bind(networkProgress));
|
|
88
|
-
// -----------------
|
|
89
|
-
// -----------------
|
|
90
|
-
response.catch(e => networkProgress.throw(e.message));
|
|
91
|
-
return response.then(async _response => {
|
|
92
|
-
_response = new clientNavigationEvent.Response(_response.body, {
|
|
93
|
-
status: _response.status,
|
|
94
|
-
statusText: _response.statusText,
|
|
95
|
-
headers: _response.headers,
|
|
77
|
+
const _response = fetch(event.request);
|
|
78
|
+
// This catch() is NOT intended to handle failure of the fetch
|
|
79
|
+
_response.catch(e => networkProgress.throw(e.message));
|
|
80
|
+
// Save a reference to this
|
|
81
|
+
return _response.then(async response => {
|
|
82
|
+
response = new clientNavigationEvent.Response(response.body, {
|
|
83
|
+
status: response.status,
|
|
84
|
+
statusText: response.statusText,
|
|
85
|
+
headers: response.headers,
|
|
96
86
|
_proxy: {
|
|
97
|
-
url:
|
|
98
|
-
ok:
|
|
99
|
-
redirected:
|
|
87
|
+
url: response.url,
|
|
88
|
+
ok: response.ok,
|
|
89
|
+
redirected: response.redirected
|
|
100
90
|
},
|
|
101
91
|
});
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// Return a promise that never resolves as a new response is underway
|
|
105
|
-
if (!networkProgress.active) {
|
|
106
|
-
return new Promise(() => {});
|
|
92
|
+
if (response.headers.get('Location')) {
|
|
93
|
+
networkProgress.redirecting(response.headers.get('Location'));
|
|
107
94
|
}
|
|
108
95
|
// Stop loading status
|
|
109
96
|
networkProgress.setActive(false);
|
|
110
|
-
return
|
|
97
|
+
return response;
|
|
111
98
|
});
|
|
112
99
|
});
|
|
113
100
|
if ($context.response instanceof clientNavigationEvent.Response) {
|
|
114
|
-
|
|
115
|
-
}
|
|
101
|
+
$context.data = await $context.response.data();
|
|
102
|
+
} else {
|
|
103
|
+
$context.data = $context.response;
|
|
104
|
+
}
|
|
116
105
|
|
|
117
106
|
// --------
|
|
118
107
|
// Render
|
|
119
108
|
// --------
|
|
120
|
-
const rendering = await router.route('render', clientNavigationEvent, $context.
|
|
109
|
+
const rendering = await router.route('render', clientNavigationEvent, $context.data, async function(event, data) {
|
|
121
110
|
// --------
|
|
122
111
|
// OOHTML would waiting for DOM-ready in order to be initialized
|
|
123
112
|
await new Promise(res => window.WebQit.DOM.ready(res));
|
|
@@ -168,7 +157,7 @@ export default function(layout, params) {
|
|
|
168
157
|
|
|
169
158
|
}
|
|
170
159
|
|
|
171
|
-
return $context.
|
|
160
|
+
return $context.response;
|
|
172
161
|
});
|
|
173
162
|
|
|
174
163
|
};
|
|
@@ -202,6 +191,9 @@ class RequestHandle {
|
|
|
202
191
|
valuetotal,
|
|
203
192
|
});
|
|
204
193
|
}
|
|
194
|
+
redirecting(location) {
|
|
195
|
+
Observer.set(networkWatch, 'redirecting', location);
|
|
196
|
+
}
|
|
205
197
|
throw(message) {
|
|
206
198
|
if (this.active === false) {
|
|
207
199
|
return;
|
|
@@ -117,7 +117,7 @@ export default function(layout, params) {
|
|
|
117
117
|
}
|
|
118
118
|
return _response;
|
|
119
119
|
}
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
return defaultFetch(evt);
|
|
122
122
|
};
|
|
123
123
|
evt.respondWith(handleFetch(evt));
|
|
@@ -144,13 +144,12 @@ export default function(layout, params) {
|
|
|
144
144
|
return network_fetch(evt);
|
|
145
145
|
};
|
|
146
146
|
|
|
147
|
-
//evt.request.mode navigate evt.request.cache force-cache evt.request.destination document request.headers.get('Accept') text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
|
|
148
147
|
//evt.request.mode navigate evt.request.cache force-cache evt.request.destination document request.headers.get('Accept') text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
|
|
149
148
|
|
|
150
149
|
const getCacheName = request => request.headers.get('Accept') === 'application/json'
|
|
151
150
|
? params.cache_name + '_json'
|
|
152
151
|
: params.cache_name;
|
|
153
|
-
|
|
152
|
+
|
|
154
153
|
// Caching strategy: cache_first
|
|
155
154
|
const cache_fetch = (evt, cacheRefresh = false, is_Navigate_ForceCache_Document = false) => {
|
|
156
155
|
|
|
@@ -162,10 +161,10 @@ export default function(layout, params) {
|
|
|
162
161
|
let url = new URL(request.url);
|
|
163
162
|
url.searchParams.set('$force-cache', '1');
|
|
164
163
|
request = new Request(url, {
|
|
165
|
-
method: request.method,
|
|
164
|
+
method: request.method,
|
|
166
165
|
headers: request.headers,
|
|
167
166
|
body: request.body,
|
|
168
|
-
mode: request.mode === 'navigate' ? null : request.mode,
|
|
167
|
+
mode: request.mode === 'navigate'/* throws */ ? null : request.mode,
|
|
169
168
|
credentials: request.credentials,
|
|
170
169
|
cache: request.cache,
|
|
171
170
|
redirect: request.redirect,
|
|
@@ -6,7 +6,7 @@ import { URL } from 'url';
|
|
|
6
6
|
import { Readable } from "stream";
|
|
7
7
|
import { FormData, File, Blob } from 'formdata-node';
|
|
8
8
|
import { FormDataEncoder } from 'form-data-encoder';
|
|
9
|
-
import { Request, Response, Headers } from 'node-fetch';
|
|
9
|
+
import fetch, { Request, Response, Headers } from 'node-fetch';
|
|
10
10
|
import _NavigationEvent from '../_NavigationEvent.js';
|
|
11
11
|
import _FormData from '../_FormData.js';
|
|
12
12
|
|
|
@@ -34,5 +34,6 @@ export default _NavigationEvent({
|
|
|
34
34
|
File,
|
|
35
35
|
Blob,
|
|
36
36
|
ReadableStream: Readable,
|
|
37
|
-
FormDataEncoder
|
|
37
|
+
FormDataEncoder,
|
|
38
|
+
fetch
|
|
38
39
|
});
|
|
@@ -17,7 +17,6 @@ import _isArray from '@webqit/util/js/isArray.js';
|
|
|
17
17
|
import { _isString, _isPlainObject, _isPlainArray } from '@webqit/util/js/index.js';
|
|
18
18
|
import _delay from '@webqit/util/js/delay.js';
|
|
19
19
|
import { slice as _streamSlice } from 'stream-slice';
|
|
20
|
-
import { v4 as uuidv4, v5 as uuidv5 } from 'uuid';
|
|
21
20
|
import * as config from '../../config/index.js';
|
|
22
21
|
import * as services from '../../services/index.js';
|
|
23
22
|
import NavigationEvent from './NavigationEvent.js';
|
|
@@ -34,33 +33,11 @@ import Router from './Router.js';
|
|
|
34
33
|
export default async function(Ui, flags = {}) {
|
|
35
34
|
|
|
36
35
|
const layout = await config.layout.read(flags, {});
|
|
37
|
-
const setup = {
|
|
36
|
+
const v_setup = {}, setup = {
|
|
38
37
|
layout,
|
|
39
38
|
server: await config.server.read(flags, layout),
|
|
40
39
|
variables: await config.variables.read(flags, layout),
|
|
41
40
|
};
|
|
42
|
-
|
|
43
|
-
if (!setup.server.shared && setup.variables.autoload !== false) {
|
|
44
|
-
Object.keys(setup.variables.entries).forEach(key => {
|
|
45
|
-
if (!(key in process.env) || setup.variables.autoload === 2) {
|
|
46
|
-
process.env[key] = setup.variables.entries[key];
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const getSessionInitializer = (sesskey, hostname = null) => {
|
|
52
|
-
const secret = sesskey || (hostname ? uuidv5(hostname, uuidv4()) : uuidv4());
|
|
53
|
-
return Sessions({
|
|
54
|
-
cookieName: '_session', // cookie name dictates the key name added to the request object
|
|
55
|
-
secret, // should be a large unguessable string
|
|
56
|
-
duration: 24 * 60 * 60 * 1000, // how long the session will stay valid in ms
|
|
57
|
-
activeDuration: 1000 * 60 * 5 // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds
|
|
58
|
-
});
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const instanceSetup = setup;
|
|
62
|
-
|
|
63
|
-
const v_setup = {};
|
|
64
41
|
if (setup.server.shared) {
|
|
65
42
|
await Promise.all(((await config.vhosts.read(flags, setup.layout)).entries || []).map(vh => new Promise(async resolve => {
|
|
66
43
|
const vlayout = await config.layout.read(flags, {ROOT: Path.join(setup.layout.ROOT, vh.path)});
|
|
@@ -70,86 +47,57 @@ export default async function(Ui, flags = {}) {
|
|
|
70
47
|
variables: await config.variables.read(flags, vlayout),
|
|
71
48
|
vh,
|
|
72
49
|
};
|
|
73
|
-
v_setup[vh.host].sessionInit = getSessionInitializer(v_setup[vh.host].variables.entries.sesskey, vh.host),
|
|
74
50
|
resolve();
|
|
75
51
|
})));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
52
|
+
} else if (setup.variables.autoload !== false) {
|
|
53
|
+
Object.keys(setup.variables.entries).forEach(key => {
|
|
54
|
+
if (!(key in process.env) || setup.variables.autoload === 2) {
|
|
55
|
+
process.env[key] = setup.variables.entries[key];
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
79
59
|
|
|
80
60
|
// ---------------------------------------------
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
61
|
+
|
|
62
|
+
function handleRequest(protocol, request, response) {
|
|
63
|
+
let _setup = setup, hostname = (request.headers.host || '').split(':')[0];
|
|
64
|
+
if (setup.server.shared) {
|
|
65
|
+
if (!(_setup = v_setup[hostname])
|
|
66
|
+
&& ((hostname.startsWith('www.') && (_setup = v_setup[hostname.substr(4)]) && _setup.server.force_www)
|
|
67
|
+
&& (!hostname.startsWith('www.') && (_setup = v_setup['www.' + hostname]) && _setup.server.force_www))) {
|
|
68
|
+
response.statusCode = 500;
|
|
69
|
+
response.end('Unrecognized host');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
86
72
|
}
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
if (protocol === 'http' && _setup.server.https.force && !flags['http-only'] && /** main server */setup.server.https.port) {
|
|
74
|
+
response.statusCode = 302;
|
|
75
|
+
response.setHeader('Location', 'https://' + request.headers.host + request.url);
|
|
76
|
+
response.end();
|
|
77
|
+
return;
|
|
90
78
|
}
|
|
91
|
-
response.statusCode = 500;
|
|
92
|
-
response.end('Unrecognized host');
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const goOrForceWww = (setup, request, response, protocol) => {
|
|
96
|
-
var hostname = request.headers.host || '';
|
|
97
79
|
if (hostname.startsWith('www.') && setup.server.force_www === 'remove') {
|
|
98
80
|
response.statusCode = 302;
|
|
99
81
|
response.setHeader('Location', protocol + '://' + hostname.substr(4) + request.url);
|
|
100
82
|
response.end();
|
|
101
|
-
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!hostname.startsWith('www.') && setup.server.force_www === 'add') {
|
|
102
86
|
response.statusCode = 302;
|
|
103
87
|
response.setHeader('Location', protocol + '://www.' + hostname + request.url);
|
|
104
88
|
response.end();
|
|
105
|
-
|
|
106
|
-
setup.sessionInit(request, response, () => {});
|
|
107
|
-
run(instanceSetup, setup, request, response, Ui, flags, protocol);
|
|
89
|
+
return;
|
|
108
90
|
}
|
|
91
|
+
run(_setup, request, response, Ui, flags, protocol);
|
|
109
92
|
};
|
|
110
93
|
|
|
111
94
|
// ---------------------------------------------
|
|
112
|
-
|
|
113
|
-
if (!flags['https-only']) {
|
|
114
|
-
|
|
115
|
-
Http.createServer((request, response) => {
|
|
116
|
-
if (setup.server.shared) {
|
|
117
|
-
var _setup;
|
|
118
|
-
if (_setup = getVSetup(request, response)) {
|
|
119
|
-
goOrForceHttps(_setup, request, response);
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
goOrForceHttps(setup, request, response);
|
|
123
|
-
}
|
|
124
|
-
}).listen(process.env.PORT || setup.server.port);
|
|
125
|
-
|
|
126
|
-
const goOrForceHttps = ($setup, $request, $response) => {
|
|
127
|
-
if ($setup.server.https.force && !flags['http-only'] && /** main server */setup.server.https.port) {
|
|
128
|
-
$response.statusCode = 302;
|
|
129
|
-
$response.setHeader('Location', 'https://' + $request.headers.host + $request.url);
|
|
130
|
-
$response.end();
|
|
131
|
-
} else {
|
|
132
|
-
goOrForceWww($setup, $request, $response, 'http');
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
95
|
|
|
96
|
+
if (!flags['https-only']) {
|
|
97
|
+
Http.createServer((request, response) => handleRequest('http', request, response)).listen(process.env.PORT || setup.server.port);
|
|
136
98
|
}
|
|
137
|
-
|
|
138
|
-
// ---------------------------------------------
|
|
139
|
-
|
|
140
99
|
if (!flags['http-only'] && setup.server.https.port) {
|
|
141
|
-
|
|
142
|
-
const httpsServer = Https.createServer((request, response) => {
|
|
143
|
-
if (setup.server.shared) {
|
|
144
|
-
var _setup;
|
|
145
|
-
if (_setup = getVSetup(request, response)) {
|
|
146
|
-
goOrForceWww(_setup, request, response, 'https');
|
|
147
|
-
}
|
|
148
|
-
} else {
|
|
149
|
-
goOrForceWww(setup, request, response, 'https');
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
100
|
+
const httpsServer = Https.createServer((request, response) => handleRequest('https', request, response));
|
|
153
101
|
if (setup.server.shared) {
|
|
154
102
|
_each(v_setup, (host, _setup) => {
|
|
155
103
|
if (Fs.existsSync(_setup.server.https.keyfile)) {
|
|
@@ -185,7 +133,6 @@ export default async function(Ui, flags = {}) {
|
|
|
185
133
|
});
|
|
186
134
|
}
|
|
187
135
|
}
|
|
188
|
-
|
|
189
136
|
httpsServer.listen(process.env.PORT2 || setup.server.https.port);
|
|
190
137
|
}
|
|
191
138
|
};
|
|
@@ -193,7 +140,6 @@ export default async function(Ui, flags = {}) {
|
|
|
193
140
|
/**
|
|
194
141
|
* The Server.
|
|
195
142
|
*
|
|
196
|
-
* @param Object instanceSetup
|
|
197
143
|
* @param Object hostSetup
|
|
198
144
|
* @param Request request
|
|
199
145
|
* @param Response response
|
|
@@ -203,13 +149,12 @@ export default async function(Ui, flags = {}) {
|
|
|
203
149
|
*
|
|
204
150
|
* @return void
|
|
205
151
|
*/
|
|
206
|
-
export async function run(
|
|
152
|
+
export async function run(hostSetup, request, response, Ui, flags = {}, protocol = 'http') {
|
|
207
153
|
|
|
208
154
|
// --------
|
|
209
155
|
// Request parsing
|
|
210
156
|
// --------
|
|
211
157
|
|
|
212
|
-
const fullUrl = protocol + '://' + request.headers.host + request.url;
|
|
213
158
|
const requestInit = { method: request.method, headers: request.headers };
|
|
214
159
|
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
215
160
|
requestInit.body = await new Promise((resolve, reject) => {
|
|
@@ -257,10 +202,38 @@ export async function run(instanceSetup, hostSetup, request, response, Ui, flags
|
|
|
257
202
|
});
|
|
258
203
|
});
|
|
259
204
|
}
|
|
260
|
-
|
|
261
|
-
//
|
|
205
|
+
|
|
206
|
+
// --------
|
|
207
|
+
// NavigationEvent instance
|
|
208
|
+
// --------
|
|
209
|
+
|
|
210
|
+
const fullUrl = protocol + '://' + request.headers.host + request.url;
|
|
262
211
|
const _request = new NavigationEvent.Request(fullUrl, requestInit);
|
|
263
|
-
const
|
|
212
|
+
const _sessionFactory = function(id, params = {}, callback = null) {
|
|
213
|
+
let factory, secret = hostSetup.variables.entries.SESSION_KEY;
|
|
214
|
+
Sessions({
|
|
215
|
+
duration: 0, // how long the session will stay valid in ms
|
|
216
|
+
activeDuration: 0, // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds
|
|
217
|
+
...params,
|
|
218
|
+
cookieName: id, // cookie name dictates the key name added to the request object
|
|
219
|
+
secret, // should be a large unguessable string
|
|
220
|
+
})(request, response, e => {
|
|
221
|
+
factory = Object.getOwnPropertyDescriptor(request, id);
|
|
222
|
+
if (callback) {
|
|
223
|
+
callback(e, factory);
|
|
224
|
+
} else if (e) {
|
|
225
|
+
// TODO
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
// Where theres no error, factory is available Sync
|
|
229
|
+
return !callback ? factory : undefined;
|
|
230
|
+
};
|
|
231
|
+
const serverNavigationEvent = new NavigationEvent(
|
|
232
|
+
_request,
|
|
233
|
+
_sessionFactory('_session', {duration: 60 * 60 * 24 * 30}).get(),
|
|
234
|
+
_sessionFactory
|
|
235
|
+
);
|
|
236
|
+
|
|
264
237
|
const $context = {
|
|
265
238
|
rdr: null,
|
|
266
239
|
layout: hostSetup.layout,
|
|
@@ -434,12 +407,7 @@ export async function run(instanceSetup, hostSetup, request, response, Ui, flags
|
|
|
434
407
|
// -------------------
|
|
435
408
|
// Chrome needs this for audio elements to play
|
|
436
409
|
response.setHeader('Accept-Ranges', 'bytes');
|
|
437
|
-
/*
|
|
438
|
-
if ($context.response.headers.contentLength && !$context.response.headers.contentRange) {
|
|
439
|
-
$context.response.headers.contentRange = `bytes 0-${$context.response.headers.contentLength}/${$context.response.headers.contentLength}`;
|
|
440
|
-
}
|
|
441
410
|
|
|
442
|
-
*/
|
|
443
411
|
// -------------------
|
|
444
412
|
// Automatic response headers
|
|
445
413
|
// -------------------
|
|
@@ -485,7 +453,7 @@ export async function run(instanceSetup, hostSetup, request, response, Ui, flags
|
|
|
485
453
|
if (name === 'set-cookie') {
|
|
486
454
|
setCookies(value);
|
|
487
455
|
} else {
|
|
488
|
-
if (name.toLowerCase() === 'location' &&
|
|
456
|
+
if (name.toLowerCase() === 'location' && $context.response.status === 200) {
|
|
489
457
|
response.statusCode = 302 /* Temporary */;
|
|
490
458
|
}
|
|
491
459
|
response.setHeader(name, value);
|
|
@@ -496,7 +464,15 @@ export async function run(instanceSetup, hostSetup, request, response, Ui, flags
|
|
|
496
464
|
// Send
|
|
497
465
|
// -------------------
|
|
498
466
|
if ($context.response.headers.redirect) {
|
|
499
|
-
|
|
467
|
+
if (serverNavigationEvent.request.headers.get('X-No-Cors-Redirect') === 'manual'
|
|
468
|
+
&& (new serverNavigationEvent.globals.URL($context.response.headers.location)).origin !== serverNavigationEvent.url.origin) {
|
|
469
|
+
response.statusCode = 200;
|
|
470
|
+
response.setHeader('X-No-Cors-Redirect', $context.response.status);
|
|
471
|
+
response.end();
|
|
472
|
+
} else {
|
|
473
|
+
response.statusCode = $context.response.status;
|
|
474
|
+
response.end();
|
|
475
|
+
}
|
|
500
476
|
} else if ($context.response.original !== undefined && $context.response.original !== null) {
|
|
501
477
|
response.statusCode = $context.response.status;
|
|
502
478
|
response.statusMessage = $context.response.statusText;
|
|
@@ -576,15 +552,23 @@ export async function run(instanceSetup, hostSetup, request, response, Ui, flags
|
|
|
576
552
|
// --------
|
|
577
553
|
|
|
578
554
|
if (flags.logs !== false) {
|
|
555
|
+
let errorCode = [ 404, 500 ].includes(response.statusCode) ? response.statusCode : 0;
|
|
556
|
+
let noCorsRedirect = response.getHeader('X-No-Cors-Redirect');
|
|
557
|
+
let redirectCode = noCorsRedirect || ((response.statusCode + '').startsWith('3') ? response.statusCode : 0);
|
|
558
|
+
let statusCode = noCorsRedirect || response.statusCode;
|
|
579
559
|
Ui.log(''
|
|
580
560
|
+ '[' + (hostSetup.vh ? Ui.style.keyword(hostSetup.vh.host) + '][' : '') + Ui.style.comment((new Date).toUTCString()) + '] '
|
|
581
561
|
+ Ui.style.keyword(protocol.toUpperCase() + ' ' + serverNavigationEvent.request.method) + ' '
|
|
582
562
|
+ Ui.style.url(serverNavigationEvent.request.url) + ($context.response && ($context.response.meta || {}).autoIndex ? Ui.style.comment((!serverNavigationEvent.request.url.endsWith('/') ? '/' : '') + $context.response.meta.autoIndex) : '') + ' '
|
|
583
563
|
+ (' (' + Ui.style.comment($context.response && ($context.response.headers || {}).contentType ? $context.response.headers.contentType : 'unknown') + ') ')
|
|
584
564
|
+ (
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
565
|
+
errorCode
|
|
566
|
+
? Ui.style.err(errorCode + ($context.fatal ? ` [ERROR]: ${$context.fatal.error || $context.fatal.toString()}` : ``))
|
|
567
|
+
: Ui.style.val(statusCode) + (
|
|
568
|
+
redirectCode
|
|
569
|
+
? ' - ' + Ui.style.val(response.getHeader('Location'))
|
|
570
|
+
: ' (' + Ui.style.keyword(response.getHeader('Content-Range') || response.statusMessage) + ')'
|
|
571
|
+
)
|
|
588
572
|
)
|
|
589
573
|
);
|
|
590
574
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import Url from 'url';
|
|
6
6
|
import Path from 'path';
|
|
7
|
-
import Pm2 from 'pm2';
|
|
7
|
+
//import Pm2 from 'pm2';
|
|
8
8
|
import _promise from '@webqit/util/js/promise.js';
|
|
9
9
|
import * as DotJson from '@webqit/backpack/src/dotfiles/DotJson.js';
|
|
10
10
|
import * as server from '../../config/server.js'
|