@webqit/webflo 0.8.45 → 0.8.49

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 (38) hide show
  1. package/docker/Dockerfile +25 -0
  2. package/docker/README.md +69 -0
  3. package/package.json +9 -5
  4. package/src/cmd/client.js +68 -97
  5. package/src/cmd/origins.js +2 -2
  6. package/src/modules/Router.js +130 -0
  7. package/src/modules/_FormData.js +60 -0
  8. package/src/modules/_Headers.js +88 -0
  9. package/src/modules/_MessageStream.js +191 -0
  10. package/src/modules/_NavigationEvent.js +89 -0
  11. package/src/modules/_Request.js +61 -0
  12. package/src/modules/_RequestHeaders.js +72 -0
  13. package/src/modules/_Response.js +56 -0
  14. package/src/modules/_ResponseHeaders.js +81 -0
  15. package/src/modules/_URL.js +111 -0
  16. package/src/modules/client/Cache.js +38 -0
  17. package/src/modules/client/Client.js +51 -22
  18. package/src/modules/client/Http.js +26 -11
  19. package/src/modules/client/NavigationEvent.js +20 -0
  20. package/src/modules/client/Router.js +30 -104
  21. package/src/modules/client/StdRequest.js +34 -33
  22. package/src/modules/client/Storage.js +56 -0
  23. package/src/modules/client/Url.js +1 -1
  24. package/src/modules/client/Worker.js +74 -68
  25. package/src/modules/client/WorkerClient.js +102 -0
  26. package/src/modules/client/WorkerComm.js +183 -0
  27. package/src/modules/client/effects/sounds.js +64 -0
  28. package/src/modules/server/NavigationEvent.js +38 -0
  29. package/src/modules/server/Router.js +53 -124
  30. package/src/modules/server/Server.js +195 -87
  31. package/src/modules/util.js +7 -7
  32. package/src/modules/NavigationEvent.js +0 -32
  33. package/src/modules/Response.js +0 -98
  34. package/src/modules/XURL.js +0 -125
  35. package/src/modules/client/ClientNavigationEvent.js +0 -22
  36. package/src/modules/client/Push.js +0 -84
  37. package/src/modules/server/ServerNavigationEvent.js +0 -23
  38. package/src/modules/server/StdIncomingMessage.js +0 -70
@@ -5,13 +5,14 @@
5
5
  import Router from './Router.js';
6
6
  import _isGlobe from 'is-glob';
7
7
  import Minimatch from 'minimatch';
8
+ import { Observer } from '@webqit/pseudo-browser/index2.js';
8
9
  import _isArray from '@webqit/util/js/isArray.js';
9
10
  import _afterLast from '@webqit/util/str/afterLast.js';
10
11
  import _after from '@webqit/util/str/after.js';
11
12
  import _before from '@webqit/util/str/before.js';
12
13
  import _any from '@webqit/util/arr/any.js';
13
- import StdRequest from './StdRequest.js';
14
-
14
+ import _copy from '@webqit/util/obj/copy.js';
15
+ import NavigationEvent from './NavigationEvent.js';
15
16
 
16
17
  /**
17
18
  * ---------------------------
@@ -22,8 +23,10 @@ import StdRequest from './StdRequest.js';
22
23
  export default function(layout, params) {
23
24
 
24
25
  // Copy...
25
- layout = {...layout};
26
- params = {...params};
26
+ layout = { ...layout };
27
+ params = { ...params };
28
+ const sessionStores = Object.create(null);
29
+ const localStores = Object.create(null);
27
30
 
28
31
  /**
29
32
  * -------------
@@ -90,44 +93,55 @@ export default function(layout, params) {
90
93
  * ONFETCH
91
94
  * -------------
92
95
  */
93
-
96
+
94
97
  // Listen now...
