@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
@@ -6,30 +6,31 @@
6
6
  import { _isString, _isUndefined } from '@webqit/util/js/index.js';
7
7
  import { Observer } from './Runtime.js';
8
8
 
9
- export default function(persistent = false) {
9
+ export default function(namespace = null, persistent = false) {
10
10
 
11
11
  const storeType = persistent ? 'localStorage' : 'sessionStorage';
12
12
  if (!window[storeType]) {
13
- throw new Error(`The specified Web Storage API ${storeType} is invalid or not supported`)
13
+ throw new Error(`The specified Web Storage API ${storeType} is invalid or not supported`);
14
14
  }
15
15
 
16
16
  const _storage = {};
17
17
  Observer.intercept(_storage, (event, received, next) => {
18
- if (event.type === 'get' && _isString(event.name)) {
19
- const value = window[storeType].getItem(event.name);
18
+ const key = namespace ? `${namespace}.${event.name}` : event.name;
19
+ if (event.type === 'get' && _isString(key)) {
20
+ const value = window[storeType].getItem(key);
20
21
  return !_isUndefined(value) ? JSON.parse(value) : value;
21
22
  }
22
23
  if (event.type === 'set') {
23
- window[storeType].setItem(event.name, !_isUndefined(event.value) ? JSON.stringify(event.value) : event.value);
24
+ window[storeType].setItem(key, !_isUndefined(event.value) ? JSON.stringify(event.value) : event.value);
24
25
  return true;
25
26
  }
26
27
  if (event.type === 'deleteProperty') {
27
- window[storeType].removeItem(event.name);
28
+ window[storeType].removeItem(key);
28
29
  return true;
29
30
  }
30
31
  if (event.type === 'has') {
31
32
  for(var i = 0; i < window[storeType].length; i ++){
32
- if (window[storeType].key(i) === event.name) {
33
+ if (window[storeType].key(i) === key) {
33
34
  return true;
34
35
  }
35
36
  };
@@ -2,12 +2,8 @@
2
2
  /**
3
3
  * @imports
4
4
  */
5
- import _isArray from '@webqit/util/js/isArray.js';
6
- import _isObject from '@webqit/util/js/isObject.js';
7
- import _isTypeObject from '@webqit/util/js/isTypeObject.js';
8
- import _isString from '@webqit/util/js/isString.js';
9
- import _isEmpty from '@webqit/util/js/isEmpty.js';
10
- import _with from '@webqit/util/obj/with.js';
5
+ import { _with } from '@webqit/util/obj/index.js';
6
+ import { _isArray, _isObject, _isTypeObject, _isString, _isEmpty } from '@webqit/util/js/index.js';
11
7
  import { wwwFormUnserialize, wwwFormSerialize } from '../util.js';
12
8
  import { Observer } from './Runtime.js';
13
9
 
@@ -6,7 +6,7 @@
6
6
  import { _isFunction } from '@webqit/util/js/index.js';
7
7
  import { Observer } from './Runtime.js';
8
8
 
9
- export default class WorkerClient {
9
+ export default class WorkerComm {
10
10
 
11
11
  constructor(file, params = {}) {
12
12
  this.ready = navigator.serviceWorker.ready;
@@ -73,7 +73,7 @@ export default class WorkerClient {
73
73
  }
74
74
  return this.post;
75
75
  },
76
- receive: (callback, onAvailability = 1) => {
76
+ receive: callback => {
77
77
  navigator.serviceWorker.addEventListener('message', callback);
78
78
  return this.post;
79
79
  },
@@ -0,0 +1,242 @@
1
+
2
+ /**
3
+ * imports
4
+ */
5
+ import Fs from 'fs';
6
+ import Url from 'url';
7
+ import Path from 'path';
8
+ import Webpack from 'webpack';
9
+ import { _beforeLast } from '@webqit/util/str/index.js';
10
+ import { _isObject, _isArray } from '@webqit/util/js/index.js';
11
+ import * as DotJs from '@webqit/backpack/src/dotfiles/DotJs.js';
12
+
13
+ /**
14
+ * @generate
15
+ */
16
+ export async function generate() {
17
+ const cx = this || {};
18
+ // -----------
19
+ if (!cx.config.runtime?.Client) {
20
+ throw new Error(`The Client configurator "config.runtime.Client" is required in context.`);
21
+ }
22
+ const clientConfig = await (new cx.config.runtime.Client(cx)).read();
23
+ if (clientConfig.support_service_worker && !cx.config.runtime.client?.Worker) {
24
+ throw new Error(`The Service Worker configurator "config.runtime.client.Worker" is required in context.`);
25
+ }
26
+ const workerConfig = await (new cx.config.runtime.client.Worker(cx)).read();
27
+ // -----------
28
+ if (!cx.config.deployment?.Layout) {
29
+ throw new Error(`The Layout configurator "config.deployment.Layout" is required in context.`);
30
+ }
31
+ const layoutConfig = await (new cx.config.deployment.Layout(cx)).read();
32
+ const bundleOutput = { path: Path.resolve(cx.CWD || '', layoutConfig.PUBLIC_DIR), };
33
+ const dirSelf = Path.dirname(Url.fileURLToPath(import.meta.url)).replace(/\\/g, '/');
34
+ // -----------
35
+ // Generate client build
36
+ let genClient = getGen.call(cx, dirSelf, layoutConfig.CLIENT_DIR, clientConfig, `The Client Build.`);
37
+ if (clientConfig.support_oohtml) {
38
+ genClient.imports = { [`${dirSelf}/generate.oohtml.js`]: null, ...genClient.imports };
39
+ }
40
+ await bundle.call(cx, genClient, { ...bundleOutput, filename: 'bundle.js', }, true/* asModule */);
41
+ cx.logger && cx.logger.log('');
42
+ // -----------
43
+ // Generate worker build
44
+ if (clientConfig.support_service_worker) {
45
+ let genWorker = getGen.call(cx, `${dirSelf}/worker`, layoutConfig.WORKER_DIR, workerConfig, `The Worker Build.`);
46
+ await bundle.call(cx, genWorker, { ...bundleOutput, filename: 'worker.js', });
47
+ cx.logger && cx.logger.log('');
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Compile routes.
53
+ *
54
+ * @param string modulesDir
55
+ * @param string routesDir
56
+ * @param object paramsObj
57
+ * @param string desc
58
+ *
59
+ * @return Object
60
+ */
61
+ function getGen(modulesDir, routesDir, paramsObj, desc) {
62
+ const cx = this || {};
63
+ if (cx.logger) {
64
+ cx.logger.log(cx.logger.style.comment(`-----------------`));
65
+ cx.logger.log(desc);
66
+ cx.logger.log(cx.logger.style.comment(`-----------------`));
67
+ cx.logger.log('');
68
+ }
69
+ // ------------------
70
+ const gen = { imports: {}, code: [], };
71
+ // ------------------
72
+ // >> Modules import
73
+ gen.imports[`${modulesDir}/index.js`] = `{ start }`;
74
+ gen.code.push(``);
75
+ // ------------------
76
+ // >> Routes mapping
77
+ gen.code.push(`// >> Routes`);
78
+ declareRoutesObj.call(cx, gen, routesDir, 'layout', '');
79
+ gen.code.push(``);
80
+ // ------------------
81
+ // >> Params
82
+ gen.code.push(`// >> Params`);
83
+ declareParamsObj.call(cx, gen, paramsObj, 'params');
84
+ gen.code.push(``);
85
+ // ------------------
86
+ // >> Startup
87
+ gen.code.push(`// >> Startup`);
88
+ gen.code.push(`start.call({ layout, params })`);
89
+ return gen;
90
+ }
91
+
92
+ /**
93
+ * Compile routes.
94
+ *
95
+ * @param object gen
96
+ * @param string routesDir
97
+ * @param string varName
98
+ *
99
+ * @return void
100
+ */
101
+ function declareRoutesObj(gen, routesDir, varName) {
102
+ const cx = this || {};
103
+ cx.logger && cx.logger.log(`> Declaring routes...`);
104
+ // ----------------
105
+ // Directory walker
106
+ const walk = (dir, callback) => {
107
+ Fs.readdirSync(dir).forEach(f => {
108
+ let resource = Path.join(dir, f);
109
+ if (Fs.statSync(resource).isDirectory()) {
110
+ walk(resource, callback);
111
+ } else {
112
+ let ext = Path.extname(resource) || '';
113
+ callback(resource, ext);
114
+ }
115
+ });
116
+ };
117
+ // ----------------
118
+ // >> Routes mapping
119
+ gen.code.push(`const ${varName} = {};`);
120
+ let indexCount = 0;
121
+ if (routesDir && Fs.existsSync(routesDir)) {
122
+ let clientDirname = routesDir.replace(/\\/g, '/').split('/').pop();
123
+ walk(routesDir, (file, ext) => {
124
+ //relativePath = relativePath.replace(/\\/g, '/');
125
+ if (file.replace(/\\/g, '/').endsWith('/index.js')) {
126
+ let relativePath = Path.relative(routesDir, file).replace(/\\/g, '/');
127
+ // Import code
128
+ let routeName = 'index' + (++ indexCount);
129
+ // IMPORTANT: we;re taking a step back here so that the parent-child relationship for
130
+ // the directories be involved
131
+ gen.imports[`../${clientDirname}/${relativePath}`] = '* as ' + routeName;
132
+ // Definition code
133
+ let routePath = _beforeLast('/' + relativePath, '/index.js');
134
+ gen.code.push(`${varName}['${routePath || '/'}'] = ${routeName};`);
135
+ // Show
136
+ cx.logger && cx.logger.log(`> ./${relativePath}`);
137
+ }
138
+ });
139
+ }
140
+ if (!indexCount) {
141
+ cx.logger && cx.logger.log(`> (none)`);
142
+ }
143
+ cx.logger && cx.logger.log(``);
144
+ }
145
+
146
+ /**
147
+ * Compile params.
148
+ *
149
+ * @param object gen
150
+ * @param object paramsObj
151
+ * @param string varName
152
+ *
153
+ * @return void
154
+ */
155
+ function declareParamsObj(gen, paramsObj, varName = null, indentation = 0) {
156
+ const cx = this || {};
157
+ // ----------------
158
+ // Params compilation
159
+ if (varName) gen.code.push(`const ${varName} = {`);
160
+ _isArray(paramsObj)
161
+ Object.keys(paramsObj).forEach(name => {
162
+ let _name = ` ${' '.repeat(indentation)}${(_isArray(paramsObj) ? '' : (name.includes(' ') ? `'${name}'` : name) + ': ')}`;
163
+ if ([ 'boolean', 'number' ].includes(typeof paramsObj[name])) {
164
+ gen.code.push(`${_name}${paramsObj[name]},`);
165
+ } else if (_isArray(paramsObj[name])) {
166
+ gen.code.push(`${_name}[`);
167
+ declareParamsObj.call(cx, gen, paramsObj[name], null, indentation + 1);
168
+ gen.code.push(` ${' '.repeat(indentation)}],`);
169
+ } else if (_isObject(paramsObj[name])) {
170
+ gen.code.push(`${_name}{`);
171
+ declareParamsObj.call(cx, gen, paramsObj[name], null, indentation + 1);
172
+ gen.code.push(` ${' '.repeat(indentation)}},`);
173
+ } else {
174
+ gen.code.push(`${_name}'${paramsObj[name]}',`);
175
+ }
176
+ });
177
+ if (varName) gen.code.push(`};`);
178
+ }
179
+
180
+ /**
181
+ * Bundle generated file
182
+ *
183
+ * @param object gen
184
+ * @param object output
185
+ * @param boolean asModule
186
+ *
187
+ * @return Promise
188
+ */
189
+ function bundle(gen, output, asModule = false) {
190
+ const cx = this || {};
191
+ const moduleFile = Path.join(output.path, `${_beforeLast(output.filename, '.')}.esm.js`);
192
+ // ------------------
193
+ // >> Show waiting...
194
+ if (cx.logger) {
195
+ let waiting = cx.logger.waiting(cx.logger.f`Writing the ES module file: ${moduleFile}`);
196
+ waiting.start();
197
+ DotJs.write(gen, moduleFile, 'ES Module file');
198
+ waiting.stop();
199
+ cx.logger.info(cx.logger.f`The module file: ${moduleFile}`);
200
+ } else {
201
+ DotJs.write(gen, moduleFile, 'ES Module file');
202
+ }
203
+ // ----------------
204
+ // >> Webpack config
205
+ const bundlingConfig = { entry: moduleFile, output };
206
+ if (asModule) {
207
+ bundlingConfig.experiments = { outputModule: true, };
208
+ bundlingConfig.output.environment = bundlingConfig.output.environment || {};
209
+ if (!('module' in bundlingConfig.output)) {
210
+ bundlingConfig.output.module = true;
211
+ bundlingConfig.output.environment.module = true;
212
+ }
213
+ }
214
+ // ----------------
215
+ // The bundling process
216
+ return new Promise(resolve => {
217
+ let waiting;
218
+ if (cx.logger) {
219
+ waiting = cx.logger.waiting(`Bundling...`);
220
+ cx.logger.log('');
221
+ cx.logger.log('> Bundling...');
222
+ cx.logger.info(cx.logger.f`FROM: ${bundlingConfig.entry}`);
223
+ cx.logger.info(cx.logger.f`TO: ${bundlingConfig.output.path + '/' + bundlingConfig.output.filename}`);
224
+ cx.logger.log('');
225
+ waiting.start();
226
+ }
227
+ // Run
228
+ let compiler = Webpack(bundlingConfig);
229
+ compiler.run((err, stats) => {
230
+ waiting.stop();
231
+ if (err) {
232
+ cx.logger.title(`Errors!`);
233
+ cx.logger.error(err);
234
+ }
235
+ let log = stats.toString({ colors: true, });
236
+ cx.logger && cx.logger.log(log);
237
+ // Remove moduleFile build
238
+ //Fs.unlinkSync(bundlingConfig.entry);
239
+ resolve(log);
240
+ });
241
+ });
242
+ }
@@ -0,0 +1,7 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import { OOHTML } from '@webqit/oohtml-ssr/apis.js';
6
+
7
+ OOHTML.call( window );
@@ -0,0 +1,18 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import Context from './Context.js';
6
+ import RuntimeClient from './RuntimeClient.js';
7
+ import Runtime from './Runtime.js';
8
+
9
+ /**
10
+ * @start
11
+ */
12
+ export async function start(clientCallback = null) {
13
+ const cx = this || {};
14
+ const defaultClientCallback = _cx => new RuntimeClient(_cx);
15
+ return new Runtime(Context.create(cx), ( ...args ) => {
16
+ return clientCallback ? clientCallback( ...args.concat( defaultClientCallback ) ) : defaultClientCallback( ...args );
17
+ });
18
+ }
@@ -0,0 +1,27 @@
1
+
2
+ const {
3
+ URL,
4
+ fetch,
5
+ Headers,
6
+ Request,
7
+ Response,
8
+ FormData,
9
+ ReadableStream,
10
+ File,
11
+ Blob
12
+ } = globalThis;
13
+
14
+ /**
15
+ * @exports
16
+ */
17
+ export {
18
+ URL,
19
+ fetch,
20
+ Headers,
21
+ Request,
22
+ Response,
23
+ FormData,
24
+ ReadableStream,
25
+ File,
26
+ Blob,
27
+ }
@@ -0,0 +1,7 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import _Contex from '../Context.js';
6
+
7
+ export default class Context extends _Contex {}
@@ -0,0 +1,243 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import _isGlobe from 'is-glob';
6
+ import Minimatch from 'minimatch';
7
+ import { _any } from '@webqit/util/arr/index.js';
8
+ import { _after, _afterLast } from '@webqit/util/str/index.js';
9
+ import { HttpEvent, Request, Response } from '../Runtime.js';
10
+ import { Observer } from '../Runtime.js';
11
+
12
+ /**
13
+ * ---------------------------
14
+ * The Worker Initializer
15
+ * ---------------------------
16
+ */
17
+
18
+ export default class Worker {
19
+
20
+ /**
21
+ * Runtime
22
+ *
23
+ * @param Object cx
24
+ * @param Function clientCallback
25
+ *
26
+ * @return void
27
+ */
28
+ constructor(cx, clientCallback) {
29
+
30
+ // ---------------
31
+ this.cx = cx;
32
+ this.clients = new Map;
33
+ this.mockSessionStore = {};
34
+ // ---------------
35
+ this.cx.runtime = this;
36
+ let client = clientCallback(this.cx, '*');
37
+ if (!client || !client.handle) throw new Error(`Application instance must define a ".handle()" method.`);
38
+ this.clients.set('*', client);
39
+
40
+ // -------------
41
+ // ONINSTALL
42
+ self.addEventListener('install', evt => {
43
+ if (this.cx.params.skip_waiting) { self.skipWaiting(); }
44
+ // Manage CACHE
45
+ if (this.cx.params.cache_name && (this.cx.params.cache_only_urls || []).length) {
46
+ // Add files to cache
47
+ evt.waitUntil( self.caches.open(this.cx.params.cache_name).then(cache => {
48
+ if (this.cx.logger) { this.cx.logger.log('[ServiceWorker] Pre-caching resources.'); }
49
+ const cache_only_urls = (this.cx.params.cache_only_urls || []).map(c => c.trim()).filter(c => c);
50
+ return cache.addAll(cache_only_urls.filter(url => !_isGlobe(url) && !_afterLast(url, '.').includes('/')));
51
+ }) );
52
+ }
53
+ });
54
+
55
+ // -------------
56
+ // ONACTIVATE
57
+ self.addEventListener('activate', evt => {
58
+ evt.waitUntil( new Promise(async resolve => {
59
+ if (this.cx.params.skip_waiting) { await self.clients.claim(); }
60
+ // Manage CACHE
61
+ if (this.cx.params.cache_name) {
62
+ // Clear outdated CACHES
63
+ await self.caches.keys().then(keyList => {
64
+ return Promise.all(keyList.map(key => {
65
+ if (key !== this.cx.params.cache_name && key !== this.cx.params.cache_name + '_json') {
66
+ if (this.cx.logger) { this.cx.logger.log('[ServiceWorker] Removing old cache:', key); }
67
+ return self.caches.delete(key);
68
+ }
69
+ }));
70
+ })
71
+ }
72
+ resolve();
73
+ }) );
74
+ });
75
+
76
+ // -------------
77
+ // ONFETCH
78
+ self.addEventListener('fetch', async evt => {
79
+ // URL schemes that might arrive here but not supported; e.g.: chrome-extension://
80
+ if (!evt.request.url.startsWith('http')) return;
81
+ const requestInit = [
82
+ 'method', 'headers', 'body', 'mode', 'credentials', 'cache', 'redirect', 'referrer', 'integrity',
83
+ ].reduce((init, prop) => ({ [prop]: evt.request[prop], ...init }), {});
84
+ evt.respondWith(this.go(evt.request.url, requestInit, { event: evt }));
85
+ });
86
+
87
+ // ---------------
88
+ Observer.set(this, 'location', {});
89
+ Observer.set(this, 'network', {});
90
+ // ---------------
91
+ }
92
+
93
+ /**
94
+ * Performs a request.
95
+ *
96
+ * @param object|string url
97
+ * @param object init
98
+ * @param object detail
99
+ *
100
+ * @return Response
101
+ */
102
+ async go(url, init = {}, detail = {}) {
103
+ // ------------
104
+ url = typeof url === 'string' ? new URL(url) : url;
105
+ init = { referrer: this.location.href, ...init };
106
+ // ------------
107
+ // The request object
108
+ let request = this.generateRequest(url.href, init);
109
+ // The navigation event
110
+ let httpEvent = new HttpEvent(request, detail, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
111
+ httpEvent.port.listen(message => {
112
+ if (message.$type === 'handler:hints' && message.session) {
113
+ // TODO: Sync sesseion data from client
114
+ return Promise.resolve();
115
+ }
116
+ });
117
+ // Response
118
+ let response;
119
+ if (httpEvent.request.url.startsWith(self.origin)/* && httpEvent.request.mode === 'navigate'*/) {
120
+ response = await this.clients.get('*').handle(httpEvent, (...args) => this.remoteFetch(...args));
121
+ } else {
122
+ response = await this.remoteFetch(httpEvent.request);
123
+ }
124
+ let finalResponse = this.handleResponse(httpEvent, response);
125
+ // Return value
126
+ return finalResponse;
127
+ }
128
+
129
+ // Generates request object
130
+ generateRequest(href, init) {
131
+ // Now, the following is key:
132
+ // The browser likes to use "force-cache" for "navigate" requests
133
+ // when, for example, the back button was used.
134
+ // Thus the origin server would still not be contacted by the self.fetch() below, leading to inconsistencies in responses.
135
+ // So, we detect this scenerio and avoid it.
136
+ if (init.mode === 'navigate' && init.cache === 'force-cache') {
137
+ init = { ...init, cache: 'default' };
138
+ }
139
+ let request = new Request(href, init);
140
+ return request;
141
+ }
142
+
143
+ // Generates session object
144
+ getSession(e, id = null, persistent = false) {
145
+ return {
146
+ get: () => this.mockSessionStore,
147
+ set: value => { this.mockSessionStore = value },
148
+ };
149
+ }
150
+
151
+ // Initiates remote fetch and sets the status
152
+ remoteFetch(request) {
153
+ const execFetch = () => {
154
+ if (_any((this.cx.params.cache_only_urls || []).map(c => c.trim()).filter(c => c), pattern => Minimatch.Minimatch(request.url, pattern))) {
155
+ Observer.set(this.network, 'strategy', 'cache-only');
156
+ return this.cacheFetch(request, { networkFallback: false, cacheRefresh: false });
157
+ }
158
+ // network_only_urls
159
+ if (_any((this.cx.params.network_only_urls || []).map(c => c.trim()).filter(c => c), pattern => Minimatch.Minimatch(request.url, pattern))) {
160
+ Observer.set(this.network, 'strategy', 'network-only');
161
+ return this.networkFetch(request, { cacheFallback: false, cacheRefresh: false });
162
+ }
163
+ // cache_first_urls
164
+ if (_any((this.cx.params.cache_first_urls || []).map(c => c.trim()).filter(c => c), pattern => Minimatch.Minimatch(request.url, pattern))) {
165
+ Observer.set(this.network, 'strategy', 'cache-first');
166
+ return this.cacheFetch(request, { networkFallback: true, cacheRefresh: true });
167
+ }
168
+ Observer.set(this.network, 'strategy', 'network-first');
169
+ return this.networkFetch(request, { cacheFallback: true, cacheRefresh: true });
170
+ };
171
+ let response = execFetch(request);
172
+ // This catch() is NOT intended to handle failure of the fetch
173
+ response.catch(e => Observer.set(this.network, 'error', e.message));
174
+ // Return xResponse
175
+ return response.then(_response => new Response(_response));
176
+ }
177
+
178
+ // Caching strategy: cache_first
179
+ cacheFetch(request, params = {}) {
180
+ return this.getRequestCache(request).then(cache => cache.match(request).then(response => {
181
+ // Nothing cache, use network
182
+ if (!response && params.networkFallback) return this.networkFetch(request, { ...params, cacheFallback: false });
183
+ // Note: fetch, but for refreshing purposes only... not the returned response
184
+ if (response && params.cacheRefresh) this.networkFetch(request, { ...params, justRefreshing: true });
185
+ Observer.set(this.network, 'cache', true);
186
+ return response;
187
+ }));
188
+ }
189
+
190
+ // Caching strategy: network_first
191
+ networkFetch(request, params = {}) {
192
+ if (params.forceNetwork) {
193
+ let url = new URL(request.url);
194
+ url.searchParams.set('$force-cache', '1');
195
+ request.attr.url = url.toString();
196
+ }
197
+ if (!params.cacheFallback) {
198
+ Observer.set(this.network, 'remote', true);
199
+ return self.fetch(request);
200
+ }
201
+ return self.fetch(request).then(response => {
202
+ if (params.cacheRefresh) this.refreshCache(request, response);
203
+ Observer.set(this.network, 'remote', true);
204
+ return response;
205
+ }).catch(() => this.getRequestCache(request).then(cache => {
206
+ Observer.set(this.network, 'cache', true);
207
+ return cache.match(request);
208
+ }));
209
+ }
210
+
211
+ // Caches response
212
+ refreshCache(request, response) {
213
+ // Check if we received a valid response
214
+ if (request.method !== 'GET' || !response || response.status !== 200 || (response.type !== 'basic' && response.type !== 'cors')) {
215
+ return response;
216
+ }
217
+ // IMPORTANT: Clone the response. A response is a stream
218
+ // and because we want the browser to consume the response
219
+ // as well as the cache consuming the response, we need
220
+ // to clone it so we have two streams.
221
+ var responseToCache = response.clone();
222
+ this.getRequestCache(request).then(cache => {
223
+ Observer.set(this.network, 'cacheRefresh', true);
224
+ cache.put(request, responseToCache);
225
+ });
226
+ return response;
227
+ }
228
+
229
+ // Returns either the regular cache or a json-specific cache
230
+ getRequestCache(request) {
231
+ let cacheName = request.headers.get('Accept') === 'application/json'
232
+ ? this.cx.params.cache_name + '_json'
233
+ : this.cx.params.cache_name;
234
+ return self.caches.open(cacheName);
235
+ }
236
+
237
+ // Handles response object
238
+ handleResponse(e, response) {
239
+ if (!(response instanceof Response)) { response = new Response(response); }
240
+ return response;
241
+ }
242
+
243
+ }
@@ -0,0 +1,46 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import Router from '../Router.js';
6
+
7
+ export default class WorkerClient {
8
+
9
+ /**
10
+ * WorkerClient
11
+ *
12
+ * @param Context cx
13
+ */
14
+ constructor(cx) {
15
+ this.cx = cx;
16
+ }
17
+
18
+ /**
19
+ * Handles HTTP events.
20
+ *
21
+ * @param HttpEvent httpEvent
22
+ * @param Function remoteFetch
23
+ *
24
+ * @return Response
25
+ */
26
+ async handle(httpEvent, remoteFetch) {
27
+ // The app router
28
+ const router = new Router(this.cx, httpEvent.url.pathname);
29
+ const handle = async () => {
30
+ // --------
31
+ // ROUTE FOR DATA
32
+ // --------
33
+ let httpMethodName = httpEvent.request.method.toLowerCase();
34
+ let response = await router.route([httpMethodName === 'delete' ? 'del' : httpMethodName, 'default'], httpEvent, {}, async event => {
35
+ return remoteFetch(event.request);
36
+ }, remoteFetch);
37
+ if (!(response instanceof httpEvent.Response)) {
38
+ response = new httpEvent.Response(response);
39
+ }
40
+ return response;
41
+ };
42
+ return handle();
43
+ }
44
+
45
+ }
46
+
@@ -0,0 +1,18 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import Context from './Context.js';
6
+ import WorkerClient from './WorkerClient.js';
7
+ import Worker from './Worker.js';
8
+
9
+ /**
10
+ * @start
11
+ */
12
+ export async function start(clientCallback = null) {
13
+ const cx = this || {};
14
+ const defaultClientCallback = _cx => new WorkerClient(_cx);
15
+ return new Worker(Context.create(cx), ( ...args ) => {
16
+ return clientCallback ? clientCallback( ...args.concat( defaultClientCallback ) ) : defaultClientCallback( ...args );
17
+ });
18
+ }