diff --git a/README.md b/README.md index bcfd13a..eb5de3c 100644 --- a/README.md +++ b/README.md @@ -63,3 +63,19 @@ delete rpc.handler; ``` In Typescript, use `delete (rpc as any).handler`. + +## Web worker + +The library can also be used with [Web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) + +```js +const myWorker = new Worker('worker.js'); +const rpc = connect(window, myWorker, "*"); +``` + +in `worker.js` + +```js +// ... +listener.listen(self, "*"); +``` diff --git a/src/index.ts b/src/index.ts index d5506fa..7d66e99 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -export {connect} from "./sender"; +export {connect, Cancelled} from "./sender"; export {default as Listener} from "./listener"; diff --git a/src/listener.ts b/src/listener.ts index ee61f9c..47d2ec2 100644 --- a/src/listener.ts +++ b/src/listener.ts @@ -1,14 +1,14 @@ import {CALL_TYPE, RESPONSE_TYPE} from "./constants"; -import {RPCRequest, WindowLike} from "./types"; +import {MessageTarget, RPCRequest, WindowLike} from "./types"; export default class Listener { - public fallbackSource: WindowLike; // Only for debugging + public fallbackSource: MessageTarget; // Only for testing constructor(private readonly methods: {[fn: string]: (...args: any[]) => any}) {} private sendResult(event: MessageEvent, result: any): void { const {channel, id} = event.data; - const source: WindowLike = (event.source as Window) || this.fallbackSource; + const source: MessageTarget = (event.source as MessageTarget) || this.fallbackSource; const targetOrigin = event.origin && event.origin !== "null" ? event.origin : "*"; Promise.resolve(result).then(r => { @@ -18,7 +18,7 @@ export default class Listener { private sendError(event: MessageEvent, error: any): void { const {channel, id} = event.data; - const source: WindowLike = (event.source as Window) || this.fallbackSource; + const source: MessageTarget = (event.source as MessageTarget) || this.fallbackSource; const targetOrigin = event.origin && event.origin !== "null" ? event.origin : "*"; source.postMessage({'@rpc': RESPONSE_TYPE, channel, id, error}, targetOrigin); diff --git a/src/sender.ts b/src/sender.ts index a3a9e3b..fe5e002 100644 --- a/src/sender.ts +++ b/src/sender.ts @@ -1,5 +1,5 @@ import {CALL_TYPE, RESPONSE_TYPE} from "./constants"; -import {RPCResponse, WindowLike} from "./types"; +import {MessageTarget, RPCResponse, WindowLike} from "./types"; interface Options { timeout?: number @@ -7,16 +7,23 @@ interface Options { let currentChannelId = 1; +export class Cancelled extends Error {} + export function connect Promise}>( parent: WindowLike, - child: WindowLike, + child: MessageTarget, targetOrigin: string, options: Options = {}, ): T { - let currentId = 1; + let currentId = 0; const promises = new Map void; reject: (reason?: any) => void, timeoutId?: number}>(); const channel = currentChannelId++; + function newId() { + if (currentId < 0) throw new Error("RPC no longer usable. Handler is removed"); + return ++currentId; + } + function call(id: number, fn: string, args: Array): void { child.postMessage({'@rpc': CALL_TYPE, channel, id, fn, args}, targetOrigin); } @@ -34,6 +41,18 @@ export function connect Promise) => { if ( (targetOrigin !== "*" && event.origin !== targetOrigin) || @@ -60,7 +79,7 @@ export function connect Promise Promise + +export type MessageTarget = Pick diff --git a/test/index.spec.ts b/test/index.spec.ts index 45f62d1..10f5c63 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -66,7 +66,6 @@ describe("simple-iframe-rpc", () => { it("gives a timeout when there's no response", async () => { const child = new JSDOM('').window; // child without listener - const parent = new JSDOM('').window; const rpc = connect(parent, child, "*", {timeout: 100}); try { @@ -77,19 +76,31 @@ describe("simple-iframe-rpc", () => { } }); - it("will return the handler to remove the event listener A", async () => { - const rpc = connect(parent, child, "*", {timeout: 100}); + it("will remove the event handler", async () => { + const rpc = connect(parent, child, "*"); assert.equal(await rpc.add(2, 3), 5); delete (rpc as any).handler; - // Should time out because there's no handler. try { await rpc.add(1, 2); assert.fail("No error was thrown"); } catch (e) { - assert.equal(e.message, 'No response for RCP call \'add\''); + assert.equal(e.message, 'RPC no longer usable. Handler is removed'); } }); + + it("will cancel when removing the event handler", async () => { + const child = new JSDOM('').window; // child without listener + const rpc = connect(parent, child, "*", {timeout: 10000}); + + const promise = rpc.add(2, 3) + .then(() => assert.fail("No error was thrown")) + .catch(reason => assert.equal(reason.message, "Event handler removed")) + + delete (rpc as any).handler; + + await promise; + }); });