95
98
  self.addEventListener('fetch', async evt => {
96
- var stdRequest = new StdRequest(evt.request);
97
- Object.defineProperty(evt, 'request', { get: () => stdRequest });
98
- evt.respondWith(handleFetch(evt));
99
- });
100
-
101
- // Fetches request
102
- const handleFetch = async evt => {
99
+ // URL schemes that might arrive here but not supported; e.g.: chrome-extension://
100
+ if (!evt.request.url.startsWith('http')) return;
101
+ // Fetches request
102
+ const handleFetch = async evt => {
103
+
104
+ if (evt.request.url.startsWith(self.origin) && (evt.request.mode === 'navigate' || evt.request.headers.get('X-Powered-By') === '@webqit/webflo')) {
105
+ // -----------------
106
+ // Sync session data to cache to be available to service-worker routers
107
+ // Sync only takes for requests that actually do send the "$session" cookie
108
+ const sessionData = Observer.proxy(sessionStores[evt.clientId] || {});
109
+ const clientNavigationEvent = new NavigationEvent(new NavigationEvent.Request(evt.request), sessionData);
110
+ // -----------------
111
+ // The app router
112
+ const router = new Router(_before(evt.request.url, '?'), layout, { layout });
113
+ const httpMethodName = evt.request.method.toLowerCase();
114
+ const _response = await router.route([httpMethodName === 'delete' ? 'del' : httpMethodName, 'default'], clientNavigationEvent, null, (event, arg) => defaultFetch(evt));
115
+ if (!(_response instanceof Response)/* _response being a native Response instance is fine */) {
116
+ return new NavigationEvent.Response(_response);
117
+ }
118
+ return _response;
119
+ }
103
120
 
104
- const $context = {
105
- layout,
121
+ return defaultFetch(evt);
106
122
  };
107
- // The app router
108
- const router = new Router(_before(evt.request.url, '?'), layout, $context);
109
- const httpMethodName = evt.request.method.toLowerCase();
110
- return await router.route([httpMethodName === 'delete' ? 'del' : httpMethodName, 'default'], [evt], null, async function() {
111
- if (_any((params.cache_only_url_list || []).map(c => c.trim()).filter(c => c), pattern => Minimatch.Minimatch(evt.request.url, pattern))) {
112
- return cache_fetch(evt);
113
- }
114
- // Now, the following is key:
115
- // The browser likes to use "force-cache" for "navigate" requests
116
- // when, for example, the back button was used.
117
- // Thus the origin server would still not be contacted by the self.fetch() below, leading to inconsistencies in responses.
118
- // So, we detect this scenerio and avoid it.
119
- if (evt.request.mode === 'navigate' && evt.request.cache === 'force-cache' && evt.request.destination === 'document') {
120
- return cache_fetch(evt, true/** cacheRefresh */);
121
- }
122
- if (_any((params.cache_first_url_list || []).map(c => c.trim()).filter(c => c), pattern => Minimatch.Minimatch(evt.request.url, pattern))) {
123
- return cache_fetch(evt, true/** cacheRefresh */);
124
- }
125
- if (_any((params.network_first_url_list || []).map(c => c.trim()).filter(c => c), pattern => Minimatch.Minimatch(evt.request.url, pattern))) {
126
- return network_fetch(evt, true/** cacheFallback */);
127
- }
128
- return network_fetch(evt);
129
- });
123
+ evt.respondWith(handleFetch(evt));
124
+ });
130
125
 
126
+ const defaultFetch = function(evt) {
127
+ if (_any((params.cache_only_url_list || []).map(c => c.trim()).filter(c => c), pattern => Minimatch.Minimatch(evt.request.url, pattern))) {
128
+ return cache_fetch(evt);
129
+ }
130
+ // Now, the following is key:
131
+ // The browser likes to use "force-cache" for "navigate" requests
132
+ // when, for example, the back button was used.
133
+ // Thus the origin server would still not be contacted by the self.fetch() below, leading to inconsistencies in responses.
134
+ // So, we detect this scenerio and avoid it.
135
+ if (evt.request.mode === 'navigate' && evt.request.cache === 'force-cache' && evt.request.destination === 'document') {
136
+ return cache_fetch(evt, true/** cacheRefresh */);
137
+ }
138
+ if (_any((params.cache_first_url_list || []).map(c => c.trim()).filter(c => c), pattern => Minimatch.Minimatch(evt.request.url, pattern))) {
139
+ return cache_fetch(evt, true/** cacheRefresh */);
140
+ }
141
+ if (_any((params.network_first_url_list || []).map(c => c.trim()).filter(c => c), pattern => Minimatch.Minimatch(evt.request.url, pattern))) {
142
+ return network_fetch(evt, true/** cacheFallback */);
143
+ }
144
+ return network_fetch(evt);
131
145
  };
