reflected 0.1.3 → 0.1.4

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.
package/README.md CHANGED
@@ -143,7 +143,7 @@ This module also exports a bare-minimum way to directly drive, whenever the *cha
143
143
 
144
144
  ```js
145
145
  // main.js module
146
- import reflect from 'reflected/ffi/main';
146
+ import reflect from 'https://esm.run/reflected/ffi/main';
147
147
 
148
148
  // returns a worker with a special `ffi` field/namespace
149
149
  // directly from reflected-ffi project
@@ -151,7 +151,7 @@ const worker = await reflect('./worker.js', { serviceWorker: './sw.js' });
151
151
 
152
152
 
153
153
  // worker.js module
154
- import reflect from 'reflected/ffi/worker';
154
+ import reflect from 'https://esm.run/reflected/ffi/worker';
155
155
 
156
156
  // retrieve the reflected-ffi namespace as it is
157
157
  const ffi = await worker();
package/package.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "reflected",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A primitive to allow workers to call synchronously any functionality exposed on the main thread.",
5
+ "files": [
6
+ "dist",
7
+ "types",
8
+ "reflected.tar.gz",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
5
12
  "exports": {
6
13
  ".": {
7
14
  "import": "./src/index.js",
@@ -66,7 +73,7 @@
66
73
  "./package.json": "./package.json"
67
74
  },
68
75
  "scripts": {
69
- "build": "rm -rf dist && rollup -c rollup.js && cp dist/sw.js test/sw.js && npm run types",
76
+ "build": "rm -rf dist && rollup -c rollup.js && cp dist/sw.js test/sw.js && cp -R ./dist ./reflected && tar -czf ./reflected.tar.gz ./reflected && rm -rf ./reflected && npm run types",
70
77
  "types": "tsc --allowJs --checkJs --lib dom,esnext --module nodeNext --target esnext -d --emitDeclarationOnly --outDir ./types ./src/*.js ./src/*/*.js"
71
78
  },
72
79
  "keywords": [
@@ -92,9 +99,6 @@
92
99
  "@rollup/plugin-terser": "^0.4.4",
93
100
  "rollup": "^4.57.1"
94
101
  },
95
- "directories": {
96
- "test": "test"
97
- },
98
102
  "repository": {
99
103
  "type": "git",
100
104
  "url": "git+https://github.com/WebReflection/reflected.git"
Binary file
package/rollup.js DELETED
@@ -1,75 +0,0 @@
1
- import { nodeResolve } from '@rollup/plugin-node-resolve';
2
- import terser from '@rollup/plugin-terser';
3
-
4
- import { writeFileSync } from 'fs';
5
- if (process.env.NEW_CHANNEL)
6
- writeFileSync(`./src/channel.js`, `export default '${crypto.randomUUID()}';`);
7
-
8
- const plugins = [nodeResolve()].concat(process.env.NO_MIN ? [] : [terser()]);
9
-
10
- export default [
11
- {
12
- plugins,
13
- input: './src/index.js',
14
- output: {
15
- esModule: true,
16
- dir: './dist'
17
- }
18
- },
19
- {
20
- plugins,
21
- input: './src/broadcast.js',
22
- output: {
23
- esModule: true,
24
- dir: './dist'
25
- }
26
- },
27
- {
28
- plugins,
29
- input: './src/message.js',
30
- output: {
31
- esModule: true,
32
- dir: './dist'
33
- }
34
- },
35
- {
36
- plugins,
37
- input: './src/xhr.js',
38
- output: {
39
- esModule: true,
40
- dir: './dist'
41
- }
42
- },
43
- {
44
- plugins,
45
- input: './src/async.js',
46
- output: {
47
- esModule: true,
48
- dir: './dist'
49
- }
50
- },
51
- {
52
- plugins,
53
- input: './src/service/worker.js',
54
- output: {
55
- esModule: true,
56
- file: './dist/sw.js'
57
- }
58
- },
59
- {
60
- plugins,
61
- input: './src/ffi/main.js',
62
- output: {
63
- esModule: true,
64
- dir: './dist/ffi'
65
- }
66
- },
67
- {
68
- plugins,
69
- input: './src/ffi/worker.js',
70
- output: {
71
- esModule: true,
72
- dir: './dist/ffi'
73
- }
74
- },
75
- ];
package/src/async.js DELETED
@@ -1,8 +0,0 @@
1
- import main from './main/async.js';
2
- import worker from './worker/async.js';
3
-
4
- export { channel } from './worker/async.js';
5
-
6
- export default (
7
- 'importScripts' in globalThis ? worker : main
8
- );
package/src/broadcast.js DELETED
@@ -1,8 +0,0 @@
1
- import main from './main/broadcast.js';
2
- import worker from './worker/broadcast.js';
3
-
4
- export { channel } from './worker/broadcast.js';
5
-
6
- export default (
7
- 'importScripts' in globalThis ? worker : main
8
- );
package/src/channel.js DELETED
@@ -1 +0,0 @@
1
- export default 'fc260aad-4404-43b8-ae9d-2c06554bb294';
package/src/ffi/main.js DELETED
@@ -1,37 +0,0 @@
1
- import local from 'reflected-ffi/local';
2
-
3
- import main from '../index.js';
4
-
5
- const { assign } = Object;
6
-
7
- /**
8
- * @param {string} url
9
- * @param {import('../index.js').MainOptions & import('reflected-ffi/local').LocalOptions} options
10
- */
11
- export default async (url, options) => {
12
- const ffi = local({
13
- timeout: 0,
14
- buffer: true,
15
- ...options,
16
- // @ts-ignore
17
- reflect: (...args) => worker.send(args),
18
- });
19
-
20
- const worker = /** @type {Worker} */(await main(
21
- url,
22
- {
23
- ...options,
24
- // @ts-ignore
25
- onsync: args => ffi.reflect(...args),
26
- }
27
- ));
28
-
29
- const { terminate } = worker;
30
- return assign(worker, {
31
- ffi,
32
- terminate() {
33
- ffi.terminate();
34
- terminate.call(worker);
35
- },
36
- });
37
- };
package/src/ffi/worker.js DELETED
@@ -1,24 +0,0 @@
1
- import remote from 'reflected-ffi/remote';
2
-
3
- import worker from '../index.js';
4
-
5
- /**
6
- * @param {import('../index.js').WorkerOptions & import('reflected-ffi/remote').RemoteOptions} options
7
- */
8
- export default async options => {
9
- const reflected = await worker({
10
- ...options,
11
- onsync: args => args,
12
- // @ts-ignore
13
- onsend: args => ffi.reflect(...args),
14
- });
15
-
16
- const ffi = remote({
17
- timeout: 0,
18
- buffer: true,
19
- ...options,
20
- reflect: (...args) => reflected(args),
21
- });
22
-
23
- return ffi;
24
- };
package/src/index.js DELETED
@@ -1,57 +0,0 @@
1
- import withResolvers from '@webreflection/utils/with-resolvers';
2
- import { native } from '@webreflection/utils/shared-array-buffer';
3
-
4
- /** @typedef {import('./main/shared.js').Options} MainOptions */
5
- /** @typedef {import('./worker/shared.js').Options} WorkerOptions */
6
-
7
- /** @type {string} */
8
- let channel;
9
-
10
- /** @type {Function} */
11
- let module;
12
-
13
- if ('importScripts' in globalThis) {
14
- let get;
15
- const { promise, resolve } = withResolvers();
16
- // @ts-ignore
17
- const reflected = new URL(location).searchParams.get('reflected');
18
- channel = reflected;
19
- if (reflected === 'message') get = import(/* webpackIgnore: true */'./worker/message.js');
20
- else if (reflected === 'broadcast') get = import(/* webpackIgnore: true */'./worker/broadcast.js');
21
- else if (reflected === 'xhr') get = import(/* webpackIgnore: true */'./worker/xhr.js');
22
- else get = import(/* webpackIgnore: true */'./worker/async.js');
23
- module = async options => {
24
- const { data, ports } = await promise;
25
- const { default: reflect } = await get;
26
- const event = new Event('message');
27
- // @ts-ignore
28
- event.data = data;
29
- // @ts-ignore
30
- event.ports = ports;
31
- dispatchEvent(event);
32
- return reflect(options);
33
- };
34
- addEventListener('message', resolve, { once: true });
35
- }
36
- else if (native) {
37
- if ('InstallTrigger' in globalThis) {
38
- channel = 'broadcast';
39
- module = (await import(/* webpackIgnore: true */'./main/broadcast.js')).default;
40
- }
41
- else {
42
- channel = 'message';
43
- module = (await import(/* webpackIgnore: true */'./main/message.js')).default;
44
- }
45
- }
46
- else if (navigator.serviceWorker) {
47
- channel = 'xhr';
48
- module = (await import(/* webpackIgnore: true */'./main/xhr.js')).default;
49
- }
50
- else {
51
- channel = 'async';
52
- module = (await import(/* webpackIgnore: true */'./main/async.js')).default;
53
- }
54
-
55
- export { channel };
56
-
57
- export default module;
package/src/main/async.js DELETED
@@ -1,29 +0,0 @@
1
- import Sender from './sender.js';
2
- import SAB from './sab.js';
3
- import { bootstrap, handler, post, url } from './shared.js';
4
- import { byteOffset, randomUUID } from '../shared.js';
5
-
6
- const CHANNEL = 'async';
7
-
8
- export class Worker extends Sender {
9
- constructor(scriptURL, options, resolve) {
10
- const channel = randomUUID();
11
- const bc = new BroadcastChannel(channel);
12
- const sab = SAB(options);
13
- const i32a = new Int32Array(sab);
14
- const handle = handler(sab, options, false);
15
- bc.addEventListener('message', async ({ data: [id, payload] }) => {
16
- await handle({ data: payload });
17
- bc.postMessage([id, new Uint8Array(sab, byteOffset, i32a[1])]);
18
- });
19
- super(...url(scriptURL, CHANNEL, options));
20
- super.addEventListener('message', () => resolve(this), { once: true });
21
- super.postMessage(post(sab, options).concat(channel));
22
- }
23
-
24
- get channel() {
25
- return CHANNEL;
26
- }
27
- };
28
-
29
- export default bootstrap(Worker);
@@ -1,21 +0,0 @@
1
- import Sender from './sender.js';
2
- import { SAB, bootstrap, handler, post, url } from './shared.js';
3
- import { randomUUID } from '../shared.js';
4
-
5
- const CHANNEL = 'broadcast';
6
-
7
- export default bootstrap(class Worker extends Sender {
8
- constructor(scriptURL, options, resolve) {
9
- const channel = randomUUID();
10
- const bc = new BroadcastChannel(channel);
11
- const sab = SAB(options);
12
- bc.addEventListener('message', handler(sab, options, true));
13
- super(...url(scriptURL, CHANNEL, options));
14
- super.addEventListener('message', () => resolve(this), { once: true });
15
- super.postMessage(post(sab, options).concat(channel));
16
- }
17
-
18
- get channel() {
19
- return CHANNEL;
20
- }
21
- });
@@ -1,20 +0,0 @@
1
- import Sender from './sender.js';
2
- import { SAB, bootstrap, handler, post, url } from './shared.js';
3
-
4
- const CHANNEL = 'message';
5
-
6
- export default bootstrap(class Worker extends Sender {
7
- constructor(scriptURL, options, resolve) {
8
- const { port1, port2 } = new MessageChannel;
9
- const sab = SAB(options);
10
- port1.addEventListener(CHANNEL, handler(sab, options, true));
11
- port1.start();
12
- super(...url(scriptURL, CHANNEL, options));
13
- super.addEventListener(CHANNEL, () => resolve(this), { once: true });
14
- super.postMessage(post(sab, options), [port2]);
15
- }
16
-
17
- get channel() {
18
- return CHANNEL;
19
- }
20
- });
package/src/main/sab.js DELETED
@@ -1,12 +0,0 @@
1
- import { SharedArrayBuffer } from '@webreflection/utils/shared-array-buffer';
2
-
3
- import { byteOffset } from '../shared.js';
4
-
5
- export default ({
6
- initByteLength = 1024,
7
- maxByteLength = (1024 * 8)
8
- }) =>
9
- new SharedArrayBuffer(
10
- byteOffset + initByteLength,
11
- { maxByteLength: byteOffset + maxByteLength }
12
- );
@@ -1,38 +0,0 @@
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.length === 2 && 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,99 +0,0 @@
1
- import withResolvers from '@webreflection/utils/with-resolvers';
2
- import { encoder } from 'reflected-ffi/encoder';
3
-
4
- import { byteOffset } from '../shared.js';
5
-
6
- const { notify, store } = Atomics;
7
-
8
- export const SAB = ({
9
- initByteLength = 1024,
10
- maxByteLength = (1024 * 8)
11
- }) =>
12
- new SharedArrayBuffer(
13
- byteOffset + initByteLength,
14
- { maxByteLength: byteOffset + maxByteLength }
15
- );
16
-
17
- /**
18
- * @typedef {Object} ServiceWorkerOptions see https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register#options
19
- * @property {string} [url] will use the `serviceWorker` value if that is a `string`, otherwise it refers to where the service worker file is located.
20
- * @property {'classic' | 'module'} [type]
21
- * @property {'all' | 'imports' | 'none'} [updateViaCache]
22
- */
23
-
24
- /**
25
- * @typedef {Object} Options
26
- * @property {(payload: unknown) => Int32Array | Promise<Int32Array>} onsync invoked when the worker expect a response as `Int32Array` to populate the SharedArrayBuffer with.
27
- * @property {(payload: unknown) => unknown | Promise<unknown>} [onsend] invoked when the worker replies to a `worker.send(data)` call.
28
- * @property {number} [initByteLength=1024] defines the initial byte length of the SharedArrayBuffer.
29
- * @property {number} [maxByteLength=8192] defines the maximum byte length (growth) of the SharedArrayBuffer.
30
- * @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.
31
- * @property {import('reflected-ffi/encoder').encoder} [encoder] defines the encoder function to use to encode the result into the SharedArrayBuffer.
32
- */
33
-
34
- /**
35
- * Initialize the worker channel communication and resolves with the worker instance.
36
- * @template T
37
- * @param {T} Worker
38
- * @returns
39
- */
40
- export const bootstrap = Worker => {
41
- /**
42
- * @param {string} scriptURL
43
- * @param {Options} options
44
- * @returns
45
- */
46
- return (scriptURL, options) => {
47
- const { promise, resolve } = withResolvers();
48
- // @ts-ignore
49
- new Worker(scriptURL, options, resolve);
50
- return /** @type {Promise<T>} */(promise);
51
- };
52
- };
53
-
54
- export const handler = (sab, options, useAtomics) => {
55
- const i32a = new Int32Array(sab);
56
- const encode = (options.encoder ?? encoder)({ byteOffset });
57
-
58
- const resolve = useAtomics ?
59
- (length => {
60
- store(i32a, 1, length);
61
- store(i32a, 0, 1);
62
- notify(i32a, 0);
63
- }) :
64
- (length => {
65
- i32a[1] = length;
66
- i32a[0] = 1;
67
- });
68
-
69
- const process = result => {
70
- const length = encode(result, sab);
71
- return typeof length === 'number' ? resolve(length) : length.then(resolve);
72
- };
73
-
74
- return async ({ data }) => process(await options.onsync(data));
75
- };
76
-
77
- const isOK = value => {
78
- switch (typeof value) {
79
- case 'symbol':
80
- case 'function':
81
- return false;
82
- }
83
- return true;
84
- };
85
-
86
- export const post = (sab, options) => {
87
- const opts = {};
88
- for (const key in options) {
89
- const value = options[key];
90
- if (isOK(key) && isOK(value)) opts[key] = value;
91
- }
92
- return [sab, opts];
93
- };
94
-
95
- export const url = (scriptURL, reflected, options) => {
96
- const url = new URL(scriptURL, location.href);
97
- url.searchParams.set('reflected', reflected);
98
- return [url, { ...options, type: 'module' }];
99
- };
package/src/main/xhr.js DELETED
@@ -1,102 +0,0 @@
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 { byteOffset, 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
- const promise = responses.get(id);
22
- responses.delete(id);
23
- sharedBC.postMessage(['response', [uid, await promise]]);
24
- }
25
- }
26
- });
27
-
28
- const { promise: sw, resolve } = withResolvers();
29
- let init = true;
30
-
31
- const activate = (swc, options) => {
32
- let w, c = true, { url } = options;
33
- swc.getRegistration(url)
34
- .then(r => (r ?? swc.register(url, options)))
35
- .then(function ready(r) {
36
- const { controller } = swc;
37
- c = c && !!controller;
38
- w = (r.installing || r.waiting || r.active);
39
- if (!w) return activate(swc, options);
40
- if (w.state === 'activated') {
41
- if (c) {
42
- // allow ServiceWorker swap on different URL
43
- if (controller.scriptURL === url)
44
- return resolve();
45
- r.unregister();
46
- }
47
- location.reload();
48
- }
49
- else {
50
- w.addEventListener('statechange', () => ready(r), { once: true });
51
- }
52
- });
53
- };
54
-
55
- class Worker extends Sender {
56
- #channel;
57
- constructor(scriptURL, options, resolve) {
58
- if (init) {
59
- init = false;
60
- let { serviceWorker } = options;
61
- if (!serviceWorker) {
62
- // @ts-ignore
63
- return new AsyncWorker(scriptURL, options, resolve);
64
- }
65
- if (typeof serviceWorker === 'string') serviceWorker = { url: serviceWorker };
66
- serviceWorker.url = new URL(serviceWorker.url, location.href).href;
67
- activate(navigator.serviceWorker, serviceWorker);
68
- }
69
- const channel = randomUUID();
70
- const bc = new BroadcastChannel(channel);
71
- const sab = SAB(options);
72
- const responses = new Map;
73
- const i32a = new Int32Array(sab);
74
- const handle = handler(sab, options, false);
75
- channels.set(channel, responses);
76
- bc.addEventListener('message', async ({ data: [id, payload] }) => {
77
- const { promise, resolve } = withResolvers();
78
- responses.set(id, promise);
79
- await handle({ data: payload });
80
- resolve(new Uint8Array(sab, byteOffset, i32a[1]));
81
- });
82
- super(...url(scriptURL, CHANNEL, options));
83
- super.addEventListener('message', () => resolve(this), { once: true });
84
- super.postMessage(post(sab, options).concat(channel));
85
- this.#channel = channel;
86
- }
87
-
88
- terminate() {
89
- channels.delete(this.#channel);
90
- super.terminate();
91
- }
92
-
93
- get channel() {
94
- return CHANNEL;
95
- }
96
- };
97
-
98
- export default (scriptURL, options) => {
99
- const { promise, resolve } = withResolvers();
100
- const worker = new Worker(scriptURL, options, resolve);
101
- return worker instanceof AsyncWorker ? promise : sw.then(() => promise);
102
- };
package/src/message.js DELETED
@@ -1,8 +0,0 @@
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
- );
@@ -1,47 +0,0 @@
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 response = {
19
- status: 200,
20
- statusText: 'OK',
21
- headers: new Headers({
22
- 'Cache-Control': 'no-cache, must-revalidate',
23
- 'Expires': 'Mon, 26 Jul 1997 05:00:00 GMT',
24
- 'Content-type': 'application/json',
25
- })
26
- };
27
-
28
- const respond = async details => {
29
- const [uid, promise] = next();
30
- bc.postMessage(['request', [uid, details]]);
31
- return new Response(await promise, response);
32
- };
33
-
34
- // @ts-ignore
35
- export const activate = event => event.waitUntil(clients.claim());
36
-
37
- export const fetch = async event => {
38
- const { request: r } = event;
39
- if (r.method === 'POST' && r.url.startsWith(url)) {
40
- event.stopImmediatePropagation();
41
- event.respondWith(r.json().then(respond));
42
- event.preventDefault();
43
- }
44
- };
45
-
46
- // @ts-ignore
47
- export const install = () => skipWaiting();
@@ -1,5 +0,0 @@
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 DELETED
@@ -1,12 +0,0 @@
1
- export const byteOffset = Int32Array.BYTES_PER_ELEMENT * 2;
2
-
3
- let hasRandomUUID = true;
4
- try {
5
- crypto.randomUUID();
6
- } catch (_) {
7
- hasRandomUUID = false;
8
- }
9
-
10
- export const randomUUID = hasRandomUUID ?
11
- (() => crypto.randomUUID() ):
12
- (() => (Date.now() + Math.random()).toString(36));
@@ -1,40 +0,0 @@
1
- import withResolvers from '@webreflection/utils/with-resolvers';
2
- import { decoder } from 'reflected-ffi/decoder';
3
- import i32 from 'weak-id/i32';
4
-
5
- import sender from './sender.js';
6
- import { byteOffset } from '../shared.js';
7
-
8
- const { promise, resolve } = withResolvers();
9
-
10
- addEventListener(
11
- 'message',
12
- ({ data: [_, main, channel] }) => resolve([main, channel]),
13
- { once: true }
14
- );
15
-
16
- export const channel = 'async';
17
-
18
- const handle = (channel, options) => {
19
- const bc = new BroadcastChannel(channel);
20
- const next = i32();
21
- const decode = (options.decoder ?? decoder)({ byteOffset });
22
- const map = new Map;
23
- bc.addEventListener('message', ({ data: [id, { length, buffer }] }) => {
24
- map.get(id)(options.onsync(length ? decode(length, buffer) : void 0));
25
- map.delete(id);
26
- });
27
- return (payload, ...rest) => {
28
- const { promise, resolve } = withResolvers();
29
- const id = next();
30
- map.set(id, resolve);
31
- // @ts-ignore
32
- bc.postMessage([id, payload], ...rest);
33
- return promise;
34
- };
35
- };
36
-
37
- export default options => promise.then(([main, channel]) => {
38
- postMessage(1);
39
- return handle(channel, sender({ ...main, ...options }));
40
- });
@@ -1,11 +0,0 @@
1
- import withResolvers from '@webreflection/utils/with-resolvers';
2
- import { handler } from './shared.js';
3
-
4
- const { promise, resolve } = withResolvers();
5
-
6
- export const channel = 'broadcast';
7
-
8
- export default handler(
9
- promise,
10
- ({ data: [sab, main, channel] }) => resolve([sab, main, new BroadcastChannel(channel)]),
11
- );
@@ -1,11 +0,0 @@
1
- import withResolvers from '@webreflection/utils/with-resolvers';
2
- import { handler } from './shared.js';
3
-
4
- const { promise, resolve } = withResolvers();
5
-
6
- export const channel = 'message';
7
-
8
- export default handler(
9
- promise,
10
- ({ data: [sab, main], ports: [channel] }) => resolve([sab, main, channel])
11
- );
@@ -1,16 +0,0 @@
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,52 +0,0 @@
1
- import { decoder } from 'reflected-ffi/decoder';
2
-
3
- import sender from './sender.js';
4
- import { byteOffset } from '../shared.js';
5
-
6
- const { load, store, wait } = Atomics;
7
-
8
- /**
9
- * @typedef {Object} Options
10
- * @property {(payload: Int32Array) => unknown} onsync transforms the resulting `Int32Array` from *main* thread into a value usable within the worker.
11
- * @property {(payload: unknown) => unknown |Promise<unknown>} onsend invoked to define what to return to the *main* thread when it calls `worker.send(payload)`.
12
- * @property {import('reflected-ffi/decoder').decoder} [decoder] defines the decoder function to use to decode the result from the SharedArrayBuffer.
13
- */
14
-
15
- /**
16
- * @param {MessageChannel | BroadcastChannel} channel
17
- * @param {Int32Array} i32a
18
- * @param {Options} options
19
- * @returns {(payload: unknown, ...rest: unknown[]) => unknown}
20
- */
21
- const handle = (channel, i32a, options) => {
22
- const decode = (options.decoder ?? decoder)({ byteOffset });
23
- const { buffer } = i32a;
24
- return (payload, ...rest) => {
25
- // @ts-ignore
26
- channel.postMessage(payload, ...rest);
27
- wait(i32a, 0, 0);
28
- store(i32a, 0, 0);
29
- const length = load(i32a, 1);
30
- return options.onsync(length ? decode(length, buffer) : void 0);
31
- };
32
- };
33
-
34
- /**
35
- *
36
- * @param {Promise<[SharedArrayBuffer, Options, MessageChannel | BroadcastChannel]>} promise
37
- * @param {(event:MessageEvent) => void} listener
38
- * @returns
39
- */
40
- export const handler = (promise, listener) => {
41
- addEventListener('message', listener, { once: true });
42
- /**
43
- * @param {Options} options
44
- * @returns
45
- */
46
- return options => promise.then(
47
- ([sab, main, channel]) => {
48
- postMessage(1);
49
- return handle(channel, new Int32Array(sab), sender({ ...main, ...options }));
50
- }
51
- );
52
- };
package/src/worker/xhr.js DELETED
@@ -1,40 +0,0 @@
1
- import withResolvers from '@webreflection/utils/with-resolvers';
2
- import { decoder } from 'reflected-ffi/decoder';
3
- import i32 from 'weak-id/i32';
4
-
5
- import sender from './sender.js';
6
-
7
- const { parse, stringify } = JSON;
8
-
9
- const { promise, resolve } = withResolvers();
10
-
11
- addEventListener(
12
- 'message',
13
- ({ data: [_, main, channel] }) => resolve([main, channel]),
14
- { once: true }
15
- );
16
-
17
- export const channel = 'xhr';
18
-
19
- const handle = (channel, options) => {
20
- const bc = new BroadcastChannel(channel);
21
- const next = i32();
22
- const decode = (options.decoder ?? decoder)({ byteOffset: 0 });
23
- const { serviceWorker } = options;
24
- return (payload, ...rest) => {
25
- const id = next();
26
- // @ts-ignore
27
- bc.postMessage([id, payload], ...rest);
28
- const xhr = new XMLHttpRequest;
29
- xhr.open('POST', serviceWorker, false);
30
- xhr.setRequestHeader('Content-Type', 'application/json');
31
- xhr.send(stringify([id, channel]));
32
- const { length, buffer } = new Uint8Array(parse(xhr.responseText));
33
- return options.onsync(length ? decode(length, buffer) : void 0);
34
- };
35
- };
36
-
37
- export default options => promise.then(([main, channel]) => {
38
- postMessage(1);
39
- return handle(channel, sender({ ...main, ...options }));
40
- });
package/src/xhr.js DELETED
@@ -1,8 +0,0 @@
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
- );
@@ -1,11 +0,0 @@
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>
@@ -1,50 +0,0 @@
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
- // ℹ️ works even if synchronous but it's resolved asynchronously
19
- // ⚠️ the worker is not responsive until this returns so
20
- // be sure you handle errors gracefully to still provide
21
- // a result the worker can consume out of the shared buffer!
22
- async onsync(payload) {
23
- const { invoke, args } = payload;
24
-
25
- if (invoke === 'test_sum') {
26
- // just demoing this can be async too
27
- return await test_sum(...args);
28
- }
29
-
30
- // it is trivial to return no result or even errors
31
- return new Error('unknown ' + invoke);
32
- },
33
-
34
- // optional: the initial SharedArrayBuffer length
35
- initByteLength: 1024,
36
-
37
- // optional: the max possible SharedArrayBuffer growth
38
- maxByteLength: 8192,
39
-
40
- // optional: the service worker as fallback
41
- // * if it's a string, it's used to register it
42
- // * if it's an object, it's used to initialize it
43
- // but it must contain a `url` field to register it
44
- // ℹ️ if already registered it will not try to register it
45
- serviceWorker: undefined,
46
- }
47
- );
48
-
49
- document.body.append(channel, ' ');
50
- document.body.append(JSON.stringify(await worker.send({ any: 'payload' })));
@@ -1,26 +0,0 @@
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;
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 });
@@ -1,10 +0,0 @@
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 ffi</title>
8
- <script type="module" src="./index.js"></script>
9
- </head>
10
- </html>
package/test/ffi/index.js DELETED
@@ -1,8 +0,0 @@
1
- import worker from '../../dist/ffi/main.js';
2
-
3
- const w = await worker('./worker.js', { serviceWorker: './sw.js' });
4
-
5
- document.body.append('channel: ', w.channel, ' - ');
6
-
7
- console.log(w);
8
-
package/test/ffi/sw.js DELETED
@@ -1 +0,0 @@
1
- const e=Promise.withResolvers||function(){var e,t,n=new this((n,s)=>{e=n,t=s});return{resolve:e,reject:t,promise:n}};var t=e.bind(Promise);const[n,s]=((e=e=>e)=>{const n=new Map,s=(()=>{const e=new Int32Array(1);return()=>e[0]++})();return[()=>{let a;do{a=e(s())}while(n.has(a));const o=t();return n.set(a,o),[a,o.promise]},(e,t,s)=>{const a=n.get(e);n.delete(e),s?a?.reject(s):a?.resolve(t)}]})(),{protocol:a,host:o,pathname:r}=location,i=`${a}//${o}${r}`,c=new BroadcastChannel("fc260aad-4404-43b8-ae9d-2c06554bb294");c.addEventListener("message",({data:[e,t]})=>{if("response"===e){const[e,n]=t;s(e,`[${n.join(",")}]`)}});const d={status:200,statusText:"OK",headers:new Headers({"Cache-Control":"no-cache, must-revalidate",Expires:"Mon, 26 Jul 1997 05:00:00 GMT","Content-type":"application/json"})},l=async e=>{const[t,s]=n();return c.postMessage(["request",[t,e]]),new Response(await s,d)};addEventListener("activate",e=>e.waitUntil(clients.claim())),addEventListener("fetch",async e=>{const{request:t}=e;"POST"===t.method&&t.url.startsWith(i)&&(e.stopImmediatePropagation(),e.respondWith(t.json().then(l)),e.preventDefault())}),addEventListener("install",()=>skipWaiting());
@@ -1,13 +0,0 @@
1
- import worker from '../../dist/ffi/worker.js';
2
-
3
- const ffi = await worker();
4
-
5
- console.log(ffi);
6
-
7
- const body = ffi.query(ffi.global, 'document.body');
8
-
9
- body.addEventListener('click', event => {
10
- console.log(event.type);
11
- });
12
-
13
- body.append('Hello World');
package/test/index.html DELETED
@@ -1,17 +0,0 @@
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</title>
8
- <!-- <script src="mini-coi.js"></script> -->
9
- <script type="module">
10
- document.body.prepend(navigator.userAgent);
11
- import('./index.js');
12
- </script>
13
- </head>
14
- <body>
15
- <pre id="log"></pre>
16
- </body>
17
- </html>
package/test/index.js DELETED
@@ -1,35 +0,0 @@
1
- import reflected, { channel } from '../dist/index.js';
2
- log.append(`bootstrapping: ${channel}\n`);
3
-
4
- try {
5
- const worker = await reflected('./worker.js', {
6
- serviceWorker: location.search === '?async' ? undefined : './sw.js',
7
- onsend: async (data, ...rest) => {
8
- // console.log('onsend', data, rest);
9
- return data;
10
- },
11
- onsync: async (data, ...rest) => {
12
- // console.log('main', data, rest);
13
- // await new Promise(resolve => setTimeout(resolve, 1000));
14
- return [6, 7, 8, 9, 10];
15
- },
16
- onerror: console.error,
17
- });
18
-
19
- log.append(`consuming: ${worker.channel}\n`);
20
-
21
- worker.addEventListener(
22
- 'message',
23
- (event) => {
24
- log.append(`reflected channel: ${event.data}\n`);
25
- },
26
- { once: true }
27
- );
28
-
29
- worker.send([1, 2, 3, 4, 5]).then(data => {
30
- log.append(`send: [${data}]\n`);
31
- });
32
- } catch (error) {
33
- log.append(`error: ${error.message}\n`);
34
- }
35
-
package/test/mini-coi.js DELETED
@@ -1,28 +0,0 @@
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);
package/test/sw.js DELETED
@@ -1 +0,0 @@
1
- const e=Promise.withResolvers||function(){var e,t,n=new this((n,s)=>{e=n,t=s});return{resolve:e,reject:t,promise:n}};var t=e.bind(Promise);const[n,s]=((e=e=>e)=>{const n=new Map,s=(()=>{const e=new Int32Array(1);return()=>e[0]++})();return[()=>{let a;do{a=e(s())}while(n.has(a));const o=t();return n.set(a,o),[a,o.promise]},(e,t,s)=>{const a=n.get(e);n.delete(e),s?a?.reject(s):a?.resolve(t)}]})(),{protocol:a,host:o,pathname:r}=location,i=`${a}//${o}${r}`,c=new BroadcastChannel("fc260aad-4404-43b8-ae9d-2c06554bb294");c.addEventListener("message",({data:[e,t]})=>{if("response"===e){const[e,n]=t;s(e,`[${n.join(",")}]`)}});const d={status:200,statusText:"OK",headers:new Headers({"Cache-Control":"no-cache, must-revalidate",Expires:"Mon, 26 Jul 1997 05:00:00 GMT","Content-type":"application/json"})},l=async e=>{const[t,s]=n();return c.postMessage(["request",[t,e]]),new Response(await s,d)};addEventListener("activate",e=>e.waitUntil(clients.claim())),addEventListener("fetch",async e=>{const{request:t}=e;"POST"===t.method&&t.url.startsWith(i)&&(e.stopImmediatePropagation(),e.respondWith(t.json().then(l)),e.preventDefault())}),addEventListener("install",()=>skipWaiting());
package/test/worker.js DELETED
@@ -1,22 +0,0 @@
1
- import reflected, { channel } from '../dist/index.js';
2
-
3
- const sync = await reflected({
4
- onsend: (data, ...rest) => {
5
- // console.log('worker', [...data], rest);
6
- return data;
7
- },
8
- onsync: (data, ...rest) => {
9
- // console.log('worker', [...data], rest);
10
- return data;
11
- }
12
- });
13
-
14
- for (let i = 0; i < 4; i++) sync('test');
15
-
16
- console.time('sync');
17
- const result = await sync('test');
18
- console.timeEnd('sync');
19
-
20
- console.log(channel, result);
21
-
22
- postMessage(`${channel} - [${result?.join?.(',')}]`);