@webqit/webflo 0.8.76 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/package.json +5 -12
  2. package/src/Cli.js +131 -0
  3. package/src/Configurator.js +97 -0
  4. package/src/Context.js +76 -0
  5. package/src/config-pi/deployment/Env.js +69 -0
  6. package/src/config-pi/deployment/Layout.js +65 -0
  7. package/src/config-pi/deployment/Origins.js +133 -0
  8. package/src/config-pi/deployment/Virtualization.js +65 -0
  9. package/src/config-pi/deployment/index.js +18 -0
  10. package/src/config-pi/index.js +16 -0
  11. package/src/config-pi/runtime/Client.js +59 -0
  12. package/src/config-pi/runtime/Server.js +174 -0
  13. package/src/config-pi/runtime/client/Worker.js +117 -0
  14. package/src/config-pi/runtime/client/index.js +12 -0
  15. package/src/config-pi/runtime/index.js +18 -0
  16. package/src/config-pi/runtime/server/Headers.js +90 -0
  17. package/src/config-pi/runtime/server/Redirects.js +108 -0
  18. package/src/config-pi/runtime/server/index.js +14 -0
  19. package/src/config-pi/static/Manifest.js +321 -0
  20. package/src/config-pi/static/Ssg.js +72 -0
  21. package/src/config-pi/static/index.js +14 -0
  22. package/src/deployment-pi/index.js +10 -0
  23. package/src/{services → deployment-pi}/origins/index.js +88 -58
  24. package/src/index.js +14 -147
  25. package/src/{runtime → runtime-pi}/Router.js +19 -19
  26. package/src/runtime-pi/client/Context.js +7 -0
  27. package/src/{runtime → runtime-pi}/client/Router.js +2 -2
  28. package/src/{runtime/client/Navigator.js → runtime-pi/client/Runtime.js} +143 -102
  29. package/src/runtime-pi/client/RuntimeClient.js +114 -0
  30. package/src/{runtime → runtime-pi}/client/Storage.js +8 -7
  31. package/src/{runtime → runtime-pi}/client/Url.js +2 -6
  32. package/src/{runtime/client/WorkerClient.js → runtime-pi/client/WorkerComm.js} +2 -2
  33. package/src/runtime-pi/client/generate.js +242 -0
  34. package/src/runtime-pi/client/generate.oohtml.js +7 -0
  35. package/src/runtime-pi/client/index.js +18 -0
  36. package/src/runtime-pi/client/whatwag.js +27 -0
  37. package/src/runtime-pi/client/worker/Context.js +7 -0
  38. package/src/runtime-pi/client/worker/Worker.js +243 -0
  39. package/src/runtime-pi/client/worker/WorkerClient.js +46 -0
  40. package/src/runtime-pi/client/worker/index.js +18 -0
  41. package/src/runtime-pi/index.js +14 -0
  42. package/src/runtime-pi/server/Context.js +16 -0
  43. package/src/{runtime → runtime-pi}/server/Router.js +6 -6
  44. package/src/runtime-pi/server/Runtime.js +531 -0
  45. package/src/runtime-pi/server/RuntimeClient.js +102 -0
  46. package/src/runtime-pi/server/index.js +41 -0
  47. package/src/runtime-pi/server/whatwag.js +35 -0
  48. package/src/{runtime → runtime-pi}/util.js +0 -0
  49. package/src/{runtime/_FormData.js → runtime-pi/xFormData.js} +2 -2
  50. package/src/{runtime/_Headers.js → runtime-pi/xHeaders.js} +4 -4
  51. package/src/runtime-pi/xHttpEvent.js +93 -0
  52. package/src/runtime-pi/xHttpMessage.js +179 -0
  53. package/src/runtime-pi/xRequest.js +67 -0
  54. package/src/runtime-pi/xRequestHeaders.js +95 -0
  55. package/src/runtime-pi/xResponse.js +62 -0
  56. package/src/{runtime/_ResponseHeaders.js → runtime-pi/xResponseHeaders.js} +38 -18
  57. package/src/{runtime/_URL.js → runtime-pi/xURL.js} +4 -4
  58. package/src/runtime-pi/xfetch.js +7 -0
  59. package/src/{services → services-pi}/certbot/http-auth-hook.js +0 -0
  60. package/src/{services → services-pi}/certbot/http-cleanup-hook.js +0 -0
  61. package/src/{services → services-pi}/certbot/index.js +21 -15
  62. package/src/services-pi/index.js +9 -0
  63. package/src/static-pi/index.js +11 -0
  64. package/src/webflo.js +33 -0
  65. package/test/index.test.js +26 -0
  66. package/src/build/client/index.js +0 -261
  67. package/src/build/index.js +0 -5
  68. package/src/config/client.js +0 -191
  69. package/src/config/headers.js +0 -121
  70. package/src/config/index.js +0 -14
  71. package/src/config/layout.js +0 -83
  72. package/src/config/manifest.js +0 -341
  73. package/src/config/origins.js +0 -165
  74. package/src/config/prerendering.js +0 -100
  75. package/src/config/redirects.js +0 -137
  76. package/src/config/server.js +0 -201
  77. package/src/config/variables.js +0 -102
  78. package/src/config/vhosts.js +0 -93
  79. package/src/runtime/_MessageStream.js +0 -195
  80. package/src/runtime/_NavigationEvent.js +0 -91
  81. package/src/runtime/_Request.js +0 -61
  82. package/src/runtime/_RequestHeaders.js +0 -72
  83. package/src/runtime/_Response.js +0 -56
  84. package/src/runtime/client/Cache.js +0 -38
  85. package/src/runtime/client/Http.js +0 -225
  86. package/src/runtime/client/NavigationEvent.js +0 -21
  87. package/src/runtime/client/Runtime.js +0 -126
  88. package/src/runtime/client/StdRequest.js +0 -74
  89. package/src/runtime/client/Worker.js +0 -312
  90. package/src/runtime/client/WorkerComm.js +0 -183
  91. package/src/runtime/client/effects/sounds.js +0 -64
  92. package/src/runtime/index.js +0 -5
  93. package/src/runtime/server/NavigationEvent.js +0 -39
  94. package/src/runtime/server/Runtime.js +0 -593
  95. package/src/runtime/server/index.js +0 -183
  96. package/src/runtime/server/index.mjs +0 -10
  97. package/src/services/index.js +0 -6