132
146
 
133
147
  const getCacheName = request => request.headers.get('Accept') === 'application/json'
@@ -168,7 +182,7 @@ export default function(layout, params) {
168
182
  const refreshCache = (request, response) => {
169
183
 
170
184
  // Check if we received a valid response
171
- if (!response || response.status !== 200 || (response.type !== 'basic' && response.type !== 'cors')) {
185
+ if ((request._method || request.method) !== 'GET' || !response || response.status !== 200 || (response.type !== 'basic' && response.type !== 'cors')) {
172
186
  return response;
173
187
  }
174
188
 
@@ -187,51 +201,53 @@ export default function(layout, params) {
187
201
  return response;
188
202
  };
189
203
 
204
+ const relay = function(evt, messageData) {
205
+ return self.clients.matchAll().then(clientList => {
206
+ clientList.forEach(client => {
207
+ if (client.id === evt.source.id) {
208
+ return;
209
+ }
210
+ client.postMessage(messageData);
211
+ });
212
+ });
213
+ };
214
+
190
215
  // -----------------------------
191
216
 
192
217
  self.addEventListener('message', evt => {
193
- const $context = {
194
- layout,
195
- };
196
- const router = new Router('/', layout, $context);
218
+
219
+ // Handle normally
220
+ const router = new Router('/', layout, { layout });
197
221
  evt.waitUntil(
198
- router.route('postmessage', [evt], null, function() {
222
+ router.route('postmessage', evt, null, function() {
199
223
  return self;
200
224
  })
201
225
  );
226
+
202
227
  });
203
228
 
204
229
  self.addEventListener('push', evt => {
205
- const $context = {
206
- layout,
207
- };
208
- const router = new Router('/', layout, $context);
230
+ const router = new Router('/', layout, { layout });
209
231
  evt.waitUntil(
210
- router.route('push', [evt], null, function() {
232
+ router.route('push', evt, null, function() {
211
233
  return self;
212
234
  })
213
235
  );
214
236
  });
215
237
 
216
238
  self.addEventListener('notificationclick', evt => {
217
- const $context = {
218
- layout,
219
- };
220
- const router = new Router('/', layout, $context);
239
+ const router = new Router('/', layout, { layout });
221
240
  evt.waitUntil(
222
- router.route('notificationclick', [evt], null, function() {
241
+ router.route('notificationclick', evt, null, function() {
223
242
  return self;
224
243
  })
225
244
  );
226
245
  });
227
246
 
228
247
  self.addEventListener('notificationclose', evt => {
229
- const $context = {
230
- layout,
231
- };
232
- const router = new Router('/', layout, $context);
248
+ const router = new Router('/', layout, { layout });
233
249
  evt.waitUntil(
234
- router.route('notificationclose', [evt], null, function() {
250
+ router.route('notificationclose', evt, null, function() {
235
251
  return self;
236
252
  })
237
253
  );
@@ -245,16 +261,6 @@ export default function(layout, params) {
245
261
  const matchClientUrl = (client, url) => '/' + _after(_after(client.url, '//'), '/') === url;
246
262
 
247
263
  /**
248
- relay(messageData) {
249
- return self.clients.matchAll().then(clientList => {
250
- clientList.forEach(client => {
251
- if (client.id === evt.source.id) {
252
- return;
253
- }
254
- client.postMessage(messageData);
255
- });
256
- });
257
- };
258
264
 
259
265
  if (notificationData) {
260
266
  var title = params.NOTIFICATION_TITLE || '';
@@ -0,0 +1,102 @@
1
+
2
+
3
+ /**
4
+ * @imports
5
+ */
6
+ import { Observer } from '@webqit/pseudo-browser/index2.js';
7
+ import { _isFunction } from '@webqit/util/js/index.js';
8
+
9
+ export default class WorkerClient {
10
+
11
+ constructor(file, params = {}) {
12
+ this.ready = navigator.serviceWorker.ready;
13
+ // --------
14
+ // Registration and lifecycle
15
+ // --------
16
+ this.registration = new Promise((resolve, reject) => {
17
+ const register = () => {
18
+ navigator.serviceWorker.register(file, { scope: params.scope || '/' }).then(async registration => {
19
+
20
+ // Helper that updates instance's state
21
+ const state = target => {
22
+ // instance2.state can be any of: "installing", "installed", "activating", "activated", "redundant"
23
+ const equivState = target.state === 'installed' ? 'waiting' :
24
+ (target.state === 'activating' || target.state === 'activated' ? 'active' : target.state)
25
+ Observer.set(this, equivState, target);
26
+ }
27
+
28
+ // We're always installing at first for a new service worker.
29
+ // An existing service would immediately be active
30
+ const worker = registration.active || registration.waiting || registration.installing;
31
+ state(worker);
32
+ worker.addEventListener('statechange', e => state(e.target));
33
+
34
+ // "updatefound" event - a new worker that will control
35
+ // this page is installing somewhere
36
+ registration.addEventListener('updatefound', () => {
37
+ // If updatefound is fired, it means that there's
38
+ // a new service worker being installed.
39
+ state(registration.installing);
40
+ registration.installing.addEventListener('statechange', e => state(e.target));
41
+ });
42
+
43
+ resolve(registration);
44
+ }).catch(e => reject(e));
45
+ };
46
+ if (params.onWondowLoad) {
47
+ window.addEventListener('load', register);
48
+ } else {
49
+ register();
50
+ }
51
+ if (params.startMessages) {
52
+ navigator.serviceWorker.startMessages();
53
+ }
54
+ });
55
+ // --------
56
+ // Post messaging
57
+ // --------
58
+ this.post = {
59
+ send: (message, onAvailability = 1) => {
60
+ if (this.active) {
61
+ if (_isFunction(message)) message = message();
62
+ this.active.postMessage(message);
63
+ } else if (onAvailability) {
64
+ // Availability Handling
65
+ const availabilityHandler = entry => {
66
+ if (_isFunction(message)) message = message();
67
+ entry.value.postMessage(message);
68
+ if (onAvailability !== 2) {
69
+ Observer.unobserve(this, 'active', availabilityHandler);
70
+ }
71
+ };
72
+ Observer.observe(this, 'active', availabilityHandler);
73
+ }
74
+ return this.post;
75
+ },
76
+ receive: (callback, onAvailability = 1) => {
77
+ navigator.serviceWorker.addEventListener('message', callback);
78
+ return this.post;
79
+ },
80
+ }
81
+ // --------
82
+ // Push notifications
83
+ // --------
84
+ this.push = {
85
+ getSubscription: async () => {
86
+ return (await this.registration).pushManager.getSubscription();
87
+ },
88
+ subscribe: async (publicKey, params = {}) => {
89
+ var subscription = await this.push.getSubscription();
90
+ return subscription ? subscription : (await this.registration).pushManager.subscribe({
91
+ applicationServerKey: urlBase64ToUint8Array(publicKey),
92
+ ...params,
93
+ });
94
+ },
95
+ unsubscribe: async () => {
96
+ var subscription = await this.push.getSubscription();
97
+ return !subscription ? null : subscription.unsubscribe();
98
+ },
99
+ }
100
+ }
101
+
102
+ }
@@ -0,0 +1,183 @@
1
+
2
+
3
+ /**
4
+ * @imports
5
+ */
6
+
7
+ var _sab;
8
+ export function request(context, data = {}) {
9
+ var response;
10
+ const channel = new MessageChannel();
11
+ channel.port1.onmessage = e => (console.log('-----receiving'), response = e.data);
12
+ console.log('------waiting 11111', self.crossOriginIsolated);
13
+ try {
14
+ _sab = _sab || new ArrayBuffer(4); Atomics;
15
+ context.postMessage({ _sab, ...data, }, [ channel.port2 ]);
16
+ Atomics.wait(new Int32Array(_sab), 0, 0, 400);
17
+ return channel;
18
+ } catch (e) {
19
+ console.error('error1', e);
20
+ var x = new XMLHttpRequest();
21
+ x.timeout = 400;
22
+ x.open('get', '/@sleep@/t.js?t=400', false);
23
+ x.setRequestHeader('cache-control', 'no-cache, no-store, max-age=0');
24
+ try { x.send() } catch(e) { console.error('error2', e, response) }
25
+ }
26
+
27
+ return response;
28
+ }
29
+
30
+ export function listen(context, handler) {
31
+ console.log('::::::::::::::::::', crossOriginIsolated)
32
+ context.addEventListener('message', e => {
33
+ if (e.data._sab instanceof SharedArrayBuffer && e.ports.length) {
34
+ console.log('--------handling', e.data)
35
+ var response = handler(e.data);
36
+ if (response !== undefined) {
37
+ e.ports[0].postMessage(response);
38
+ console.log('--------sent');
39
+ Atomics.notify(new Int32Array(e.data._sab), 0, 1);
40
+ console.log('--------notified');
41
+ }
42
+ }
43
+ });
44
+ }
45
+
46
+ /**
47
+ * ------------------
48
+ * https://www.sitepen.com/blog/the-return-of-sharedarraybuffers-and-atomics
49
+ * ------------------
50
+ */
51
+
52
+ export function sharedArrayBufferToUtf16String(buf) {
53
+ const array = new Uint16Array(buf);
54
+ return String.fromCharCode.apply(null, array);
55
+ }
56
+
57
+ export function utf16StringToSharedArrayBuffer(str) {
58
+ // 2 bytes for each char
59
+ const bytes = str.length *2;
60
+ const buffer = new SharedArrayBuffer(bytes);
61
+ const arrayBuffer = new Uint16Array(buffer);
62
+ for (let i = 0, strLen = str.length; i < strLen; i++) {
63
+ arrayBuffer[i] = str.charCodeAt(i);
64
+ }
65
+ return { array: arrayBuffer, buffer: buffer };
66
+ }
67
+
68
+ export function encodeUf8StringToSharedArrayBuffer(string) {
69
+ // Calculate the byte size of the UTF-8 string
70
+ let bytes = string.length;
71
+ for (let i = string.length -1; i <= 0; i--) {
72
+ const code = string.charCodeAt(i);
73
+ if (code > 0x7f && code <= 0x7ff) {
74
+ bytes++;
75
+ } else if (code > 0x7ff && code <= 0xffff) {
76
+ bytes+=2;
77
+ }
78
+ if (code >= 0xdc00 && code <= 0xdfff) {
79
+ i--; // trail surrogate
80
+ }
81
+ }
82
+ const buffer = new SharedArrayBuffer(bytes);
83
+ const arrayBuffer = new Uint8Array(buffer);
84
+ const encoded = unescape(encodeURIComponent(string));
85
+ for (var i = 0; i < encoded.length; i++) {
86
+ arrayBuffer[i] = encoded[i].charCodeAt(0);
87
+ }
88
+ return { array: arrayBuffer, buffer: buffer };
89
+ }
90
+
91
+ export function decodeUtf8StringFromSharedArrayBuffer(array) {
92
+ var encodedString = String.fromCharCode.apply(null, array);
93
+ var decodedString = decodeURIComponent(escape(encodedString));
94
+ return decodedString;
95
+ }
96
+
97
+ /**
98
+ * // main.js
99
+ worker.postMessage(sharedBuffer);
100
+ // worker.js
101
+ constsharedArray = new Int32Array(m.data);
102
+
103
+ const exampleString = "Hello world, this is an example string!";
104
+ const sharedArrayBuffer = utf16StringToSharedArrayBuffer(exampleString).buffer;
105
+ const backToString = sharedArrayBufferToUtf16String(sharedArrayBuffer);
106
+ */
107
+
108
+ // Sync Local Storage
109
+ function sharedStore(store, persistent = false, onAvailability = 1) {
110
+ const storeData = () => Object.keys(store).reduce((_store, key) => (_store[key] = store[key], _store), {});
111
+ this.post.send(() => ({ _type: 'WHOLE_STORAGE_SYNC', _persistent: persistent, store: storeData() }), onAvailability);
112
+ window.addEventListener('beforeunload', e => {
113
+ this.post.send({ _type: 'WHOLE_STORAGE_SYNC', _persistent: persistent });
114
+ });
115
+ // --------
116
+ Observer.observe(store, changes => {
117
+ changes.forEach(change => {
118
+ if (change.type === 'set') {
119
+ if (!(change.detail || {}).noSync) {
120
+ this.post.send({ _type: 'STORAGE_SYNC', _persistent: persistent, ..._copy(change, [ 'type', 'name', 'path', 'value', 'oldValue', 'isUpdate', 'related', ]) });
121
+ }
122
+ } else if (change.type === 'deletion') {
123
+ if (!(change.detail || {}).noSync) {
124
+ this.post.send({ _type: 'STORAGE_SYNC', _persistent: persistent, ..._copy(change, [ 'type', 'name', 'path', 'value', 'oldValue', 'isUpdate', 'related', ]) });
125
+ }
126
+ }
127
+ });
128
+ });
129
+ // --------
130
+ this.post.receive(e => {
131
+ if (e.data && e.data._type === 'STORAGE_SYNC' && e.data._persistent === persistent) {
132
+ if (e.data.type === 'set') {
133
+ Observer.set(store, e.data.name, e.data.value, { detail: { noSync: true } });
134
+ } else if (e.data.type === 'deletion') {
135
+ Observer.deleteProperty(store, e.data.name, { detail: { noSync: true } });
136
+ }
137
+ }
138
+ }, onAvailability);
139
+ }
140
+
141
+ self.addEventListener('message', evt => {
142
+
143
+ // SESSION_SYNC
144
+ var clientId = evt.source.id;
145
+ if (evt.data && evt.data._type === 'WHOLE_STORAGE_SYNC') {
146
+ const storage = evt.data._persistent ? localStores : sessionStores;
147
+ if (evt.data.store) {
148
+ storage[clientId] = evt.data.store;
149
+ // --------------------------
150
+ // Get mutations synced TO client
151
+ Observer.observe(storage[clientId], changes => {
152
+ changes.forEach(change => {
153
+ if (!(change.detail || {}).noSync) {
154
+ self.clients.get(clientId).then(client => {
155
+ client.postMessage({ _type: 'STORAGE_SYNC', _persistent: evt.data._persistent, ..._copy(change, [ 'type', 'name', 'path', 'value', 'oldValue', 'isUpdate', 'related', ]), });
156
+ });
157
+ }
158
+ });
159
+ });
160
+ // --------------------------
161
+ } else {
162
+ delete storage[clientId];
163
+ }
164
+ } else if (evt.data && evt.data._type === 'STORAGE_SYNC') {
165
+ // --------------------------
166
+ // Get mutations synced FROM client
167
+ const storage = evt.data._persistent ? localStores : sessionStores;
168
+ if (evt.data.type === 'set') {
169
+ if (storage[clientId]) Observer.set(storage[clientId], evt.data.name, evt.data.value, { detail: { noSync: true } });
170
+ } else if (evt.data.type === 'deletion') {
171
+ if (storage[clientId]) Observer.deleteProperty(storage[clientId], evt.data.name, { detail: { noSync: true } });
172
+ }
173
+ // --------------------------
174
+
175
+ // --------------------------
176
+ // Relay to other clients
177
+ if (evt.data._persistent) {
178
+ relay(evt, evt.data);
179
+ }
180
+ // --------------------------
181
+ return;
182
+ }
183
+ });
@@ -0,0 +1,64 @@
1
+ const context = new window.AudioContext();
2
+
3
+ export function playFile(filepath) {
4
+ // see https://jakearchibald.com/2016/sounds-fun/
5
+ // Fetch the file
6
+ fetch(filepath)
7
+ // Read it into memory as an arrayBuffer
8
+ .then(response => response.arrayBuffer())
9
+ // Turn it from mp3/aac/whatever into raw audio data
10
+ .then(arrayBuffer => context.decodeAudioData(arrayBuffer))
11
+ .then(audioBuffer => {
12
+ // Now we're ready to play!
13
+ const soundSource = context.createBufferSource();
14
+ soundSource.buffer = audioBuffer;
15
+ soundSource.connect(context.destination);
16
+ soundSource.start();
17
+ });
18
+ }
19
+
20
+ export function playSuccess() {
21
+ const successNoise = context.createOscillator();
22
+ successNoise.frequency = "600";
23
+ successNoise.type = "sine";
24
+ successNoise.frequency.exponentialRampToValueAtTime(800, context.currentTime + 0.05);
25
+ successNoise.frequency.exponentialRampToValueAtTime(1000, context.currentTime + 0.15);
26
+
27
+ successGain = context.createGain();
28
+ successGain.gain.exponentialRampToValueAtTime(0.01, context.currentTime + 0.3);
29
+
30
+ successFilter = context.createBiquadFilter("bandpass");
31
+ successFilter.Q = 0.01;
32
+
33
+ successNoise
34
+ .connect(successFilter)
35
+ .connect(successGain)
36
+ .connect(context.destination);
37
+ successNoise.start();
38
+ successNoise.stop(context.currentTime + 0.2);
39
+ }
40
+
41
+ export function playError() {
42
+ const errorNoise = context.createOscillator();
43
+ errorNoise.frequency = "400";
44
+ errorNoise.type = "sine";
45
+ errorNoise.frequency.exponentialRampToValueAtTime(200, context.currentTime + 0.05);
46
+ errorNoise.frequency.exponentialRampToValueAtTime(100, context.currentTime + 0.2);
47
+
48
+ errorGain = context.createGain();
49
+ errorGain.gain.exponentialRampToValueAtTime(0.01, context.currentTime + 0.3);
50
+
51
+ errorNoise.connect(errorGain).connect(context.destination);
52
+ errorNoise.start();
53
+ errorNoise.stop(context.currentTime + 0.3);
54
+ }
55
+
56
+ let successButton = document.querySelector("#success");
57
+ successButton.addEventListener("click", function() {
58
+ playFile('https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/success.mp3');
59
+ });
60
+
61
+ let errorButton = document.querySelector("#error");
62
+ errorButton.addEventListener("click", function() {
63
+ playFile('https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/error.mp3');
64
+ });
@@ -0,0 +1,38 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import { URL } from 'url';
6
+ import { Readable } from "stream";
7
+ import { FormData, File, Blob } from 'formdata-node';
8
+ import { FormDataEncoder } from 'form-data-encoder';
9
+ import { Request, Response, Headers } from 'node-fetch';
10
+ import _NavigationEvent from '../_NavigationEvent.js';
11
+ import _FormData from '../_FormData.js';
12
+
13
+ /**
14
+ * Patch MessageStream with formData()
15
+ */
16
+ const _streamFormDataPatch = MessageStream => class extends MessageStream {
17
+
18
+ // formData() polyfill
19
+ async formData() {
20
+ return null;
21
+ }
22
+
23
+ };
24
+
25
+ /**
26
+ * The NavigationEvent class
27
+ */
28
+ export default _NavigationEvent({
29
+ URL,
30
+ Request: _streamFormDataPatch(Request),
31
+ Response: _streamFormDataPatch(Response),
32
+ Headers,
33
+ FormData: _FormData(FormData),
34
+ File,
35
+ Blob,
36
+ ReadableStream: Readable,
37
+ FormDataEncoder
38
+ });