reflected 0.0.0 → 0.0.2

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 (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/dist/async-DF1WaSCr.js +1 -0
  4. package/dist/async.js +1 -0
  5. package/dist/broadcast-D0xibjmN.js +1 -0
  6. package/dist/broadcast-Dc7wAEE6.js +1 -0
  7. package/dist/broadcast.js +1 -0
  8. package/dist/channel-CdS9bLt4.js +1 -0
  9. package/dist/i32-C78nBJH2.js +1 -0
  10. package/dist/index.js +1 -0
  11. package/dist/message-D7s0I-EX.js +1 -0
  12. package/dist/message-DLH7cJo6.js +1 -0
  13. package/dist/message.js +1 -0
  14. package/dist/sender-BMLGKAss.js +1 -0
  15. package/dist/shared-C_kd5Il-.js +1 -0
  16. package/dist/shared-MkVjuKUg.js +1 -0
  17. package/dist/shared-RFmxa5x4.js +1 -0
  18. package/dist/shared-array-buffer-cwdMr2mc.js +1 -0
  19. package/dist/sw.js +1 -0
  20. package/dist/with-resolvers-CHEvl4oe.js +1 -0
  21. package/dist/xhr-D5y-AocB.js +1 -0
  22. package/dist/xhr-DAG_4oRo.js +1 -0
  23. package/dist/xhr.js +1 -0
  24. package/package.json +91 -10
  25. package/rollup.js +59 -0
  26. package/src/async.js +8 -0
  27. package/src/broadcast.js +8 -0
  28. package/src/channel.js +1 -0
  29. package/src/index.js +33 -10
  30. package/src/main/async.js +29 -0
  31. package/src/main/broadcast.js +21 -0
  32. package/src/main/message.js +20 -0
  33. package/src/main/sab.js +12 -0
  34. package/src/main/sender.js +38 -0
  35. package/src/main/shared.js +74 -16
  36. package/src/main/xhr.js +101 -0
  37. package/src/message.js +8 -0
  38. package/src/service/listeners.js +37 -0
  39. package/src/service/worker.js +5 -0
  40. package/src/shared.js +10 -0
  41. package/src/worker/async.js +38 -0
  42. package/src/worker/{firefox.js → broadcast.js} +4 -1
  43. package/src/worker/{chrome.js → message.js} +4 -1
  44. package/src/worker/sender.js +16 -0
  45. package/src/worker/shared.js +28 -7
  46. package/src/worker/xhr.js +38 -0
  47. package/src/xhr.js +8 -0
  48. package/test/README/index.html +11 -0
  49. package/test/README/index.js +54 -0
  50. package/test/README/mini-coi.js +28 -0
  51. package/test/README/worker.js +26 -0
  52. package/test/index.html +8 -9
  53. package/test/index.js +35 -0
  54. package/test/mini-coi.js +28 -0
  55. package/test/sw.js +1 -0
  56. package/test/worker.js +16 -4
  57. package/types/async.d.ts +3 -0
  58. package/types/broadcast.d.ts +21 -0
  59. package/types/channel.d.ts +2 -0
  60. package/types/index.d.ts +9 -0
  61. package/types/main/async.d.ts +7 -0
  62. package/types/main/broadcast.d.ts +20 -0
  63. package/types/main/message.d.ts +20 -0
  64. package/types/main/sab.d.ts +5 -0
  65. package/types/main/sender.d.ts +5 -0
  66. package/types/main/shared.d.ts +44 -0
  67. package/types/main/xhr.d.ts +2 -0
  68. package/types/message.d.ts +21 -0
  69. package/types/reflected/src/async.d.ts +3 -0
  70. package/types/reflected/src/broadcast.d.ts +3 -0
  71. package/types/reflected/src/channel.d.ts +2 -0
  72. package/types/reflected/src/index.d.ts +3 -0
  73. package/types/reflected/src/main/async.d.ts +7 -0
  74. package/types/reflected/src/main/broadcast.d.ts +2 -0
  75. package/types/reflected/src/main/message.d.ts +2 -0
  76. package/types/reflected/src/main/sab.d.ts +5 -0
  77. package/types/reflected/src/main/sender.d.ts +5 -0
  78. package/types/reflected/src/main/shared.d.ts +44 -0
  79. package/types/reflected/src/main/xhr.d.ts +2 -0
  80. package/types/reflected/src/message.d.ts +3 -0
  81. package/types/reflected/src/service/listeners.d.ts +3 -0
  82. package/types/reflected/src/service/worker.d.ts +1 -0
  83. package/types/reflected/src/shared.d.ts +1 -0
  84. package/types/reflected/src/worker/async.d.ts +3 -0
  85. package/types/reflected/src/worker/broadcast.d.ts +3 -0
  86. package/types/reflected/src/worker/message.d.ts +3 -0
  87. package/types/reflected/src/worker/sender.d.ts +2 -0
  88. package/types/reflected/src/worker/shared.d.ts +11 -0
  89. package/types/reflected/src/worker/xhr.d.ts +3 -0
  90. package/types/reflected/src/xhr.d.ts +3 -0
  91. package/types/service/listeners.d.ts +3 -0
  92. package/types/service/worker.d.ts +1 -0
  93. package/types/shared.d.ts +1 -0
  94. package/types/weak-id/i32.d.ts +2 -0
  95. package/types/worker/async.d.ts +3 -0
  96. package/types/worker/broadcast.d.ts +3 -0
  97. package/types/worker/message.d.ts +3 -0
  98. package/types/worker/sender.d.ts +2 -0
  99. package/types/worker/shared.d.ts +11 -0
  100. package/types/worker/xhr.d.ts +3 -0
  101. package/types/xhr.d.ts +3 -0
  102. package/src/main/chrome.js +0 -19
  103. package/src/main/fallback.js +0 -29
  104. package/src/main/firefox.js +0 -19
  105. package/src/main/shared-id.js +0 -1
  106. package/src/worker/fallback.js +0 -1
@@ -0,0 +1,38 @@
1
+ import withResolvers from '@webreflection/utils/with-resolvers';
2
+ import i32 from 'weak-id/i32';
3
+
4
+ import SHARED_CHANNEL from '../channel.js';
5
+
6
+ const { isArray } = Array;
7
+
8
+ const onsend = value => value;
9
+
10
+ export default class Sender extends Worker {
11
+ #next;
12
+ #requests;
13
+ constructor(scriptURL, options) {
14
+ super(scriptURL, options);
15
+ this.#next = i32();
16
+ this.#requests = new Map;
17
+ if (!options.onsend) options.onsend = onsend;
18
+ super.addEventListener('message', async event => {
19
+ const { data } = event;
20
+ if (isArray(data) && data[0] === SHARED_CHANNEL) {
21
+ event.stopImmediatePropagation();
22
+ event.preventDefault();
23
+ const [id, payload] = data[1];
24
+ const resolve = this.#requests.get(id);
25
+ this.#requests.delete(id);
26
+ resolve(await options.onsend(payload));
27
+ }
28
+ });
29
+ }
30
+
31
+ send(payload, ...rest) {
32
+ const id = this.#next();
33
+ const { promise, resolve } = withResolvers();
34
+ this.#requests.set(id, resolve);
35
+ super.postMessage([SHARED_CHANNEL, [id, payload]], ...rest);
36
+ return promise;
37
+ }
38
+ }
@@ -1,36 +1,94 @@
1
- import withResolvers from '/node_modules/@webreflection/utils/src/with-resolvers.js';
1
+ import withResolvers from '@webreflection/utils/with-resolvers';
2
2
 
3
3
  const { notify, store } = Atomics;
4
- const { isView } = ArrayBuffer;
5
4
 
6
- const minByteLength = Int32Array.BYTES_PER_ELEMENT * 2;
5
+ export const minByteLength = Int32Array.BYTES_PER_ELEMENT * 2;
7
6
 
8
- export const SAB = ({ minByteLength = 1032, maxByteLength = 8200 }) =>
9
- new SharedArrayBuffer(minByteLength, { maxByteLength });
7
+ export const SAB = ({
8
+ initByteLength = 1024,
9
+ maxByteLength = (1024 * 8)
10
+ }) =>
11
+ new SharedArrayBuffer(
12
+ minByteLength + initByteLength,
13
+ { maxByteLength: minByteLength + maxByteLength }
14
+ );
15
+
16
+ /**
17
+ * @typedef {Object} ServiceWorkerOptions see https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register#options
18
+ * @property {string} [url] will use the `serviceWorker` value if that is a `string`, otherwise it refers to where the service worker file is located.
19
+ * @property {'classic' | 'module'} [type]
20
+ * @property {'all' | 'imports' | 'none'} [updateViaCache]
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} Options
25
+ * @property {(payload: unknown) => Int32Array | Promise<Int32Array>} onsync invoked when the worker expect a response as `Int32Array` to populate the SharedArrayBuffer with.
26
+ * @property {(payload: unknown) => unknown | Promise<unknown>} [onsend] invoked when the worker replies to a `worker.send(data)` call.
27
+ * @property {number} [initByteLength=1024] defines the initial byte length of the SharedArrayBuffer.
28
+ * @property {number} [maxByteLength=8192] defines the maximum byte length (growth) of the SharedArrayBuffer.
29
+ * @property {string | ServiceWorkerOptions} [serviceWorker] defines the service worker to use as fallback if SharedArrayBuffer is not supported. If not defined, the `async` fallback will be used so that no `sync` operations from the worker will be possible.
30
+ */
31
+
32
+ /**
33
+ * Initialize the worker channel communication and resolves with the worker instance.
34
+ * @template T
35
+ * @param {T} Worker
36
+ * @returns
37
+ */
38
+ export const bootstrap = Worker => {
39
+ /**
40
+ * @param {string} scriptURL
41
+ * @param {Options} options
42
+ * @returns
43
+ */
44
+ return (scriptURL, options) => {
45
+ const { promise, resolve } = withResolvers();
46
+ // @ts-ignore
47
+ new Worker(scriptURL, options, resolve);
48
+ return /** @type {Promise<T>} */(promise);
49
+ };
50
+ };
10
51
 
11
52
  export const handler = (sab, options, useAtomics) => {
12
53
  const i32a = new Int32Array(sab);
13
- return async ({ data }, ...rest) => {
14
- let result = await options.ondata(data, ...rest);
15
- if (!isView(result)) result = new Int32Array(result);
16
- const { byteLength } = result.buffer;
17
- const requiredByteLength = byteLength + minByteLength;
54
+ return async ({ data }) => {
55
+ const result = await options.onsync(data);
56
+ const length = result.length;
57
+ const requiredByteLength = minByteLength + result.buffer.byteLength;
18
58
  if (sab.byteLength < requiredByteLength) sab.grow(requiredByteLength);
19
59
  i32a.set(result, 2);
20
60
  if (useAtomics) {
21
- store(i32a, 1, result.length);
61
+ store(i32a, 1, length);
22
62
  store(i32a, 0, 1);
23
63
  notify(i32a, 0);
24
64
  }
65
+ else {
66
+ i32a[1] = length;
67
+ i32a[0] = 1;
68
+ }
25
69
  };
26
70
  };
27
71
 
28
- export const post = (sab, options) => [sab, { ...options, ondata: void 0 }];
72
+ const isOK = value => {
73
+ switch (typeof value) {
74
+ case 'symbol':
75
+ case 'function':
76
+ return false;
77
+ }
78
+ return true;
79
+ };
29
80
 
30
- export const url = (scriptURL, reflected) => {
81
+ export const post = (sab, options) => {
82
+ const opts = {};
83
+ for (const key in options) {
84
+ const value = options[key];
85
+ if (isOK(key) && isOK(value)) opts[key] = value;
86
+ }
87
+ return [sab, opts];
88
+ };
89
+
90
+ export const url = (scriptURL, reflected, options) => {
31
91
  const url = new URL(scriptURL, location.href);
32
92
  url.searchParams.set('reflected', reflected);
33
- return url;
93
+ return [url, { ...options, type: 'module' }];
34
94
  };
35
-
36
- export { withResolvers };
@@ -0,0 +1,101 @@
1
+ import withResolvers from '@webreflection/utils/with-resolvers';
2
+ import { Worker as AsyncWorker } from './async.js';
3
+
4
+ import SHARED_CHANNEL from '../channel.js';
5
+ import SAB from './sab.js';
6
+ import Sender from './sender.js';
7
+
8
+ import { handler, post, url } from './shared.js';
9
+ import { randomUUID } from '../shared.js';
10
+
11
+ const CHANNEL = 'xhr';
12
+
13
+ const channels = new Map;
14
+
15
+ const sharedBC = new BroadcastChannel(SHARED_CHANNEL);
16
+ sharedBC.addEventListener('message', async ({ data: [op, details] }) => {
17
+ if (op === 'request') {
18
+ const [uid, [id, channel]] = details;
19
+ const responses = channels.get(channel);
20
+ if (responses) {
21
+ sharedBC.postMessage(['response', [uid, await responses.get(id)]]);
22
+ responses.delete(id);
23
+ }
24
+ }
25
+ });
26
+
27
+ const { promise: sw, resolve } = withResolvers();
28
+ let init = true;
29
+
30
+ const activate = (swc, options) => {
31
+ let w, c = true, { url } = options;
32
+ swc.getRegistration(url)
33
+ .then(r => (r ?? swc.register(url, options)))
34
+ .then(function ready(r) {
35
+ const { controller } = swc;
36
+ c = c && !!controller;
37
+ w = (r.installing || r.waiting || r.active);
38
+ if (!w) return activate(swc, options);
39
+ if (w.state === 'activated') {
40
+ if (c) {
41
+ // allow ServiceWorker swap on different URL
42
+ if (controller.scriptURL === url)
43
+ return resolve();
44
+ r.unregister();
45
+ }
46
+ location.reload();
47
+ }
48
+ else {
49
+ w.addEventListener('statechange', () => ready(r), { once: true });
50
+ }
51
+ });
52
+ };
53
+
54
+ class Worker extends Sender {
55
+ #channel;
56
+ constructor(scriptURL, options, resolve) {
57
+ if (init) {
58
+ init = false;
59
+ let { serviceWorker } = options;
60
+ if (!serviceWorker) {
61
+ // @ts-ignore
62
+ return new AsyncWorker(scriptURL, options, resolve);
63
+ }
64
+ if (typeof serviceWorker === 'string') serviceWorker = { url: serviceWorker };
65
+ serviceWorker.url = new URL(serviceWorker.url, location.href).href;
66
+ activate(navigator.serviceWorker, serviceWorker);
67
+ }
68
+ const channel = randomUUID();
69
+ const bc = new BroadcastChannel(channel);
70
+ const sab = SAB(options);
71
+ const responses = new Map;
72
+ const i32a = new Int32Array(sab);
73
+ const handle = handler(sab, options, false);
74
+ channels.set(channel, responses);
75
+ bc.addEventListener('message', async ({ data: [id, payload] }) => {
76
+ const { promise, resolve } = withResolvers();
77
+ responses.set(id, promise);
78
+ await handle({ data: payload });
79
+ resolve(i32a.slice(0, 2 + i32a[1]));
80
+ });
81
+ super(...url(scriptURL, CHANNEL, options));
82
+ super.addEventListener('message', () => resolve(this), { once: true });
83
+ super.postMessage(post(sab, options).concat(channel));
84
+ this.#channel = channel;
85
+ }
86
+
87
+ terminate() {
88
+ channels.delete(this.#channel);
89
+ super.terminate();
90
+ }
91
+
92
+ get channel() {
93
+ return CHANNEL;
94
+ }
95
+ };
96
+
97
+ export default (scriptURL, options) => {
98
+ const { promise, resolve } = withResolvers();
99
+ const worker = new Worker(scriptURL, options, resolve);
100
+ return worker instanceof AsyncWorker ? promise : sw.then(() => promise);
101
+ };
package/src/message.js ADDED
@@ -0,0 +1,8 @@
1
+ import main from './main/message.js';
2
+ import worker from './worker/message.js';
3
+
4
+ export { channel } from './worker/message.js';
5
+
6
+ export default (
7
+ 'importScripts' in globalThis ? worker : main
8
+ );
@@ -0,0 +1,37 @@
1
+ import CHANNEL from '../channel.js';
2
+
3
+ import nextResolver from 'next-resolver';
4
+
5
+ const [next, resolve] = nextResolver();
6
+
7
+ const { protocol, host, pathname } = location;
8
+ const url = `${protocol}//${host}${pathname}`;
9
+
10
+ const bc = new BroadcastChannel(CHANNEL);
11
+ bc.addEventListener('message', ({ data: [op, details] }) => {
12
+ if (op === 'response') {
13
+ const [uid, payload] = details;
14
+ resolve(uid, `[${payload.join(',')}]`);
15
+ }
16
+ });
17
+
18
+ const respond = async details => {
19
+ const [uid, promise] = next();
20
+ bc.postMessage(['request', [uid, details]]);
21
+ return new Response(await promise);
22
+ };
23
+
24
+ // @ts-ignore
25
+ export const activate = event => event.waitUntil(clients.claim());
26
+
27
+ export const fetch = async event => {
28
+ const { request: r } = event;
29
+ if (r.method === 'POST' && r.url.startsWith(url)) {
30
+ event.stopImmediatePropagation();
31
+ event.respondWith(r.json().then(respond));
32
+ event.preventDefault();
33
+ }
34
+ };
35
+
36
+ // @ts-ignore
37
+ export const install = () => skipWaiting();
@@ -0,0 +1,5 @@
1
+ import { activate, fetch, install } from './listeners.js';
2
+
3
+ addEventListener('activate', activate);
4
+ addEventListener('fetch', fetch);
5
+ addEventListener('install', install);
package/src/shared.js ADDED
@@ -0,0 +1,10 @@
1
+ let hasRandomUUID = true;
2
+ try {
3
+ crypto.randomUUID();
4
+ } catch (_) {
5
+ hasRandomUUID = false;
6
+ }
7
+
8
+ export const randomUUID = hasRandomUUID ?
9
+ (() => crypto.randomUUID() ):
10
+ (() => (Date.now() + Math.random()).toString(36));
@@ -0,0 +1,38 @@
1
+ import withResolvers from '@webreflection/utils/with-resolvers';
2
+ import i32 from 'weak-id/i32';
3
+
4
+ import sender from './sender.js';
5
+
6
+ const { promise, resolve } = withResolvers();
7
+
8
+ addEventListener(
9
+ 'message',
10
+ ({ data: [sab, main, channel] }) => resolve([sab, main, channel]),
11
+ { once: true }
12
+ );
13
+
14
+ export const channel = 'async';
15
+
16
+ const handle = (channel, i32a, options) => {
17
+ const bc = new BroadcastChannel(channel);
18
+ const next = i32();
19
+ const map = new Map;
20
+ bc.addEventListener('message', ({ data: [id, payload] }) => {
21
+ i32a.set(payload, 0);
22
+ map.get(id)(options.onsync(i32a.subarray(2, 2 + i32a[1])));
23
+ map.delete(id);
24
+ });
25
+ return (payload, ...rest) => {
26
+ const { promise, resolve } = withResolvers();
27
+ const id = next();
28
+ map.set(id, resolve);
29
+ // @ts-ignore
30
+ bc.postMessage([id, payload], ...rest);
31
+ return promise;
32
+ };
33
+ };
34
+
35
+ export default options => promise.then(([sab, main, channel]) => {
36
+ postMessage(1);
37
+ return handle(channel, new Int32Array(sab), sender({ ...main, ...options }));
38
+ });
@@ -1,7 +1,10 @@
1
- import { handler, withResolvers } from './shared.js';
1
+ import withResolvers from '@webreflection/utils/with-resolvers';
2
+ import { handler } from './shared.js';
2
3
 
3
4
  const { promise, resolve } = withResolvers();
4
5
 
6
+ export const channel = 'broadcast';
7
+
5
8
  export default handler(
6
9
  promise,
7
10
  ({ data: [sab, main, channel] }) => resolve([sab, main, new BroadcastChannel(channel)]),
@@ -1,7 +1,10 @@
1
- import { handler, withResolvers } from './shared.js';
1
+ import withResolvers from '@webreflection/utils/with-resolvers';
2
+ import { handler } from './shared.js';
2
3
 
3
4
  const { promise, resolve } = withResolvers();
4
5
 
6
+ export const channel = 'message';
7
+
5
8
  export default handler(
6
9
  promise,
7
10
  ({ data: [sab, main], ports: [channel] }) => resolve([sab, main, channel])
@@ -0,0 +1,16 @@
1
+ import SHARED_CHANNEL from '../channel.js';
2
+
3
+ const { isArray } = Array;
4
+
5
+ export default options => {
6
+ addEventListener('message', async event => {
7
+ const { data } = event;
8
+ if (isArray(data) && data[0] === SHARED_CHANNEL) {
9
+ event.stopImmediatePropagation();
10
+ event.preventDefault();
11
+ const [id, payload] = data[1];
12
+ postMessage([SHARED_CHANNEL, [id, await options.onsend(payload)]]);
13
+ }
14
+ });
15
+ return options;
16
+ };
@@ -1,22 +1,43 @@
1
- import withResolvers from '/node_modules/@webreflection/utils/src/with-resolvers.js';
1
+ import sender from './sender.js';
2
2
 
3
3
  const { load, store, wait } = Atomics;
4
4
 
5
- const handle = (channel, i32a, options) => (data, ...rest) => {
6
- channel.postMessage(data, ...rest);
5
+ /**
6
+ * @typedef {Object} Options
7
+ * @property {(payload: Int32Array) => unknown} onsync transforms the resulting `Int32Array` from *main* thread into a value usable within the worker.
8
+ * @property {(payload: unknown) => unknown |Promise<unknown>} onsend invoked to define what to return to the *main* thread when it calls `worker.send(payload)`.
9
+ */
10
+
11
+ /**
12
+ * @param {MessageChannel | BroadcastChannel} channel
13
+ * @param {Int32Array} i32a
14
+ * @param {Options} options
15
+ * @returns {(payload: unknown, ...rest: unknown[]) => unknown}
16
+ */
17
+ const handle = (channel, i32a, options) => (payload, ...rest) => {
18
+ // @ts-ignore
19
+ channel.postMessage(payload, ...rest);
7
20
  wait(i32a, 0, 0);
8
21
  store(i32a, 0, 0);
9
- return options.ondata(i32a.subarray(2, 2 + load(i32a, 1)), ...rest);
22
+ return options.onsync(i32a.subarray(2, 2 + load(i32a, 1)));
10
23
  };
11
24
 
25
+ /**
26
+ *
27
+ * @param {Promise<[SharedArrayBuffer, Options, MessageChannel | BroadcastChannel]>} promise
28
+ * @param {(event:MessageEvent) => void} listener
29
+ * @returns
30
+ */
12
31
  export const handler = (promise, listener) => {
13
32
  addEventListener('message', listener, { once: true });
33
+ /**
34
+ * @param {Options} options
35
+ * @returns
36
+ */
14
37
  return options => promise.then(
15
38
  ([sab, main, channel]) => {
16
39
  postMessage(1);
17
- return handle(channel, new Int32Array(sab), { ...main, ...options });
40
+ return handle(channel, new Int32Array(sab), sender({ ...main, ...options }));
18
41
  }
19
42
  );
20
43
  };
21
-
22
- export { withResolvers };
@@ -0,0 +1,38 @@
1
+ import withResolvers from '@webreflection/utils/with-resolvers';
2
+ import i32 from 'weak-id/i32';
3
+
4
+ import sender from './sender.js';
5
+
6
+ const { parse, stringify } = JSON;
7
+
8
+ const { promise, resolve } = withResolvers();
9
+
10
+ addEventListener(
11
+ 'message',
12
+ ({ data: [sab, main, channel] }) => resolve([sab, main, channel]),
13
+ { once: true }
14
+ );
15
+
16
+ export const channel = 'xhr';
17
+
18
+ const handle = (channel, i32a, options) => {
19
+ const bc = new BroadcastChannel(channel);
20
+ const next = i32();
21
+ const { serviceWorker } = options;
22
+ return (payload, ...rest) => {
23
+ const id = next();
24
+ // @ts-ignore
25
+ bc.postMessage([id, payload], ...rest);
26
+ const xhr = new XMLHttpRequest;
27
+ xhr.open('POST', serviceWorker, false);
28
+ xhr.setRequestHeader('Content-Type', 'application/json');
29
+ xhr.send(stringify([id, channel]));
30
+ i32a.set(parse(xhr.responseText), 0);
31
+ return options.onsync(i32a.subarray(2, 2 + i32a[1]));
32
+ };
33
+ };
34
+
35
+ export default options => promise.then(([sab, main, channel]) => {
36
+ postMessage(1);
37
+ return handle(channel, new Int32Array(sab), sender({ ...main, ...options }));
38
+ });
package/src/xhr.js ADDED
@@ -0,0 +1,8 @@
1
+ import main from './main/xhr.js';
2
+ import worker from './worker/xhr.js';
3
+
4
+ export { channel } from './worker/xhr.js';
5
+
6
+ export default (
7
+ 'importScripts' in globalThis ? worker : main
8
+ );
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <style>body{font-family:sans-serif}</style>
7
+ <title>reflected README</title>
8
+ <script src="./mini-coi.js"></script>
9
+ <script type="module" src="./index.js"></script>
10
+ </head>
11
+ </html>
@@ -0,0 +1,54 @@
1
+ import reflect, { channel } from '../../dist/index.js';
2
+
3
+ function test_sum(...args) {
4
+ let i = 0;
5
+ while (args.length)
6
+ i += args.pop();
7
+ return i;
8
+ }
9
+
10
+ // ℹ️ must await the initialization
11
+ const worker = await reflect(
12
+ // Worker scriptURL
13
+ './worker.js',
14
+ // Worker options + required utilities / helpers
15
+ // ℹ️ type is enforced to be 'module' due top-level await
16
+ {
17
+ // invoked when the worker asks to synchronize a call
18
+ // and it mmust return an Int32Array reference to populate
19
+ // the SharedArrayBuffer and notify/unlock the worker
20
+ // ℹ️ works even if synchronous but it's resolved asynchronously
21
+ // ⚠️ the worker is not responsive until this returns so
22
+ // be sure you handle errors gracefully to still provide
23
+ // a result the worker can consume out of the shared buffer!
24
+ async onsync(payload) {
25
+ const { invoke, args } = payload;
26
+
27
+ if (invoke === 'test_sum') {
28
+ // just demoing this can be async too
29
+ const value = await test_sum(...args);
30
+ return new Int32Array([value]);
31
+ }
32
+
33
+ // errors should still be Int32Array but
34
+ // it is trivial to return no result
35
+ return new Int32Array(0);
36
+ },
37
+
38
+ // optional: the initial SharedArrayBuffer length
39
+ initByteLength: 1024,
40
+
41
+ // optional: the max possible SharedArrayBuffer growth
42
+ maxByteLength: 8192,
43
+
44
+ // optional: the service worker as fallback
45
+ // * if it's a string, it's used to register it
46
+ // * if it's an object, it's used to initialize it
47
+ // but it must contain a `url` field to register it
48
+ // ℹ️ if already registered it will not try to register it
49
+ serviceWorker: undefined,
50
+ }
51
+ );
52
+
53
+ document.body.append(channel, ' ');
54
+ document.body.append(JSON.stringify(await worker.send({ any: 'payload' })));
@@ -0,0 +1,28 @@
1
+ /*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
2
+ /*! mini-coi - Andrea Giammarchi and contributors, licensed under MIT */
3
+ (({ document: d, navigator: { serviceWorker: s } }) => {
4
+ if (d) {
5
+ const { currentScript: c } = d;
6
+ s.register(c.src, { scope: c.getAttribute('scope') || '.' }).then(r => {
7
+ r.addEventListener('updatefound', () => location.reload());
8
+ if (r.active && !s.controller) location.reload();
9
+ });
10
+ }
11
+ else {
12
+ addEventListener('install', () => skipWaiting());
13
+ addEventListener('activate', e => e.waitUntil(clients.claim()));
14
+ addEventListener('fetch', e => {
15
+ const { request: r } = e;
16
+ if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return;
17
+ e.respondWith(fetch(r).then(r => {
18
+ const { body, status, statusText } = r;
19
+ if (!status || status > 399) return r;
20
+ const h = new Headers(r.headers);
21
+ h.set('Cross-Origin-Opener-Policy', 'same-origin');
22
+ h.set('Cross-Origin-Embedder-Policy', 'require-corp');
23
+ h.set('Cross-Origin-Resource-Policy', 'cross-origin');
24
+ return new Response(status == 204 ? null : body, { status, statusText, headers: h });
25
+ }));
26
+ });
27
+ }
28
+ })(self);
@@ -0,0 +1,26 @@
1
+ import reflect, { channel } from '../../dist/index.js';
2
+
3
+ // ℹ️ must await the initialization
4
+ const reflected = await reflect({
5
+ // receives the returned data from the main thread.
6
+ // use this helper to transform such data into something
7
+ // that the worker can use/understand after invoke
8
+ // ℹ️ must be synchronous and it's invoked synchronously
9
+ onsync(response) {
10
+ return response.length ? response[0] : undefined;
11
+ },
12
+
13
+ async onsend(payload) {
14
+ return payload;
15
+ },
16
+ });
17
+
18
+ // retrive the result of `test_sum(1, 2, 3)`
19
+ // directly from the main thread.
20
+ // only the async channel variant would need to await
21
+ const value = reflected({
22
+ invoke: 'test_sum',
23
+ args: [1, 2, 3]
24
+ });
25
+
26
+ console.log({ channel, value });
package/test/index.html CHANGED
@@ -3,16 +3,15 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Document</title>
6
+ <style>body{font-family:sans-serif}</style>
7
+ <title>reflected</title>
8
+ <!-- <script src="mini-coi.js"></script> -->
7
9
  <script type="module">
8
- import reflected from '../src/index.js';
9
- const ref = await reflected('./worker.js', {
10
- ondata: async (data, ...rest) => {
11
- console.log('main', data, rest);
12
- await new Promise(resolve => setTimeout(resolve, 1000));
13
- return new Int32Array([6, 7, 8, 9, 10]);
14
- }
15
- });
10
+ document.body.prepend(navigator.userAgent);
11
+ import('./index.js');
16
12
  </script>
17
13
  </head>
14
+ <body>
15
+ <pre id="log"></pre>
16
+ </body>
18
17
  </html>