package/src/index.js CHANGED
@@ -1,151 +1,18 @@
1
- #!/usr/bin/env node
2
1
 
3
2
  /**
4
- * imports
3
+ * @imports
5
4
  */
6
- import _isEmpty from '@webqit/util/js/isEmpty.js';
7
- import _merge from '@webqit/util/obj/merge.js';
8
- import parseArgs from '@webqit/backpack/src/cli/parseArgs.js';
9
- import Ui from '@webqit/backpack/src/cli/Ui.js';
10
- import * as DotJson from '@webqit/backpack/src/dotfiles/DotJson.js';
11
- import { Promptx } from '@webqit/backpack/src/cli/Promptx.js';
12
- import * as build from './build/index.js';
13
- import * as config from './config/index.js';
14
- import * as runtime from './runtime/index.js';
15
- import * as services from './services/index.js';
5
+ import * as config from './config-pi/index.js';
6
+ import * as deployment from './deployment-pi/index.js';
7
+ import * as runtime from './runtime-pi/index.js';
8
+ import * as services from './services-pi/index.js';
16
9
 
17
- // ------------------------------------------
18
-
19
- const commands = {
20
- config: 'Starts a configuration process.',
21
- build: build.client.desc.build,
22
- deploy: services.origins.desc.deploy,
23
- ...runtime.server.desc,
24
- };
25
-
26
- // ------------------------------------------
27
-
28
- const { command, keywords, flags, options, ellipsis } = parseArgs(process.argv);
29
-
30
- // ------------------------------------------
31
-
32
- console.log('');
33
-
34
- (async function() {
35
- const layout = await config.layout.read({});
36
- layout.PKG = DotJson.read('./package.json');
37
- switch(command) {
38
-
39
- // --------------------------
40
-
41
- case 'build':
42
- build.client.build(Ui, flags, layout);
43
- break;
44
-
45
- // --------------------------
46
-
47
- case 'config':
48
-
49
- var domain = Object.keys(keywords)[0];
50
- // ----------------
51
- if (!domain && ellipsis) {
52
- domain = await Promptx({
53
- name: 'domain',
54
- type: 'select',
55
- choices: Object.keys(config).map(c => ({value: c})),
56
- message: 'Please select a configuration domain',
57
- }).then(d => d.domain);
58
- }
59
- if (!domain || !config[domain]) {
60
- Ui.log(Ui.f`Please add a configuration domain to the ${command} command. For options, use the ellipsis ${'...'}`);
61
- return;
62
- }
63
- // ----------------
64
- const data = await config[domain].read(flags, layout);
65
- Promptx(await config[domain].questions(data, {}, layout)).then(async _data => {
66
- await config[domain].write(_merge(data, _data), flags, layout);
67
- });
68
-
69
- break;
70
-
71
- // --------------------------
72
-
73
- case 'start':
74
- runtime.server.start(Ui, flags, layout);
75
- break;
76
-
77
- case 'stop':
78
- case 'restart':
79
- var _runtime = Object.keys(keywords)[0];
80
- // ----------------
81
- if (!_runtime && ellipsis) {
82
- _runtime = await Promptx({
83
- name: 'runtime',
84
- type: 'select',
85
- choices: (await runtime.server.processes(Ui)).map(r => ({title: r.name, description: r.status, value: r.name})).concat({description: 'All of the above', value: 'all'}),
86
- message: 'Please select a runtime name',
87
- }).then(d => d.runtime);
88
- }
89
- // ----------------
90
- await runtime.server[command](Ui, _runtime || 'all', flags);
91
- process.exit();
92
- break;
93
-
94
- case 'processes':
95
- const processes = await runtime.server.processes(Ui, flags);
96
- Ui.title(`SERVERS`);
97
- if (processes.length) {
98
- processes.forEach(service => {
99
- Ui.log(Ui.f`> ${service}`);
100
- });
101
- } else {
102
- Ui.log(Ui.f`> (empty)`);
103
- }
104
- process.exit();
105
- break;
106
-
107
- // --------------------------
108
-
109
- case 'deploy':
110
- var origin = Object.keys(keywords)[0],
111
- options;
112
- // ----------------
113
- if (!origin && ellipsis) {
114
- if (!(options = (await config.origins.read(flags, layout)).REPOS) || _isEmpty(options)) {
115
- Ui.log(Ui.f`Please configure an origin (${'webflo config ...'}) to use the ${'deploy'} command.`);
116
- return;
117
- }
118
- origin = await Promptx({
119
- name: 'origin',
120
- type: 'select',
121
- choices: options.map(r => ({value: r.TAG})),
122
- message: 'Please select a origin',
123
- }).then(d => d.origin);
124
- }
125
- if (!origin) {
126
- Ui.log(Ui.f`Please add an origin name to the ${command} command. For options, use the ellipsis ${'...'}`);
127
- return;
128
- }
129
- // ----------------
130
- services.origins.deploy(Ui, origin, flags, layout);
131
- break;
132
-
133
- // --------------------------
134
-
135
- case 'cert':
136
- var domains = Object.keys(keywords);
137
- services.certbot.generate(Ui, domains, flags, layout);
138
- break;
139
-
140
- case 'help':
141
- default:
142
- Ui.title(`NAVIGATOR HELP`);
143
- Ui.log('');
144
- Ui.log(Ui.f`Say ${'webflo'} <${'command'}>`);
145
- Ui.log('');
146
- Ui.log(Ui.f`Where <${'command'}> is one of:`);
147
- Ui.log(Ui.f`${commands}`);
148
- Ui.log('');
149
- Ui.log(Ui.f`You may also refer to the Webflo DOCS as ${'https://webqit.io/tooling/webflo'}`);
150
- }
151
- })();
10
+ /**
11
+ * @exports
12
+ */
13
+ export {
14
+ config,
15
+ deployment,
16
+ runtime,
17
+ services,
18
+ }
@@ -2,10 +2,8 @@
2
2
  /**
3
3
  * @imports
4
4
  */
