@webqit/webflo 0.10.3 → 0.11.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 (60) hide show
  1. package/README.md +1509 -3
  2. package/bundle.html.json +1665 -0
  3. package/package.json +3 -3
  4. package/src/Context.js +8 -4
  5. package/src/config-pi/deployment/Env.js +2 -2
  6. package/src/config-pi/deployment/Layout.js +2 -2
  7. package/src/config-pi/deployment/Origins.js +2 -2
  8. package/src/config-pi/deployment/Virtualization.js +2 -2
  9. package/src/config-pi/runtime/Client.js +39 -11
  10. package/src/config-pi/runtime/Server.js +26 -10
  11. package/src/config-pi/runtime/client/Worker.js +32 -14
  12. package/src/config-pi/runtime/server/Headers.js +2 -2
  13. package/src/config-pi/runtime/server/Redirects.js +2 -2
  14. package/src/config-pi/static/Manifest.js +2 -2
  15. package/src/config-pi/static/Ssg.js +2 -2
  16. package/src/runtime-pi/Router.js +1 -1
  17. package/src/runtime-pi/client/Runtime.js +116 -62
  18. package/src/runtime-pi/client/RuntimeClient.js +28 -43
  19. package/src/runtime-pi/client/Workport.js +163 -0
  20. package/src/runtime-pi/client/generate.js +285 -77
  21. package/src/runtime-pi/client/{generate.oohtml.js → oohtml/full.js} +0 -0
  22. package/src/runtime-pi/client/oohtml/namespacing.js +7 -0
  23. package/src/runtime-pi/client/oohtml/scripting.js +8 -0
  24. package/src/runtime-pi/client/oohtml/templating.js +8 -0
  25. package/src/runtime-pi/client/worker/Worker.js +58 -24
  26. package/src/runtime-pi/client/worker/Workport.js +80 -0
  27. package/src/runtime-pi/server/Router.js +2 -2
  28. package/src/runtime-pi/server/Runtime.js +30 -11
  29. package/src/runtime-pi/server/RuntimeClient.js +24 -14
  30. package/src/runtime-pi/util.js +2 -2
  31. package/src/webflo.js +7 -9
  32. package/test/site/package.json +9 -0
  33. package/test/site/public/bundle.html +6 -0
  34. package/test/site/public/bundle.html.json +4 -0
  35. package/test/site/public/bundle.js +2 -0
  36. package/test/site/public/bundle.js.gz +0 -0
  37. package/test/site/public/bundle.webflo.js +15 -0
  38. package/test/site/public/bundle.webflo.js.gz +0 -0
  39. package/test/site/public/index.html +30 -0
  40. package/test/site/public/index1.html +35 -0
  41. package/test/site/public/page-2/bundle.html +5 -0
  42. package/test/site/public/page-2/bundle.html.json +1 -0
  43. package/test/site/public/page-2/bundle.js +2 -0
  44. package/test/site/public/page-2/bundle.js.gz +0 -0
  45. package/test/site/public/page-2/index.html +46 -0
  46. package/test/site/public/page-2/logo-130x130.png +0 -0
  47. package/test/site/public/page-2/main.html +3 -0
  48. package/test/site/public/page-3/logo-130x130.png +0 -0
  49. package/test/site/public/page-4/subpage/bundle.html +0 -0
  50. package/test/site/public/page-4/subpage/bundle.html.json +1 -0
  51. package/test/site/public/page-4/subpage/bundle.js +2 -0
  52. package/test/site/public/page-4/subpage/bundle.js.gz +0 -0
  53. package/test/site/public/page-4/subpage/index.html +31 -0
  54. package/test/site/public/sparoots.json +5 -0
  55. package/test/site/public/worker.js +3 -0
  56. package/test/site/public/worker.js.gz +0 -0
  57. package/test/site/server/index.js +16 -0
  58. package/src/Cli.js +0 -131
  59. package/src/Configurator.js +0 -97
  60. package/src/runtime-pi/client/WorkerComm.js +0 -102
@@ -16,6 +16,7 @@ import xRequest from "../xRequest.js";
16
16
  import xResponse from "../xResponse.js";
17
17
  import xfetch from '../xfetch.js';
18
18
  import xHttpEvent from '../xHttpEvent.js';
19
+ import Workport from './Workport.js';
19
20
 
20
21
  const URL = xURL(whatwag.URL);
21
22
  const FormData = xFormData(whatwag.FormData);
