@webqit/webflo 0.8.72 → 0.8.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "vanila-javascript"
13
13
  ],
14
14
  "homepage": "https://webqit.io/tooling/webflo",
15
- "version": "0.8.72",
15
+ "version": "0.8.73",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
@@ -45,22 +45,24 @@ export default class Http {
45
45
  * @return void
46
46
  */
47
47
  async go(url, options = {}) {
48
- if (this.activeController) {
49
- this.activeController.abort();
48
+ if (this.abortController) {
49
+ this.abortController.abort();
50
50
  }
51
- this.activeController = new AbortController();
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-No-Cors-Redirect': 'manual',
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.activeController.signal,
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('X-No-Cors-Redirect')) {
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: window.document.location.href, ...options };
80
+ options = { referrer: this.location.href, ...options };
79
81
  Observer.set(this.location, url, { detail: options, });
80
- if (options.srcType === 'history' || !(_before(url.href, '#') === _before(options.referrer, '#') && (options.method || 'GET').toUpperCase() === 'GET')) {
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 instance
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] = submits.reduce((val, el) => val || (el.hasAttribute(`form${prop.toLowerCase()}`) ? el[`form${_toTitle(prop)}`] : null), null) || form[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 = e.submitter.dataset.method || form.dataset.method || 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 (e.submitter.name) {
190
- formData.set(e.submitter.name, e.submitter.value);
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 Http from './Http.js';
13
+ import Navigator from './Navigator.js';
14
14
 
15
15
  /**
16
16
  * ---------------------------
@@ -21,184 +21,107 @@ 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
- const httpInstance = this;
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 app router
59
- const clientNavigationEvent = new NavigationEvent(request, session);
60
- const requestPath = clientNavigationEvent.url.pathname;
61
- const router = new Router(requestPath, layout, $context);
62
- if (networkProgressOngoing) {
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
- try {
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
- // ROUTE FOR DATA
71
- // --------
72
- const httpMethodName = clientNavigationEvent.request.method.toLowerCase();
73
- $context.response = await router.route([httpMethodName === 'delete' ? 'del' : httpMethodName, 'default'], clientNavigationEvent, document.state, async function(event) {
74
- // -----------------
75
- var networkProgress = networkProgressOngoing = new RequestHandle();
76
- networkProgress.setActive(true, event.request._method || event.request.method);
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,
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
- if ($context.response instanceof clientNavigationEvent.Response) {
101
- $context.data = await $context.response.data();
102
- } else {
103
- $context.data = $context.response;
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
+ console.log('----------------data', data);
71
+ await router.route('render', clientNavigationEvent, data, async function(event, data) {
132
72
  // --------
133
- // Render...
134
- // --------
135
-
136
- if (/*document.activeElement === document.body && */event && _isObject(event.detail) && (event.detail.src instanceof Element) && /* do only on url path change */ _before(event.value, '?') !== _before(event.oldValue, '?')) {
137
- setTimeout(() => {
138
- let vieportTop;
139
- if (clientNavigationEvent.url.hash && (urlTarget = document.querySelector(clientNavigationEvent.url.hash))) {
140
- urlTarget.scrollIntoView();
141
- } else if (vieportTop = Array.from(document.querySelectorAll('[data-viewport-top]')).pop()) {
142
- vieportTop.focus();
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);
73
+ // OOHTML would waiting for DOM-ready in order to be initialized
74
+ await new Promise(res => window.WebQit.DOM.ready(res));
75
+ if (!window.document.state.env) {
76
+ window.document.setState({
77
+ env: 'client',
78
+ onHydration: params.srcType === 'init',
79
+ network: navigator.network,
80
+ url: navigator.location,
81
+ session,
82
+ }, { update: true });
151
83
  }
84
+ window.document.setState({ page: data }, { update: 'merge' });
85
+ window.document.body.setAttribute('template', 'page/' + clientNavigationEvent.url.pathname.split('/').filter(a => a).map(a => a + '+-').join('/'));
86
+ return new Promise(res => {
87
+ window.document.addEventListener('templatesreadystatechange', () => res(window));
88
+ if (window.document.templatesReadyState === 'complete') {
89
+ res(window);
90
+ }
91
+ });
92
+ });
152
93
 
153
- } catch(e) {
154
-
155
- window.document.body.setAttribute('template', '');
156
- throw e;
157
-
94
+ // --------
95
+ // Render...
96
+ // --------
97
+
98
+ if (params.src instanceof Element) {
99
+ setTimeout(() => {
100
+ let viewportTop;
101
+ if (clientNavigationEvent.url.hash && (urlTarget = document.querySelector(clientNavigationEvent.url.hash))) {
102
+ urlTarget.scrollIntoView();
103
+ } else if (viewportTop = Array.from(document.querySelectorAll('[data-viewport-top]')).pop()) {
104
+ viewportTop.focus();
105
+ } else {
106
+ document.documentElement.classList.add('scroll-reset');
107
+ document.body.scrollIntoView();
108
+ setTimeout(() => {
109
+ document.documentElement.classList.remove('scroll-reset');
110
+ }, 600);
111
+ }
112
+ }, 0);
158
113
  }
159
114
 
160
- return $context.response;
115
+ return response;
161
116
  });
162
117
 
118
+ Observer.observe(session, changes => {
119
+ //console.log('SESSION_STATE_CHANGE', changes[0].name, changes[0].value);
120
+ });
121
+ Observer.observe(workerClient, changes => {
122
+ //console.log('SERVICE_WORKER_STATE_CHANGE', changes[0].name, changes[0].value);
123
+ });
124
+ Observer.observe(navigator, changes => {
125
+ //console.log('NAVIGATORSTATE_CHANGE', changes[0].name, changes[0].value);
126
+ });
163
127
  };
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
- _request,
233
- _sessionFactory('_session', {duration: 60 * 60 * 24 * 30}).get(),
231
+ new NavigationEvent.Request(fullUrl, requestInit),
232
+ _sessionFactory('_session', { duration: 60 * 60 }).get(),
234
233
  _sessionFactory
235
234
  );
236
235
 
@@ -464,15 +463,16 @@ export async function run(hostSetup, request, response, Ui, flags = {}, protocol
464
463
  // Send
465
464
  // -------------------
466
465
  if ($context.response.headers.redirect) {
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();
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
472
  } else {
473
473
  response.statusCode = $context.response.status;
474
- response.end();
475
474
  }
475
+ response.end();
476
476
  } else if ($context.response.original !== undefined && $context.response.original !== null) {
477
477
  response.statusCode = $context.response.status;
478
478
  response.statusMessage = $context.response.statusText;
@@ -553,9 +553,9 @@ export async function run(hostSetup, request, response, Ui, flags = {}, protocol
553
553
 
554
554
  if (flags.logs !== false) {
555
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;
556
+ let xRedirectCode = response.getHeader('X-Redirect-Code');
557
+ let redirectCode = xRedirectCode || ((response.statusCode + '').startsWith('3') ? response.statusCode : 0);
558
+ let statusCode = xRedirectCode || response.statusCode;
559
559
  Ui.log(''
560
560
  + '[' + (hostSetup.vh ? Ui.style.keyword(hostSetup.vh.host) + '][' : '') + Ui.style.comment((new Date).toUTCString()) + '] '
561
561
  + Ui.style.keyword(protocol.toUpperCase() + ' ' + serverNavigationEvent.request.method) + ' '