@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 CHANGED
@@ -12,7 +12,7 @@
12
12
  "vanila-javascript"
13
13
  ],
14
14
  "homepage": "https://webqit.io/tooling/webflo",
15
- "version": "0.8.70-0",
15
+ "version": "0.8.72",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
@@ -104,26 +104,30 @@ const _MessageStream = (NativeMessageStream, Headers, FormData) => {
104
104
  }
105
105
 
106
106
  // Payload
107
- jsonBuild(force = false) {
108
- if (!this._typedDataCache.jsonBuild || force) {
109
- this._typedDataCache.jsonBuild = new Promise(async resolve => {
110
- var request = this, jsonBuild, contentType = request.headers.get('content-type') || '';
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 || (!contentType && !['get'].includes((request.method || '').toLowerCase())) ? '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
- if (type === 'formData') {
117
- jsonBuild = (await request.formData()).json();
118
- } else {
119
- jsonBuild = type === 'json' ? await request.json() : (
120
- type === 'plain' ? await request.text() : request.body
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.jsonBuild;
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.jsonBuild = body;
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 request
26
- * @param Object sessionStore
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 request
44
- * @param object src
42
+ * @param object|string href
43
+ * @param object options
45
44
  *
46
- * @return UserEvent
45
+ * @return void
47
46
  */
48
- go(href, request = {}, src = null) {
49
- return Observer.set(this.location, 'href', href, {
50
- detail: {request, src,},
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
- * instance.location
129
+ * Navigation Interception
58
130
  * ----------------
59
131
  */
60
- Observer.set(instance, 'location', new Url(window.document.location));
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
- Observer.set(instance.location, Url.copy(window.document.location), {
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, href;
82
- if ((anchor = e.target.closest('a')) && (href = anchor.href)
83
- // And not towards any target nor have a download directive
84
- && !anchor.target && !anchor.download
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
- Observer.set(instance.location, Url.copy(anchor), {
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 actionEl = window.document.createElement('a'),
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
- if ((actionEl.href = submitParams.action) && !submitParams.target
120
- // Same origin... but...
121
- && (!actionEl.origin || actionEl.origin === instance.location.origin)) {
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 === 'get') {
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
- if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) {
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
 
@@ -17,4 +17,5 @@ export default _NavigationEvent({
17
17
  File,
18
18
  Blob,
19
19
  ReadableStream,
20
+ fetch,
20
21
  });
@@ -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
- const headers = event.request.headers;
79
- if (!headers.get('Accept')) {
80
- headers.set('Accept', 'application/json');
81
- }
82
- if (!headers.get('Cache-Control')) {
83
- headers.set('Cache-Control', 'no-store');
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: _response.url,
98
- ok: _response.ok,
99
- redirected: _response.redirected
87
+ url: response.url,
88
+ ok: response.ok,
89
+ redirected: response.redirected
100
90
  },
101
91
  });
102
- // Save a reference to this
103
- $context.responseClone = _response;
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 _response;
97
+ return response;
111
98
  });
112
99
  });
113
100
  if ($context.response instanceof clientNavigationEvent.Response) {
114
- $context.response = await $context.response.jsonBuild();
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.response, async function(event, data) {
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.responseClone;
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
- } else {
77
- setup.sessionInit = getSessionInitializer(setup.variables.entries.sesskey);
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
- const getVSetup = (request, response) => {
83
- var _setup, v_hostname = (request.headers.host || '').split(':')[0];
84
- if (_setup = v_setup[v_hostname]) {
85
- return _setup;
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 ((v_hostname.startsWith('www.') && (_setup = v_setup[v_hostname.substr(4)]) && _setup.server.force_www)
88
- || (!v_hostname.startsWith('www.') && (_setup = v_setup['www.' + v_hostname]) && _setup.server.force_www)) {
89
- return _setup;
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
- } else if (!hostname.startsWith('www.') && setup.server.force_www === 'add') {
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
- } else {
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(instanceSetup, hostSetup, request, response, Ui, flags = {}, protocol = 'http') {
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
- // The Formidabble thing in NavigationEvent class would still need
261
- // a reference to the Nodejs request
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 serverNavigationEvent = new NavigationEvent(_request, request._session);
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' && !$context.response.status) {
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
- response.end();
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
- [ 404, 500 ].includes(response.statusCode)
586
- ? Ui.style.err(response.statusCode + ($context.fatal ? ` [ERROR]: ${$context.fatal.error || $context.fatal.toString()}` : ``))
587
- : Ui.style.val(response.statusCode) + ((response.statusCode + '').startsWith('3') ? ' - ' + Ui.style.val(response.getHeader('Location')) : ' (' + Ui.style.keyword(response.getHeader('Content-Range') || response.statusMessage) + ')')
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'