@@ -98,8 +99,7 @@ export default class Runtime {
98
99
  window.addEventListener('click', e => {
99
100
  var anchor = e.target.closest('a');
100
101
  if (!anchor || !anchor.href) return;
101
- if (!anchor.target && !anchor.download && (!anchor.origin || anchor.origin === this.location.origin)) {
102
- if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
102
+ if (!anchor.target && !anchor.download && this.isSpaRoute(anchor, e)) {
103
103
  // Publish everything, including hash
104
104
  this.go(Url.copy(anchor), {}, { src: anchor, srcType: 'link', });
105
105
  // URLs with # will cause a natural navigation
@@ -129,8 +129,7 @@ export default class Runtime {
129
129
  actionEl.href = submitParams.action;
130
130
  // ---------------
131
131
  // If not targeted and same origin...
132
- if (!submitParams.target && (!actionEl.origin || actionEl.origin === this.location.origin)) {
133
- if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
132
+ if (!submitParams.target && this.isSpaRoute(actionEl, e)) {
134
133
  // Build data
135
134
  var formData = new FormData(form);
136
135
  if ((submitter || {}).name) {
@@ -164,6 +163,27 @@ export default class Runtime {
164
163
  window.addEventListener('online', () => Observer.set(this.network, 'online', navigator.onLine));
165
164
  window.addEventListener('offline', () => Observer.set(this.network, 'online', navigator.onLine));
166
165
 
166
+ // -----------------------
167
+ // Service Worker && COMM
168
+ if (this.cx.service_worker_support) {
169
+ let workport = new Workport(this.cx.worker_filename, { scope: this.cx.worker_scope, startMessages: true });
170
+ Observer.set(this, 'workport', workport);
171
+ workport.messaging.listen(async evt => {
172
+ let responsePort = evt.ports[0];
173
+ let client = this.clients.get('*');
174
+ let response = client.alert && await client.alert(evt);
175
+ if (responsePort) {
176
+ if (response instanceof Promise) {
177
+ response.then(data => {
178
+ responsePort.postMessage(data);
179
+ });
180
+ } else {
181
+ responsePort.postMessage(response);
182
+ }
183
+ }
184
+ });
185
+ }
186
+
167
187
  // ---------------
168
188
  this.go(this.location, {}, { srcType: 'init' });
169
189
  // ---------------
@@ -176,6 +196,43 @@ export default class Runtime {
176
196
  return window.history;
177
197
  }
178
198
 
199
+ // Check is-route
200
+ isSpaRoute(url, e) {
201
+ url = typeof url === 'string' ? new whatwag.URL(url) : url;
202
+ if (url.origin && url.origin !== this.location.origin) return false;
203
+ if (e && (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)) return false;
204
+ if (!this.cx.params.routing) return true;
205
+ if (this.cx.params.routing.targets === false/** explicit false means disabled */) return false;
206
+ let b = url.pathname.split('/').filter(s => s);
207
+ const match = a => {
208
+ a = a.split('/').filter(s => s);
209
+ return a.reduce((prev, s, i) => prev && (s === b[i] || [s, b[i]].includes('-')), true);
210
+ };
211
+ return match(this.cx.params.routing.root) && this.cx.params.routing.subroots.reduce((prev, subroot) => {
212
+ return prev && !match(subroot);
213
+ }, true);
214
+ }
215
+
216
+ // Generates request object
217
+ generateRequest(href, init) {
218
+ return new Request(href, {
219
+ signal: this._abortController.signal,
220
+ ...init,
221
+ headers: {
222
+ 'Accept': 'application/json',
223
+ 'X-Redirect-Policy': 'manual-when-cross-spa',
224
+ 'X-Redirect-Code': this._xRedirectCode,
225
+ 'X-Powered-By': '@webqit/webflo',
226
+ ...(init.headers || {}),
227
+ },
228
+ });
229
+ }
230
+
231
+ // Generates session object
232
+ getSession(e, id = null, persistent = false) {
233
+ return Storage(id, persistent);
234
+ }
235
+
179
236
  /**
180
237
  * Performs a request.
181
238
  *
@@ -192,7 +249,7 @@ export default class Runtime {
192
249
  // Put his forward before instantiating a request and aborting previous
193
250
  // Same-page hash-links clicks on chrome recurse here from histroy popstate
194
251
  if (detail.srcType !== 'init' && (_before(url.href, '#') === _before(init.referrer, '#') && (init.method || 'GET').toUpperCase() === 'GET')) {
195
- return;
252
+ return new Response(null, { status: 304 }); // Not Modified
196
253
  }
197
254
  // ------------
198
255
  if (this._abortController) {
@@ -201,55 +258,77 @@ export default class Runtime {
201
258
  this._abortController = new AbortController();
202
259
  this._xRedirectCode = 200;
203
260
  // ------------
261
+ // States
262
+ // ------------
263
+ Observer.set(this.network, 'error', null);
264
+ Observer.set(this.network, 'requesting', { ...init, ...detail });
204
265
  if (['link', 'form'].includes(detail.srcType)) {
205
- Observer.set(detail.src, 'active', true);
206
- Observer.set(detail.submitter || {}, 'active', true);
266
+ detail.src.state && (detail.src.state.active = true);
267
+ detail.submitter && detail.submitter.state && (detail.submitter.state.active = true);
207
268
  }
208
269
  // ------------
209
- Observer.set(this.location, url, { detail: { ...init, ...detail }, });
210
- Observer.set(this.network, 'redirecting', null);
211
- // ------------
270
+ // Run
271
+ // ------------
212
272
  // The request object
213
273
  let request = this.generateRequest(url.href, init);
214
274
  // The navigation event
215
275
  let httpEvent = new HttpEvent(request, detail, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
216
276
  // Response
217
- let response = await this.clients.get('*').handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
218
- let finalResponse = this.handleResponse(httpEvent, response);
277
+ let client = this.clients.get('*'), response, finalResponse;
278
+ try {
279
+ // ------------
280
+ // Response
281
+ // ------------
282
+ response = await client.handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
283
+ finalResponse = this.handleResponse(httpEvent, response);
284
+ // ------------
285
+ // Address bar
286
+ // ------------
287
+ if (response.redirected) {
288
+ Observer.set(this.location, { href: response.url }, { detail: { redirected: true }, });
289
+ } else if (![302, 301].includes(finalResponse.status)) {
290
+ Observer.set(this.location, url);
291
+ }
292
+ // ------------
293
+ // States
294
+ // ------------
295
+ Observer.set(this.network, 'requesting', null);
296
+ if (['link', 'form'].includes(detail.srcType)) {
297
+ detail.src.state && (detail.src.state.active = false);
298
+ detail.submitter && detail.submitter.state && (detail.submitter.state.active = false);
299
+ }
300
+ // ------------
301
+ // Rendering
302
+ // ------------
303
+ if (finalResponse.ok && finalResponse.headers.contentType === 'application/json') {
304
+ client.render && await client.render(httpEvent, finalResponse);
305
+ } else if (!finalResponse.ok) {
306
+ if ([404, 500].includes(finalResponse.status)) {
307
+ Observer.set(this.network, 'error', new Error(finalResponse.statusText, { cause: finalResponse.status }));
308
+ }
309
+ client.unrender && await client.unrender(httpEvent);
310
+ }
311
+ } catch(e) {
312
+ console.error(e);
313
+ Observer.set(this.network, 'error', { ...e, retry: () => this.go(url, init = {}, detail) });
314
+ finalResponse = new Response(null, { status: 500, statusText: e.message });
315
+ }
316
+ // ------------
219
317
  // Return value
220
318
  return finalResponse;
221
319
  }
222
320
 
223
- // Generates request object
224
- generateRequest(href, init) {
225
- return new Request(href, {
226
- signal: this._abortController.signal,
227
- ...init,
228
- headers: {
229
- 'Accept': 'application/json',
230
- 'X-Redirect-Policy': 'manual-when-cross-origin',
231
- 'X-Redirect-Code': this._xRedirectCode,
232
- 'X-Powered-By': '@webqit/webflo',
233
- ...(init.headers || {}),
234
- },
235
- });
236
- }
237
-
238
- // Generates session object
239
- getSession(e, id = null, persistent = false) {
240
- return Storage(id, persistent);
241
- }
242
-
243
321
  // Initiates remote fetch and sets the status
244
322
  remoteFetch(request, ...args) {
245
- Observer.set(this.network, 'remote', true);
323
+ let href = typeof request === 'string' ? request : (request.url || request.href);
324
+ Observer.set(this.network, 'remote', href);
246
325
  let _response = fetch(request, ...args);
247
326
  // This catch() is NOT intended to handle failure of the fetch
248
- _response.catch(e => Observer.set(this.network, 'error', e.message));
327
+ _response.catch(e => Observer.set(this.network, 'error', e));
249
328
  // Return xResponse
250
329
  return _response.then(async response => {
251
330
  // Stop loading status
252
- Observer.set(this.network, 'remote', false);
331
+ Observer.set(this.network, 'remote', null);
253
332
  return new Response(response);
254
333
  });
255
334
  }
@@ -257,19 +336,10 @@ export default class Runtime {
257
336
  // Handles response object
258
337
  handleResponse(e, response) {
259
338
  if (!(response instanceof Response)) { response = new Response(response); }
260
- Observer.set(this.network, 'remote', false);
261
- Observer.set(this.network, 'error', null);
262
- if (['link', 'form'].includes(e.detail.srcType)) {
263
- Observer.set(e.detail.src, 'active', false);
264
- Observer.set(e.detail.submitter || {}, 'active', false);
265
- }
266
- if (response.redirected && this.isSameOrigin(response.url)) {
267
- Observer.set(this.location, { href: response.url }, {
268
- detail: { isRedirect: true },
269
- });
270
- } else {
339
+ if (!response.redirected) {
271
340
  let location = response.headers.get('Location');
272
341
  if (location && response.status === this._xRedirectCode) {
342
+ response.attrs.status = parseInt(response.headers.get('X-Redirect-Code'));
273
343
  Observer.set(this.network, 'redirecting', location);
274
344
  window.location = location;
275
345
  }
@@ -277,20 +347,4 @@ export default class Runtime {
277
347
  return response;
278
348
  }
279
349
 
280
- /**
281
- * Checks if an URL is same origin.
282
- *
283
- * @param object|string url
284
- *
285
- * @return Bool
286
- */
287
- isSameOrigin(url) {
288
- if (typeof url === 'string') {
289
- let href = url;
290
- url = window.document.createElement('a');
291
- url.href = href
292
- }
293
- return !url.origin || url.origin === this.location.origin;
294
- }
295
-
296
350
  }
@@ -2,8 +2,6 @@
2
2
  /**
3
3
  * @imports
4
4
  */
5
- import { Observer } from './Runtime.js';
6
- import WorkerComm from './WorkerComm.js';
7
5
  import Router from './Router.js';
8
6
 
9
7
  export default class RuntimeClient {
@@ -15,12 +13,6 @@ export default class RuntimeClient {
15
13
  */
16
14
  constructor(cx) {
17
15
  this.cx = cx;
18
- if (this.cx.support_service_worker) {
19
- const workerComm = new WorkerComm(this.cx.worker_filename, { scope: this.cx.worker_scope, startMessages: true });
20
- Observer.observe(workerComm, changes => {
21
- //console.log('SERVICE_WORKER_STATE_CHANGE', changes[0].name, changes[0].value);
22
- });
23
- }
24
16
  }
25
17
 
26
18
  /**
@@ -39,60 +31,53 @@ export default class RuntimeClient {
39
31
  // ROUTE FOR DATA
40
32
  // --------
41
33
  let httpMethodName = httpEvent.request.method.toLowerCase();
42
- let response = await router.route([httpMethodName === 'delete' ? 'del' : httpMethodName, 'default'], httpEvent, {}, async event => {
34
+ return router.route([httpMethodName === 'delete' ? 'del' : httpMethodName, 'default'], httpEvent, {}, async event => {
43
35
  return remoteFetch(event.request);
44
36
  }, remoteFetch);
45
- if (!(response instanceof httpEvent.Response)) {
46
- response = new httpEvent.Response(response);
47
- }
48
-
49
- // --------
50
- // Rendering
51
- // --------
52
- if (response.ok && response.headers.contentType === 'application/json') {
53
- await this.render(httpEvent, response, router);
54
- await this.scrollIntoView(httpEvent);
55
- } else if (!response.ok) {
56
- await this.unrender();
57
- }
58
-
59
- return response;
60
37
  };
61
-
62
38
  // --------
63
39
  // PIPE THROUGH MIDDLEWARES
64
40
  // --------
65
- return (this.cx.middlewares || []).concat(handle).reverse().reduce((next, fn) => {
66
- return () => fn.call(this.cx, httpEvent, router, next);
67
- }, null)();
41
+ return await (this.cx.middlewares || []).concat(handle).reverse().reduce((next, fn) => {
42
+ return () => fn.call(this.cx, httpEvent, router, next);
43
+ }, null)();
68
44
  }
69
45
 
70
46
  // Renderer
71
- async render(httpEvent, response, router) {
47
+ async render(httpEvent, response) {
72
48
  let data = await response.json();
49
+ const router = new Router(this.cx, httpEvent.url.pathname);
73
50
  return router.route('render', httpEvent, data, async (httpEvent, data) => {
74
51
  // --------
75
52
  // OOHTML would waiting for DOM-ready in order to be initialized
76
- await new Promise(res => window.WebQit.DOM.ready(res));
77
- if (!window.document.state.env) {
78
- window.document.setState({
79
- env: 'client',
80
- onHydration: (httpEvent.detail || {}).srcType === 'init',
81
- network: this.cx.runtime.network,
82
- url: this.cx.runtime.location,
83
- }, { update: true });
53
+ if (window.WebQit.DOM) {
54
+ await new Promise(res => window.WebQit.DOM.ready(res));
84
55
  }
85
- window.document.setState({ page: data }, { update: 'merge' });
86
- window.document.body.setAttribute('template', 'page/' + httpEvent.url.pathname.split('/').filter(a => a).map(a => a + '+-').join('/'));
87
- await new Promise(res => (window.document.templatesReadyState === 'complete' && res(), window.document.addEventListener('templatesreadystatechange', res)));
56
+ if (window.document.state) {
57
+ if (!window.document.state.env) {
58
+ window.document.setState({
59
+ env: 'client',
60
+ onHydration: (httpEvent.detail || {}).srcType === 'init',
61
+ network: this.cx.runtime.network,
62
+ url: this.cx.runtime.location,
63
+ }, { update: true });
64
+ }
65
+ window.document.setState({ data }, { update: 'merge' });
66
+ }
67
+ if (window.document.templates) {
68
+ window.document.body.setAttribute('template', 'routes/' + httpEvent.url.pathname.split('/').filter(a => a).map(a => a + '+-').join('/'));
69
+ await new Promise(res => (window.document.templatesReadyState === 'complete' && res(), window.document.addEventListener('templatesreadystatechange', res)));
70
+ }
71
+ await this.scrollIntoView(httpEvent);
88
72
  return window;
89
73
  });
90
74
  }
91
75
 
92
76
  // Unrender
93
- async unrender() {
94
- window.document.setState({ page: {} }, { update: 'merge' });
95
- window.document.body.setAttribute('template', '');
77
+ async unrender(httpEvent) {
78
+ if (window.document.state) {
79
+ window.document.setState({ data: {} }, { update: 'merge' });
80
+ }
96
81
  }
97
82
 
98
83
  // Normalize scroll position
@@ -0,0 +1,163 @@
1
+
2
+
3
+ /**
4
+ * @imports
5
+ */
6
+ import { _isFunction, _isObject } from '@webqit/util/js/index.js';
7
+ import { Observer } from './Runtime.js';
8
+
9
+ export default class Workport {
10
+
11
+ constructor(file, params = {}) {
12
+ this.ready = navigator.serviceWorker.ready;
13
+
14
+ // --------
15
+ // Registration and lifecycle
16
+ // --------
17
+ this.registration = new Promise((resolve, reject) => {
18
+ const register = () => {
19
+ navigator.serviceWorker.register(file, { scope: params.scope || '/' }).then(async registration => {
20
+
21
+ // Helper that updates instance's state
22
+ const state = target => {
23
+ // instance2.state can be any of: "installing", "installed", "activating", "activated", "redundant"
24
+ const equivState = target.state === 'installed' ? 'waiting' :
25
+ (target.state === 'activating' || target.state === 'activated' ? 'active' : target.state)
26
+ Observer.set(this, equivState, target);
27
+ }
28
+
29
+ // We're always installing at first for a new service worker.
30
+ // An existing service would immediately be active
31
+ const worker = registration.active || registration.waiting || registration.installing;
32
+ state(worker);
33
+ worker.addEventListener('statechange', e => state(e.target));
34
+
35
+ // "updatefound" event - a new worker that will control
36
+ // this page is installing somewhere
37
+ registration.addEventListener('updatefound', () => {
38
+ // If updatefound is fired, it means that there's
39
+ // a new service worker being installed.
40
+ state(registration.installing);
41
+ registration.installing.addEventListener('statechange', e => state(e.target));
42
+ });
43
+
44
+ resolve(registration);
45
+ }).catch(e => reject(e));
46
+ };
47
+ if (params.onWondowLoad) {
48
+ window.addEventListener('load', register);
49
+ } else {
50
+ register();
51
+ }
52
+ if (params.startMessages) {
53
+ navigator.serviceWorker.startMessages();
54
+ }
55
+ });
56
+
57
+ // --------
58
+ // Post messaging
59
+ // --------
60
+ const postSendCallback = (message, callback, onAvailability = 1) => {
61
+ if (this.active) {
62
+ if (_isFunction(message)) message = message();
63
+ callback(this.active, message);
64
+ } else if (onAvailability) {
65
+ // Availability Handling
66
+ const availabilityHandler = entry => {
67
+ if (_isFunction(message)) message = message();
68
+ callback(entry.value, message);
69
+ if (onAvailability !== 2) {
70
+ Observer.unobserve(this, 'active', availabilityHandler);
71
+ }
72
+ };
73
+ Observer.observe(this, 'active', availabilityHandler);
74
+ }
75
+ };
76
+ this.messaging = {
77
+ post: (message, onAvailability = 1) => {
78
+ postSendCallback(message, (active, message) => {
79
+ active.postMessage(message);
80
+ }, onAvailability);
81
+ return this.post;
82
+ },
83
+ listen: callback => {
84
+ navigator.serviceWorker.addEventListener('message', callback);
85
+ return this.post;
86
+ },
87
+ request: (message, onAvailability = 1) => {
88
+ return new Promise(res => {
89
+ postSendCallback(message, (active, message) => {
90
+ let messageChannel = new MessageChannel();
91
+ active.postMessage(message, [ messageChannel.port2 ]);
92
+ messageChannel.port1.onmessage = e => res(e.data);
93
+ }, onAvailability);
94
+ });
95
+ },
96
+ channel(channelId) {
97
+ if (!this.channels.has(channelId)) { this.channels.set(channelId, new BroadcastChannel(channel)); }
98
+ let channel = this.channels.get(channelId);
99
+ return {
100
+ broadcast: message => channel.postMessage(message),
101
+ listen: callback => channel.addEventListener('message', callback),
102
+ };
103
+ },
104
+ channels: new Map,
105
+ };
106
+
107
+ // --------
108
+ // Notifications
109
+ // --------
110
+ this.notifications = {
111
+ fire: (title, params = {}) => {
112
+ return new Promise((res, rej) => {
113
+ if (typeof Notification === 'undefined' || Notification.permission !== 'granted') {
114
+ return rej(typeof Notification !== 'undefined' && Notification && Notification.permission);
115
+ }
116
+ notification.addEventListener('error', rej);
117
+ let notification = new Notification(title, params);
118
+ notification.addEventListener('click', res);
119
+ notification.addEventListener('close', res);
120
+ });
121
+ },
122
+ };
123
+
124
+ // --------
125
+ // Push notifications
126
+ // --------
127
+ this.push = {
128
+ getSubscription: async () => {
129
+ return (await this.registration).pushManager.getSubscription();
130
+ },
131
+ subscribe: async (publicKey, params = {}) => {
132
+ var subscription = await this.push.getSubscription();
133
+ return subscription ? subscription : (await this.registration).pushManager.subscribe(
134
+ _isObject(publicKey) ? publicKey : {
135
+ applicationServerKey: urlBase64ToUint8Array(publicKey),
136
+ ...params,
137
+ }
138
+ );
139
+ },
140
+ unsubscribe: async () => {
141
+ var subscription = await this.push.getSubscription();
142
+ return !subscription ? null : subscription.unsubscribe();
143
+ },
144
+ };
145
+ }
146
+
147
+ }
148
+
149
+ // Public base64 to Uint
150
+ function urlBase64ToUint8Array(base64String) {
151
+ var padding = '='.repeat((4 - base64String.length % 4) % 4);
152
+ var base64 = (base64String + padding)
153
+ .replace(/\-/g, '+')
154
+ .replace(/_/g, '/');
155
+
156
+ var rawData = window.atob(base64);
157
+ var outputArray = new Uint8Array(rawData.length);
158
+
159
+ for (var i = 0; i < rawData.length; ++i) {
160
+ outputArray[i] = rawData.charCodeAt(i);
161
+ }
162
+ return outputArray;
163
+ }