5
- import _isString from '@webqit/util/js/isString.js';
6
- import _isFunction from '@webqit/util/js/isFunction.js';
7
- import _isArray from '@webqit/util/js/isArray.js';
8
- import _arrFrom from '@webqit/util/arr/from.js';
5
+ import { _isString, _isFunction, _isArray } from '@webqit/util/js/index.js';
6
+ import { _from as _arrFrom } from '@webqit/util/arr/index.js';
9
7
 
10
8
  /**
11
9
  * ---------------------------
@@ -19,16 +17,14 @@ export default class Router {
19
17
  * Constructs a new Router instance
20
18
  * over route definitions.
21
19
  *
22
- * @param string|array path
23
- * @param object layout
24
- * @param object context
20
+ * @param Context cx
21
+ * @param String|Array path
25
22
  *
26
23
  * @return void
27
24
  */
28
- constructor(path, layout, context) {
25
+ constructor(cx, path) {
26
+ this.cx = cx;
29
27
  this.path = _isArray(path) ? path : (path + '').split('/').filter(a => a);
30
- this.layout = layout;
31
- this.context = context;
32
28
  }
33
29
 
34
30
  /**
@@ -38,10 +34,11 @@ export default class Router {
38
34
  * @param Object event
39
35
  * @param any arg
40
36
  * @param function _default
37
+ * @param function remoteFetch
41
38
  *
42
39
  * @return object
43
40
  */
44
- async route(method, event, arg, _default) {
41
+ async route(method, event, arg, _default, remoteFetch = null) {
45
42
 
46
43
  const $this = this;
47
44
 
@@ -49,15 +46,17 @@ export default class Router {
49
46
  // The loop
50
47
  // ----------------
51
48
  const next = async function(thisTick) {
52
- const _this = { ...$this.context };
49
+ const thisContext = {};
53
50
  if (!thisTick.trail || thisTick.trail.length < thisTick.destination.length) {
54
51
  thisTick = await $this.readTick(thisTick);
55
52
  // -------------
56
- _this.pathname = `/${thisTick.trail.join('/')}`;
57
- _this.stepname = thisTick.trail[thisTick.trail.length - 1];
58
- $this.finalizeHandlerContext(_this, thisTick);
53
+ thisContext.pathname = `/${thisTick.trail.join('/')}`;
54
+ thisContext.stepname = thisTick.trail[thisTick.trail.length - 1];
55
+ $this.finalizeHandlerContext(thisContext, thisTick);
59
56
  // -------------
60
57
  if (thisTick.exports) {
58
+ // Broadcast any hints exported by handler
59
+ if (thisTick.exports.hints) { await event.port.post({ ...thisTick.exports.hints, $type: 'handler:hints' }); }
61
60
  const methods = _arrFrom(thisTick.method);
62
61
  const handler = _isFunction(thisTick.exports) && methods.includes('default') ? thisTick.exports : methods.reduce((_handler, name) => _handler || thisTick.exports[name.toLowerCase()], null);
63
62
  if (handler) {
@@ -102,7 +101,7 @@ export default class Router {
102
101
  _next.pathname = nextPathname.join('/');
103
102
  _next.stepname = nextPathname[0];
104
103
  // -------------
105
- return await handler.call(_this, thisTick.event, thisTick.arg, _next/*next*/);
104
+ return await handler.call(thisContext, thisTick.event, thisTick.arg, _next/*next*/, remoteFetch);
106
105
  }
107
106
  // Handler not found but exports found
108
107
  return next(thisTick);
@@ -115,7 +114,7 @@ export default class Router {
115
114
  // Local file
116
115
  // -------------
117
116
  if (_default) {
118
- return await _default.call(_this, thisTick.event, thisTick.arg);
117
+ return await _default.call(thisContext, thisTick.event, thisTick.arg, remoteFetch);
119
118
  }
120
119
  };
121
120
 
@@ -126,5 +125,6 @@ export default class Router {
126
125
  arg,
127
126
  });
128
127
 
129
- }
130
- };
128
+ }
129
+
130
+ }
@@ -0,0 +1,7 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import _Contex from '../../Context.js';
6
+
7
+ export default class Context extends _Contex {}
@@ -14,8 +14,8 @@ import _Router from '../Router.js';
14
14
  export default class Router extends _Router {
15
15
 
16
16
  async readTick(thisTick) {
17
- var routeTree = this.layout;
18
- var routePaths = Object.keys(this.layout);
17
+ var routeTree = this.cx.layout;
18
+ var routePaths = Object.keys(this.cx.layout);
19
19
  if (thisTick.trail) {
20
20
  thisTick.currentSegment = thisTick.destination[thisTick.trail.length];
21
21
  thisTick.currentSegmentOnFile = [ thisTick.currentSegment, '-' ].reduce((_segmentOnFile, _seg) => {
@@ -2,22 +2,64 @@
2
2
  /**
3
3
  * @imports
4
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';
5
+ import { _before, _toTitle } from '@webqit/util/str/index.js';
6
+ import { Observer } from '@webqit/oohtml-ssr/apis.js';
7
+ import Storage from './Storage.js';
9
8
  import Url from './Url.js';
9
+ import { wwwFormUnserialize, wwwFormSet, wwwFormSerialize } from '../util.js';
10
+ import * as whatwag from './whatwag.js';
11
+ import xURL from '../xURL.js';
12
+ import xFormData from "../xFormData.js";
13
+ import xRequestHeaders from "../xRequestHeaders.js";
14
+ import xResponseHeaders from "../xResponseHeaders.js";
15
+ import xRequest from "../xRequest.js";
16
+ import xResponse from "../xResponse.js";
17
+ import xfetch from '../xfetch.js';
18
+ import xHttpEvent from '../xHttpEvent.js';
19
+
20
+ const URL = xURL(whatwag.URL);
21
+ const FormData = xFormData(whatwag.FormData);
22
+ const ReadableStream = whatwag.ReadableStream;
23
+ const RequestHeaders = xRequestHeaders(whatwag.Headers);
24
+ const ResponseHeaders = xResponseHeaders(whatwag.Headers);
25
+ const Request = xRequest(whatwag.Request, RequestHeaders, FormData);
26
+ const Response = xResponse(whatwag.Response, ResponseHeaders, FormData);
27
+ const fetch = xfetch(whatwag.fetch);
28
+ const HttpEvent = xHttpEvent(Request, Response, URL);
29
+
30
+ export {
31
+ URL,
32
+ FormData,
33
+ ReadableStream,
34
+ RequestHeaders,
35
+ ResponseHeaders,
36
+ Request,
37
+ Response,
38
+ fetch,
39
+ HttpEvent,
40
+ Observer,
41
+ }
10
42
 
11
- export default class Navigator {
43
+ export default class Runtime {
12
44
 
13
- constructor(client) {
14
- this.client = client;
45
+ /**
46
+ * Runtime
47
+ *
48
+ * @param Object cx
49
+ * @param Function clientCallback
50
+ *
51
+ * @return void
52
+ */
53
+ constructor(cx, clientCallback) {
15
54
 
16
- /**
17
- * ----------------
18
- * Navigator location
19
- * ----------------
20
- */
55
+ // ---------------
56
+ this.cx = cx;
57
+ this.clients = new Map;
58
+ // ---------------
59
+ this.cx.runtime = this;
60
+ let client = clientCallback(this.cx, '*');
61
+ if (!client || !client.handle) throw new Error(`Application instance must define a ".handle()" method.`);
62
+ this.clients.set('*', client);
21
63
 
22
64
  // -----------------------
23
65
  // Initialize location
@@ -113,26 +155,16 @@ export default class Navigator {
113
155
  }
114
156
  });
115
157
 
116
- /**
117
- * ----------------
118
- * Navigator network
119
- * ----------------
120
- */
121
-
122
158
  // -----------------------
123
159
  // Initialize network
124
160
  Observer.set(this, 'network', {});
125
161
  window.addEventListener('online', () => Observer.set(this.network, 'online', navigator.onLine));
126
162
  window.addEventListener('offline', () => Observer.set(this.network, 'online', navigator.onLine));
127
163
 
128
- /**
129
- * ----------------
130
- * Initial navigation
131
- * ----------------
132
- */
133
-
134
- this.go(this.location, { srcType: 'init' });
135
- }
164
+ // ---------------
165
+ this.go(this.location, {}, { srcType: 'init' });
166
+ // ---------------
167
+ }
136
168
 
137
169
  /**
138
170
  * History object
@@ -145,91 +177,100 @@ export default class Navigator {
145
177
  * Performs a request.
146
178
  *
147
179
  * @param object|string href
148
- * @param object params
180
+ * @param object init
181
+ * @param object src
149
182
  *
150
- * @return void
183
+ * @return Response
151
184
  */
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
-
185
+ async go(url, init = {}, detail = {}) {
186
+ if (this._abortController) {
187
+ this._abortController.abort();
188
+ }
189
+ this._abortController = new AbortController();
190
+ this._xRedirectCode = 300;
208
191
  // ------------
209
- url = typeof url === 'string' ? { href: url } : url;
210
- params = { referrer: this.location.href, ...params };
192
+ url = typeof url === 'string' ? new whatwag.URL(url) : url;
193
+ init = { referrer: this.location.href, ...init };
211
194
  // ------------
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);
195
+ if (detail.srcType !== 'init' && (_before(url.href, '#') === _before(init.referrer, '#') && (init.method || 'GET').toUpperCase() === 'GET')) {
196
+ return;
197
+ }
198
+ // ------------
199
+ if (['link', 'form'].includes(detail.srcType)) {
200
+ Observer.set(detail.src, 'active', true);
201
+ Observer.set(detail.submitter || {}, 'active', true);
218
202
  }
219
203
  // ------------
204
+ Observer.set(this.location, url, { detail: { ...init, ...detail }, });
205
+ Observer.set(this.network, 'redirecting', null);
206
+ // ------------
207
+ // The request object
208
+ let request = this.generateRequest(url.href, init);
209
+ // The navigation event
210
+ let httpEvent = new HttpEvent(request, detail, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
211
+ // Response
212
+ let response = await this.clients.get('*').handle(httpEvent, (...args) => this.remoteFetch(...args));
213
+ let finalResponse = this.handleResponse(httpEvent, response);
214
+ // Return value
215
+ return finalResponse;
216
+ }
220
217
 
221
- if (this._abortController) {
222
- this._abortController.abort();
223
- }
224
- this._abortController = new AbortController();
225
- let xRedirectCode = 300;
218
+ // Generates request object
219
+ generateRequest(href, init) {
220
+ return new Request(href, {
221
+ signal: this._abortController.signal,
222
+ ...init,
223
+ headers: {
224
+ 'Accept': 'application/json',
225
+ 'X-Redirect-Policy': 'manual-when-cross-origin',
226
+ 'X-Redirect-Code': this._xRedirectCode,
227
+ 'X-Powered-By': '@webqit/webflo',
228
+ ...(init.headers || {}),
229
+ },
230
+ });
231
+ }
226
232
 
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
- }
233
+ // Generates session object
234
+ getSession(e, id = null, persistent = false) {
235
+ return Storage(id, persistent);
236
+ }
230
237
 
231
- return this._abortController;
232
- }
238
+ // Initiates remote fetch and sets the status
239
+ remoteFetch(request) {
240
+ Observer.set(this.network, 'remote', true);
241
+ let _response = fetch(request);
242
+ // This catch() is NOT intended to handle failure of the fetch
243
+ _response.catch(e => Observer.set(this.network, 'error', e.message));
244
+ // Return xResponse
245
+ return _response.then(async response => {
246
+ // Stop loading status
247
+ Observer.set(this.network, 'remote', false);
248
+ return new Response(response);
249
+ });
250
+ }
251
+
252
+ // Handles response object
253
+ handleResponse(e, response) {
254
+ if (!(response instanceof Response)) { response = new Response(response); }
255
+ Observer.set(this.network, 'remote', false);
256
+ Observer.set(this.network, 'error', null);
257
+ if (['link', 'form'].includes(e.detail.srcType)) {
258
+ Observer.set(e.detail.src, 'active', false);
259
+ Observer.set(e.detail.submitter || {}, 'active', false);
260
+ }
261
+ if (response.redirected && this.isSameOrigin(response.url)) {
262
+ Observer.set(this.location, { href: response.url }, {
263
+ detail: { isRedirect: true },
264
+ });
265
+ } else {
266
+ let location = response.headers.get('Location');
267
+ if (location && response.status === this._xRedirectCode) {
268
+ Observer.set(this.network, 'redirecting', location);
269
+ window.location = location;
270
+ }
271
+ }
272
+ return response;
273
+ }
233
274
 
234
275
  /**
235
276
  * Checks if an URL is same origin.
@@ -0,0 +1,114 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import { Observer } from './Runtime.js';
6
+ import WorkerComm from './WorkerComm.js';
7
+ import Router from './Router.js';
8
+
9
+ export default class RuntimeClient {
10
+
11
+ /**
12
+ * RuntimeClient
13
+ *
14
+ * @param Context cx
15
+ */
16
+ constructor(cx) {
17
+ this.cx = cx;
18
+ const workerComm = new WorkerComm('/worker.js', { startMessages: true });
19
+ Observer.observe(workerComm, changes => {
20
+ //console.log('SERVICE_WORKER_STATE_CHANGE', changes[0].name, changes[0].value);
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Handles HTTP events.
26
+ *
27
+ * @param HttpEvent httpEvent
28
+ * @param Function remoteFetch
29
+ *
30
+ * @return Response
31
+ */
32
+ async handle(httpEvent, remoteFetch) {
33
+ // The app router
34
+ const router = new Router(this.cx, httpEvent.url.pathname);
35
+ const handle = async () => {
36
+ // --------
37
+ // ROUTE FOR DATA
38
+ // --------
39
+ let httpMethodName = httpEvent.request.method.toLowerCase();
40
+ let response = await router.route([httpMethodName === 'delete' ? 'del' : httpMethodName, 'default'], httpEvent, {}, async event => {
41
+ return remoteFetch(event.request);
42
+ }, remoteFetch);
43
+ if (!(response instanceof httpEvent.Response)) {
44
+ response = new httpEvent.Response(response);
45
+ }
46
+
47
+ // --------
48
+ // Rendering
49
+ // --------
50
+ if (response.ok && response.headers.contentType === 'application/json') {
51
+ await this.render(httpEvent, response, router);
52
+ await this.scrollIntoView(httpEvent);
53
+ } else if (!response.ok) {
54
+ await this.unrender();
55
+ }
56
+
57
+ return response;
58
+ };
59
+
60
+ // --------
61
+ // PIPE THROUGH MIDDLEWARES
62
+ // --------
63
+ return (this.cx.middlewares || []).concat(handle).reverse().reduce((next, fn) => {
64
+ return () => fn.call(this.cx, httpEvent, router, next);
65
+ }, null)();
66
+ }
67
+
68
+ // Renderer
69
+ async render(httpEvent, response, router) {
70
+ let data = await response.json();
71
+ return router.route('render', httpEvent, data, async (httpEvent, data) => {
72
+ // --------
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: (httpEvent.detail || {}).srcType === 'init',
79
+ network: this.cx.runtime.network,
80
+ url: this.cx.runtime.location,
81
+ }, { update: true });
82
+ }
83
+ window.document.setState({ page: data }, { update: 'merge' });
84
+ window.document.body.setAttribute('template', 'page/' + httpEvent.url.pathname.split('/').filter(a => a).map(a => a + '+-').join('/'));
85
+ await new Promise(res => (window.document.templatesReadyState === 'complete' && res(), window.document.addEventListener('templatesreadystatechange', res)));
86
+ return true;
87
+ });
88
+ }
89
+
90
+ // Unrender
91
+ async unrender() {
92
+ window.document.setState({ page: {} }, { update: 'merge' });
93
+ window.document.body.setAttribute('template', '');
94
+ }
95
+
96
+ // Normalize scroll position
97
+ async scrollIntoView(httpEvent) {
98
+ if (!(httpEvent.detail.src instanceof Element)) return;
99
+ await new Promise(res => setTimeout(res, 10));
100
+ let viewportTop;
101
+ if (httpEvent.url.hash && (urlTarget = document.querySelector(httpEvent.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
+ await new Promise(res => setTimeout(res, 600));
109
+ document.documentElement.classList.remove('scroll-reset');
110
+ }
111
+ }
112
+
113
+ }
114
+