From fe2036af64663ca80c649342a93355f444f46bfa Mon Sep 17 00:00:00 2001 From: Jason Staten Date: Wed, 19 Sep 2018 15:39:59 -0600 Subject: [PATCH] handling is private --- package.json | 3 ++ src/JsonRpc.test.ts | 36 +++++++++++++++------- src/JsonRpc.ts | 75 ++++++++++++++++++++++++++------------------- 3 files changed, 72 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index 1698dd9..cd80818 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "repository": "https://github.com/statianzo/pmrpc", "author": "statianzo", "license": "ISC", + "scripts": { + "test": "jest" + }, "devDependencies": { "@types/jest": "^23.3.1", "jest": "^23.5.0", diff --git a/src/JsonRpc.test.ts b/src/JsonRpc.test.ts index 4510516..b801ec0 100644 --- a/src/JsonRpc.test.ts +++ b/src/JsonRpc.test.ts @@ -16,7 +16,7 @@ it('handles requests', async () => { method: 'hello', }; - const response = await rpc.handleRequest(request); + const response = await (rpc as any).handleRequest(request); expect(response).toEqual({ jsonrpc: '2.0', id: 123, @@ -37,12 +37,12 @@ it('waits on promise results', async () => { method: 'deferred', }; - const response = await rpc.handleRequest(request); + const response = await (rpc as any).handleRequest(request); expect(response.result).toEqual('deferredResult'); }); it('errors on missing method', async () => { - const rpc = new JsonRpc({}); + const rpc = new JsonRpc(); const request = { id: 123, @@ -50,7 +50,7 @@ it('errors on missing method', async () => { method: 'missing', }; - const response = await rpc.handleRequest(request); + const response = await (rpc as any).handleRequest(request); expect(response.id).toEqual(123); expect(response.error.code).toEqual(ErrorCodes.MethodNotFound); }); @@ -70,7 +70,7 @@ it('errors on throwing method', async () => { method: 'blowUp', }; - const response = await rpc.handleRequest(request); + const response = await (rpc as any).handleRequest(request); expect(response.id).toEqual(123); expect(response.error.code).toEqual(ErrorCodes.InternalError); }); @@ -81,6 +81,13 @@ describe('mounted', () => { let rpc1: JsonRpc; let rpc2: JsonRpc; + // Shim to set MessageEvent.source in jsdom + const shimHandleSource = (rpc: any, source: object) => { + const original = rpc.handleMessage; + rpc.handleMessage = (e: MessageEvent) => + original.call(rpc, {data: e.data, source}); + }; + beforeEach(() => { frame1 = document.createElement('iframe'); frame2 = document.createElement('iframe'); @@ -89,17 +96,24 @@ describe('mounted', () => { rpc1 = new JsonRpc({ methods: {one: () => 'one'}, + destination: frame2.contentWindow, }); rpc2 = new JsonRpc({ + destination: frame1.contentWindow, methods: { greet: (name: string) => `Hello, ${name}`, - explode: () => { throw Error('Kapow'); }, - } + explode: () => { + throw Error('Kapow'); + }, + }, }); - rpc1.mount(frame1.contentWindow, frame2.contentWindow, '*'); - rpc2.mount(frame2.contentWindow, frame1.contentWindow, '*'); + shimHandleSource(rpc1, frame2.contentWindow); + shimHandleSource(rpc2, frame1.contentWindow); + + rpc1.mount(frame1.contentWindow); + rpc2.mount(frame2.contentWindow); }); afterEach(() => { @@ -114,13 +128,13 @@ describe('mounted', () => { it('propagates errors between iframes', async () => { await expect(rpc1.apply('explode')).rejects.toMatchObject({ - code: ErrorCodes.InternalError + code: ErrorCodes.InternalError, }); }); it('propagates missing between iframes', async () => { await expect(rpc1.apply('missing')).rejects.toMatchObject({ - code: ErrorCodes.MethodNotFound + code: ErrorCodes.MethodNotFound, }); }); }); diff --git a/src/JsonRpc.ts b/src/JsonRpc.ts index 7bc1c09..8b45024 100644 --- a/src/JsonRpc.ts +++ b/src/JsonRpc.ts @@ -8,12 +8,17 @@ export const enum ErrorCodes { InternalError = -32603, } -type JsonRpcDestination = Window; +type JsonRpcSource = EventTarget; +type JsonRpcDestination = MessageEventSource; type Methods = {[key: string]: Function}; -type JsonRpcResult = any; +type Deferred = {resolve: Function; reject: Function}; +type DeferredLookup = {[key: number]: Deferred}; interface JsonRpcConfig { + origin?: string; + source?: JsonRpcSource; methods?: Methods; + destination?: JsonRpcDestination; } interface JsonRpcError { @@ -32,11 +37,11 @@ interface JsonRpcRequest { interface JsonRpcResponse { jsonrpc: string; id: number; - result?: JsonRpcResult; + result?: any; error?: JsonRpcError; } -const buildResponse = (id: number) => (result: JsonRpcResult) => ({ +const buildResponse = (id: number) => (result: any) => ({ jsonrpc: VERSION, id, result, @@ -44,28 +49,31 @@ const buildResponse = (id: number) => (result: JsonRpcResult) => ({ const buildErrorResponse = (id: number) => (error: { code?: ErrorCodes; - rpcMessage?: string; + message?: string; }) => ({ jsonrpc: VERSION, id, error: { code: error.code || ErrorCodes.InternalError, - message: error.rpcMessage || null, + message: error.message, }, }); -type Deferred = {resolve: Function; reject: Function}; -type DeferredLookup = {[key : number] : Deferred}; - class JsonRpc { private methods: Methods; private destination: JsonRpcDestination; + private source: JsonRpcSource; private origin: string; private sequence = 0; - private deferreds : DeferredLookup = {}; + private deferreds: DeferredLookup = {}; - constructor({methods}: JsonRpcConfig) { - this.methods = methods || {}; + constructor({methods = {}, source, destination, origin}: JsonRpcConfig = {}) { + this.methods = methods; + this.destination = destination; + this.origin = origin || '*'; + if (source) { + this.mount(source); + } } apply(method: string, params?: any[]) { @@ -73,20 +81,17 @@ class JsonRpc { const promise = new Promise((resolve, reject) => { this.deferreds[id] = {resolve, reject}; }); - this.destination.postMessage( - { - id, - jsonrpc: VERSION, - method, - params, - }, - this.origin - ); + this.postMessage(this.destination, { + id, + jsonrpc: VERSION, + method, + params, + }); return promise; } - handleRequest(request: JsonRpcRequest): Promise { + private handleRequest(request: JsonRpcRequest): Promise { return Promise.resolve() .then(() => { const method = this.methods[request.method]; @@ -94,39 +99,47 @@ class JsonRpc { ? method.apply(null, request.params) : Promise.reject({ code: ErrorCodes.MethodNotFound, - rpcMessage: 'Method not found', + message: 'Method not found', }); }) .then(buildResponse(request.id), buildErrorResponse(request.id)); } - handleResponse(response: JsonRpcResponse) { + private handleResponse(response: JsonRpcResponse) { const deferred = this.deferreds[response.id]; delete this.deferreds[response.id]; if (!deferred) return; if (response.result) { deferred.resolve(response.result); - } - else { + } else { deferred.reject(response.error); } } - handleMessage = (e: MessageEvent) => { + private postMessage(target: JsonRpcDestination, message: any) { + target = target as Window; //Shadow to a Window + const isWindow = target.window === target; + target.postMessage(message, isWindow ? this.origin : null); + } + + private handleMessage = (e: MessageEvent) => { if (e.data.method) { this.handleRequest(e.data).then(response => - this.destination.postMessage(response, this.origin) + this.postMessage(e.source, response) ); } else { this.handleResponse(e.data); } }; - mount(source: EventTarget, destination: JsonRpcDestination, origin: string) { + mount(source: EventTarget) { + this.source = source; source.addEventListener('message', this.handleMessage); - this.destination = destination; - this.origin = origin; + } + + unmount() { + this.source.removeEventListener('message', this.handleMessage); } }