# coincident [![build](https://github.com/WebReflection/coincident/actions/workflows/node.js.yml/badge.svg)](https://github.com/WebReflection/coincident/actions/workflows/node.js.yml) **Social Media Photo by [bady abbas](https://unsplash.com/@bady) on [Unsplash](https://unsplash.com/)** An [Atomics](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics) based [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) to simplify, and synchronize, [Worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker) related tasks. #### Example ```html ``` ```js // worker.js import coincident from 'https://unpkg.com/coincident'; console.log('asking for an input'); // pauses in a non blocking way the worker until the answer has been received console.log('input', coincident(self).input('what is 1 + 2 ?')); console.log('input received'); ``` ## API The module exports a utility/helper able to *Proxy* once a generic *worker* or *globalThis* / *self* context, adding an unobtrusive listener, providing orchestration out of the box for bootstrapped *workers* that use such module. #### Worker -> Main ```js import coincident from 'coincident'; // or const coincident = require('coincident'); // on the main thread const worker = new Worker('./any-worker.js'); coincident(worker).enabler = async (...args) => { // do something sync or async with received args return {some: 'value'}; }; // on the worker side const result = coincident(self).enabler('one', {or_more: 'args'}); console.log(result); // {some: 'value'} ``` The second optional argument of the `coincident(context[, JSON])` helper can be any *JSON* like namespace able to `parse` and `stringify` data, such as [flatted](https://www.npmjs.com/package/flatted) or [@ungap/structured-clone/json](https://github.com/ungap/structured-clone/#tojson) (or use `coincident/structured`). Additionally, it can contain a `transform` callback that will be used to change arguments into something else via `(value: any) => any` operation per each of them. Moreover, it can also contain an `interrupt` callback or object that will be used to check some internal state while waiting synchronously from a worker (it's not used when main invokes a worker, it's only used when workers invokes and wait-block itself for main). **Additionally**, the exported function has a `coincident.tranfer(...buffers)` helper that if used as last argument of any worker demanded task will transfer buffers instead of cloning/copying these. on top of that, the `setInterruptHandler(callback)` field allows any foreign code to periodically check on awaited main tasks, from a *worker*. #### Main -> Worker This module can also communicate from a *main* thread to a *worker*, but in this case the *main* thread needs to `await` for results because [Atomics.wait() cannot be used in main](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait). ```html ``` ```js // worker.js import coincident from 'coincident'; // expose a specific function to the main thread coincident(self).compute = (a, b) => (a + b); ``` ### coincident/structured This entry point exports the exact same module except it uses [@ungap/structured-clone/json](https://github.com/ungap/structured-clone/#tojson) `parse` and `stringify` functions, allowing more complex, or recursive, objects to be passed along as result to, or from, the *worker*. Please keep in mind not all complex types are supported by the polyfill but also any other export could use this version with ease: ```js import * as JSON from '@ungap/structured-clone/json'; import coincident from 'coincident/uhtml'; // bootstrap in both main / workers like this const {proxy, window, isWindowProxy} = coincident(self, JSON); // that's it: structured-clone enabled for responses! ``` ### coincident/window This entry point exports the same `coincident` module (using *JSON* as default) **but** the utility returns an obejct with 3 fields: * **proxy** it's the usual proxy utility to expose or invoke functions defined in the main counter-part. * **window** it's the proxy that orchestrates access to the *main* world in *Workers*, including the ability to pass callbacks from the *Worker*, with the only caveat these will be inevitably executed asynchronously on the main thread, so that *Promise* or *thenable* work out of the box but *accessors* or defined callbacks will need to be awaited from the worker too. DOM listeners should be also handled with no issues but the `event` can't *preventDefault* or *stopPropagation* as the listener will be asynchronous too. All well known *Symbol* also cross boundaries so that `[...window.document.querySelectorAll('*')]` or any other *Symbol* based functionality should be preserved, as long as the `symbol` is known as runtime symbols can't cross paths in any meaningful way. In the *main* thread, this is just a reference to the `globalThis` object. * **isWindowProxy** is an utility that helps introspecting window proxies, callbacks, classes, or main thread references in general. This returns always `false` in the *main* thread. While the initial utility/behavior is preserved on both sides via `proxy`, the *Worker* can seamlessly use `window` utility to operate on *DOM*, *localStorage*, or literally anything else, included *Promise* based operations, DOM listeners, and so on. ```html ``` ```js // The worker.js !!! import coincident from 'coincident/window'; const {proxy, window, isWindowProxy} = coincident(self); // the proxy can expose or answer to main proxy counter part // ... but here is the fun part ... const {document} = window; document.body.innerHTML = '

Hello World!

