Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Communicate with web Workers
and other Windows
using a simple Promise
based API
With post-me it is easy for a parent (for example the main app) and a child (for example a worker or an iframe) to expose methods and custom events to each other.
In this live demo the main window communicates with a web worker and an iframe (source).
Import post-me as a module:
npm install post-me
import { ParentHandshake } from 'post-me';
Import post-me as a script:
<script src="https://unpkg.com/post-me/dist/index.min.js"></script>
<script>
const ParentHandshake = PostMe.ParentHandshake;
</script>
In the example below, the parent application calls methods exposed by the worker and listens to events emitted by it.
For the sake of simiplicity, only the worker is exposing methods and events, however the parent could do it as well.
Parent code:
import { ParentHandshake, WorkerMessenger } from 'post-me';
const worker = new Worker('./worker.js');
const messenger = new WorkerMessenger({ worker });
ParentHandshake(messenger).then((connection) => {
const remoteHandle = connection.remoteHandle();
// Call methods on the worker and get the result as a promise
remoteHandle.call('sum', 3, 4).then((result) => {
console.log(result); // 7
});
// Listen for a specific custom event from the worker
remoteHandle.addEventListener('ping', (payload) => {
console.log(payload) // 'Oh, hi!'
});
});
Worker code:
import { ChildHandshake, WorkerMessenger } from 'post-me';
// Methods exposed by the worker: each function can either return a value or a Promise.
const methods = {
sum: (x, y) => x + y,
mul: (x, y) => x * y
}
const messenger = WorkerMessenger({worker: self});
ChildHandshake(messenger, methods).then((connection) => {
const localHandle = connection.localHandle();
// Emit custom events to the app
localHandle.emit('ping', 'Oh, hi!');
});
Using typescript you can ensure that the parent and the child are using each other's methods and events correctly. Most coding mistakes will be caught during development by the typescript compiler.
Thanks to post-me extensive typescript support, the correctness of the following items can be statically checked during development:
Below a modified version of the previous example using typescript.
Types code:
// types.ts
export type WorkerMethods = {
sum: (x: number, y: number) => number;
mul: (x: number, y: number) => number;
}
export type WorkerEvents = {
'ping': string;
}
Parent Code:
import {
ParentHandshake, WorkerMessenger, RemoteHandle
} from 'post-me';
import { WorkerMethods, WorkerEvents } from './types';
const worker = new Worker('./worker.js');
const messenger = new WorkerMessenger({ worker });
ParentHandshake(messenger).then((connection) => {
const remoteHandle: RemoteHandle<WorkerMethods, WorkerEvents>
= connection.remoteHandle();
// Call methods on the worker and get the result as a Promise
remoteHandle.call('sum', 3, 4).then((result) => {
console.log(result); // 7
});
// Listen for a specific custom event from the app
remoteHandle.addEventListener('ping', (payload) => {
console.log(payload) // 'Oh, hi!'
});
// The following lines have various mistakes that will be caught by the compiler
remoteHandle.call('mul', 3, 'four'); // Wrong argument type
remoteHandle.call('foo'); // 'foo' doesn't exist on WorkerMethods type
});
Worker code:
import { ChildHandshake, WorkerMessenger, LocalHandle } from 'post-me';
import { WorkerMethods, WorkerEvents } from './types';
const methods: WorkerMethods = {
sum: (x: number, y: number) => x + y,
mul: (x: number, y: number) => x * y,
}
const messenger = WorkerMessenger({worker: self});
ChildHandshake(messenger, methods).then((connection) => {
const localHandle: LocalHandle<WorkerMethods, WorkerEvents>
= connection.localHandle();
// Emit custom events to the worker
localHandle.emit('ping', 'Oh, hi!');
});
post-me can establish the same level of bidirectional communications not only with workers but with other windows too (e.g. iframes) and message channels.
Internally, the low level differences between communicating with a Worker
, a Window
, or a MessageChannel
have been abstracted, and the Handshake
will accept any object that implements the Messenger
interface defined by post-me.
This approach makes it easy for post-me to be extended by its users.
A Messenger
implementation for communicating between Windows
and MessagePorts
is already provided in the library (WindowMessenger
and PortMessenger
).
Here is an example of using post-me to communicate with an iframe.
Parent code:
import { ParentHandshake, WindowMessenger } from 'post-me';
// Create the child window any way you like (iframe here, but could be popup or tab too)
const childFrame = document.createElement('iframe');
const childWindow = childFrame.contentWindow;
// For safety it is strongly adviced to pass the explicit child origin instead of '*'
const messenger = new WindowMessenger({
localWindow: window,
remoteWindow: childWindow,
remoteOrigin: '*'
});
ParentHandshake(messenger).then((connection) => {/* ... */});
Child code:
import { ChildHandshake, WindowMessenger } from 'post-me';
// For safety it is strongly adviced to pass the explicit child origin instead of '*'
const messenger = new WindowMessenger({
localWindow: window,
remoteWindow: window.parent,
remoteOrigin: '*'
});
ChildHandshake(messenger).then((connection) => {/* ... */});
Here is an example of using post-me to communicate over a MessageChannel
.
import { ParentHandshake, ChildHandshake, PortMessenger } from 'post-me';
// Create a MessageChannel
const channel = new MessageChannel();
const port1 = channel.port1;
const port2 = channel.port2;
// In the real world port1 and port2 would be transferred to other workers/windows
{
const messenger = new PortMessenger({port: port1});
ParentHandshake(messenger).then(connection => {/* ... */});
}
{
const messenger = new PortMessenger({port: port2});
ChildHandshake(messenger).then(connection => {/* ... */});
}
Even though functions cannot actually be shared across contexts, with a little magic under the hood post-me let's you pass callback functions as arguments when calling a method on the other worker/window.
Passing callbacks can be useful to obtain progress or partial results from a long running task.
Parent code:
//...
ParentHandshake(messenger).then(connection => {
const remoteHandle = connection.remoteHandle();
const onProgress = (progress) => {
console.log(progress); // 0.25, 0.5, 0.75
}
remoteHandle.call("slowSum", 2, 3, onProgress).then(result => {
console.log(result); // 5
});
});
Worker code:
const methods = {
slowSum: (x, y, onProgress) => {
onProgress(0.25);
onProgress(0.5);
onProgress(0.75);
return x + y;
}
// ...
ChildHandshake(messenger, methods).then(connection => {/* */})
By default any call parameter, return value, and event payload is cloned when passed to the other context.
While in most cases this doesn't have a significant impact on performance, sometimes you might need to transfer an object instead of cloning it. NOTE: only Transferable
objects can be transfered (ArrayBuffer
, MessagePort
, ImageBitmap
, OffscreenCanvas
).
post-me provides a way to optionally transfer objects that are part of a method call, return value, or event payload.
In the example below, the parent passes a very large array to a worker, the worker modifies the array in place, and returns it to the parent. Transfering the array instead of cloning it twice can save significant amounts of time.
Parent code:
// ...
ParentHandshake(messenger).then((connection) => {
const remoteHandle = connection.remoteHandle();
// Transfer the buffer of the array parameter of every call that will be made to 'fillArray'
remoteHandle.setCallTransfer('fillArray', (array, value) => [array.buffer]);
{
const array = new Float64Array(100000000);
remoteHandle.call('fillArray', array, 5);
}
// Transfer the buffer of the array parameter only for this one call made to 'scaleArray'
{
const array = new Float64Array(100000000);
const args = [array, 2];
const callOptions = { transfer: [array.buffer] };
remoteHandle.customCall('scaleArray', args, callOptions);
}
});
Worker code:
// ...
const methods = {
fillArray: (array, value) => {
array.forEach((_, i) => {array[i] = value});
return array;
},
scaleArray: (buffer, type value) => {
array.forEach((a, i) => {array[i] = a * value});
return array;
}
}
ChildHandshake(messenger, model).then((connection) => {
const localHandle = connection.localHandle();
// For each method, declare which parts of the return value should be transferred instead of cloned.
localHandle.setReturnTransfer('fillArray', (result) => [result.buffer]);
localHandle.setReturnTransfer('scaleArray', (result) => [result.buffer]);
});
You can optionally output the internal low-level messages exchanged between the two ends.
To enable debugging, simply decorate any Messenger
instance with the provided DebugMessenger
decorator.
You can optionally pass to the decorator your own logging function (a glorified console.log
by default), which can be useful to make the output more readable, or to inspect messages in automated tests.
import { ParentHandshake, WindowMessenger, DebugMessenger } from 'post-me';
import debug from 'debug'; // Use the full feature logger from the debug library
// import { debug } from 'post-me'; // Or the lightweight implementation provided
let messenger = new WindowMessenger({
localWindow: window,
remoteWindow: childWindow,
remoteOrigin: '*'
});
// To enable debugging of each message exchange, decorate the messenger with DebugMessenger
const log = debug('post-me:parent'); // optional
messenger = DebugMessenger(messenger, log);
ParentHandshake(messenger).then((connection) => {
// ...
});
Output:
The post-me API is loosely inspired by postmate, with several major improvements and fixes to outstanding issues:
FAQs
Use web Workers and other Windows through a simple Promise API
The npm package post-me receives a total of 1,741 weekly downloads. As such, post-me popularity was classified as popular.
We found that post-me demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.