'; document.body.appendChild( document.createElement('div') ).textContent = '😱'; const scoped = true; document.body.addEventListener('click', event => { console.log(event.type, 'running in a worker', scoped); }); ``` See the [test/window.js](./test/window.js) file, try it [live](https://webreflection.github.io/coincident/test/window.html) or reach `http://localhost:8080/test/window.html` locally to play around this feature. #### Extra features Beside providing some handy utility, the `window` (and other exports such as `uhtml`) allows *Workers*' listeners to synchronously invoke methods on the **event** on the main thread, before the proxied worker function gets a chance to get executed (remember, functions form the *worker* are inevitably async when executed from *main*). ```js document.body.addEventListener( 'click', event => console.log(event.type), {invoke: ['preventDefault', 'stopImmediatePropagation']} ); ``` When options are passed, be sure these contains [all the details you need](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#parameters) *plus* an `invoke` *string* or array of strings. ### coincident/uhtml This export includes [uhtml](https://github.com/WebReflection/uhtml#readme) to fully drive reactive UI from a Worker. The `uhtml` field is added to the returned object after `coincident(self)` call. ```html ``` ```js // The worker.js !!! import coincident from 'coincident/window'; const {proxy, window, uhtml} = coincident(self); // uhtml directly from the thread const {render, html} = uhtml; const {document} = window; render(document.body, html`

Hello uhtml!

`); ``` See [test/uhtml.js](./test/uhtml.js) page, try it [live](https://webreflection.github.io/coincident/test/uhtml.html), or test it locally via `http://localhost:8080/test/uhtml.html` after `npm run server`. ### coincident/server ⚠️ **WARNING - THIS EXPORT SHOULD NOT BE PUBLICLY AVAILABLE** This export, automatically based on `structured` export to allow passing more complex data around, allows a *Worker* to drive not just the *main* thread (UI/DOM/global page namespace) but also a *server* such as *NodeJS* or *Bun* (both successfully tested) and likely more, as long as a *WebSocketServer* like reference is passed along while creating the connection. **HTML example** - see [test/server/index.html](./test/server/index.html) ```html ``` **Worker example** - see [test/server/worker.js](./test/server/worker.js) ```js import coincident from 'coincident/server'; const { proxy, // the main thread (UI/DOM) proxy window, // the window reference isWindowProxy, // the window introspection helper server, // the server reference isServerProxy, // the server introspection helper } = coincident(self); // deal directly with the server modules out of the box const os = server.require('os'); // deal directly with the main UI/DOM thread out of the box window.document.body.innerHTML = `

coincident/server

Platform Info

`; ``` **NodeJS / Bun example** - see [test/server/main.cjs](./test/server/main.cjs) ```js const {createServer} = require('http'); const {join} = require('path'); const {WebSocketServer} = require('ws'); // static file server utility + headers for SharedArrayBuffer const staticHandler = require('static-handler'); const handler = staticHandler(join(__dirname, '..', '..'), { 'Access-Control-Allow-Origin': '*', 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp', 'Cross-Origin-Resource-Policy': 'cross-origin' }); const server = createServer(handler); // require the coincident export const coincident = require('coincident/server'); // pass along the wss to instrument with listeners // and a list of globals utilities to make available // whenever the Worker tries to reach these coincident(new WebSocketServer({server}), {require}); // bootstrap the server and that's it! server.listen(8080, () => { console.log(`http://localhost:8080/test/server/`); }); ``` The main goal of this export is **IoT** related projects, *KIOSK* like project, *Electron* like project, *Tauri* integration, and so on ... there's the possibility to fully orchestrate from a worker listeners on the page that result into *Server* operations and so on and so forth, so please be aware while this is an **achievement unlocked** from my side, it's also potentially dangerous if used in any open space where evil attackers can try to operate Server side operations on "*your*" behalf, as if it was the *Worker* itself asking for such operations. That being said, in Apps where no foreign code is allowed, this export unlocks an infinite amount of potentials for any sort of project. Alternatively, please consider strict *CSP* rules to be sure no code evaluation can arrive from a console, from foreign injected files, from users' input, and so on. Thanks for trying this feature out as no other projects (to date) can do in a cross platform, cross browser, cross environment way what this module currently offer. ### Local Only If worried about possible foreign scripts doing weird things it is possible to use these headers instead so that only local files would be able to execute any content. ```js { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp', 'Cross-Origin-Resource-Policy': 'same-origin' } ``` ### coincident/bun Same as *coincident/server* except it's 100% based on *Bun* primitives (*websocket*): ```js import { serve, file } from 'bun'; import coincidentWS from 'coincident/bun'; const port = 8080; const headers = { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp', 'Cross-Origin-Resource-Policy': 'same-origin' }; serve({ port, fetch(req, server) { // upgrade sockets if possible if (server.upgrade(req)) return; // do anything else you'd do with Bun let path = '.' + new URL(req.url).pathname; if (path.endsWith('/')) path += 'index.html'; return new Response(file(path), {headers}); }, // create automatically the websocket utility websocket: coincidentWS({import: name => import(name)}), }); console.log(`http://localhost:${port}/test/server/`); ``` ### A quick way to test SharedArrayBuffer If you would like to just test locally *SharedArrayBuffer* you can also use [mini-coi](https://github.com/WebReflection/mini-coi#readme), now enriched with a *CLI* that bootstraps locally in a snap: ```sh npx mini-coi . # or even ... bunx mini-coi . ``` If you would like to bring *SharedArrayBuffer* to your GitHub pages or any other hosting space where you can't configure headers, you can use the *Service Worker* approach currently tested to work already here and elsewhere. To copy the **script** that must be used on the page locally as Service Worker, you can also use *mini-coi*: ```sh # put mini-coi.js into ./public/mini-coi.js npx mini-coi -sw ./public/ ```