handling is private

master
Jason Staten 6 years ago
parent db5b1a6233
commit fe2036af64

@ -7,6 +7,9 @@
"repository": "https://github.com/statianzo/pmrpc", "repository": "https://github.com/statianzo/pmrpc",
"author": "statianzo", "author": "statianzo",
"license": "ISC", "license": "ISC",
"scripts": {
"test": "jest"
},
"devDependencies": { "devDependencies": {
"@types/jest": "^23.3.1", "@types/jest": "^23.3.1",
"jest": "^23.5.0", "jest": "^23.5.0",

@ -16,7 +16,7 @@ it('handles requests', async () => {
method: 'hello', method: 'hello',
}; };
const response = await rpc.handleRequest(request); const response = await (rpc as any).handleRequest(request);
expect(response).toEqual({ expect(response).toEqual({
jsonrpc: '2.0', jsonrpc: '2.0',
id: 123, id: 123,
@ -37,12 +37,12 @@ it('waits on promise results', async () => {
method: 'deferred', method: 'deferred',
}; };
const response = await rpc.handleRequest(request); const response = await (rpc as any).handleRequest(request);
expect(response.result).toEqual('deferredResult'); expect(response.result).toEqual('deferredResult');
}); });
it('errors on missing method', async () => { it('errors on missing method', async () => {
const rpc = new JsonRpc({}); const rpc = new JsonRpc();
const request = { const request = {
id: 123, id: 123,
@ -50,7 +50,7 @@ it('errors on missing method', async () => {
method: 'missing', method: 'missing',
}; };
const response = await rpc.handleRequest(request); const response = await (rpc as any).handleRequest(request);
expect(response.id).toEqual(123); expect(response.id).toEqual(123);
expect(response.error.code).toEqual(ErrorCodes.MethodNotFound); expect(response.error.code).toEqual(ErrorCodes.MethodNotFound);
}); });
@ -70,7 +70,7 @@ it('errors on throwing method', async () => {
method: 'blowUp', method: 'blowUp',
}; };
const response = await rpc.handleRequest(request); const response = await (rpc as any).handleRequest(request);
expect(response.id).toEqual(123); expect(response.id).toEqual(123);
expect(response.error.code).toEqual(ErrorCodes.InternalError); expect(response.error.code).toEqual(ErrorCodes.InternalError);
}); });
@ -81,6 +81,13 @@ describe('mounted', () => {
let rpc1: JsonRpc; let rpc1: JsonRpc;
let rpc2: 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(() => { beforeEach(() => {
frame1 = document.createElement('iframe'); frame1 = document.createElement('iframe');
frame2 = document.createElement('iframe'); frame2 = document.createElement('iframe');
@ -89,17 +96,24 @@ describe('mounted', () => {
rpc1 = new JsonRpc({ rpc1 = new JsonRpc({
methods: {one: () => 'one'}, methods: {one: () => 'one'},
destination: frame2.contentWindow,
}); });
rpc2 = new JsonRpc({ rpc2 = new JsonRpc({
destination: frame1.contentWindow,
methods: { methods: {
greet: (name: string) => `Hello, ${name}`, greet: (name: string) => `Hello, ${name}`,
explode: () => { throw Error('Kapow'); }, explode: () => {
} throw Error('Kapow');
},
},
}); });
rpc1.mount(frame1.contentWindow, frame2.contentWindow, '*'); shimHandleSource(rpc1, frame2.contentWindow);
rpc2.mount(frame2.contentWindow, frame1.contentWindow, '*'); shimHandleSource(rpc2, frame1.contentWindow);
rpc1.mount(frame1.contentWindow);
rpc2.mount(frame2.contentWindow);
}); });
afterEach(() => { afterEach(() => {
@ -114,13 +128,13 @@ describe('mounted', () => {
it('propagates errors between iframes', async () => { it('propagates errors between iframes', async () => {
await expect(rpc1.apply('explode')).rejects.toMatchObject({ await expect(rpc1.apply('explode')).rejects.toMatchObject({
code: ErrorCodes.InternalError code: ErrorCodes.InternalError,
}); });
}); });
it('propagates missing between iframes', async () => { it('propagates missing between iframes', async () => {
await expect(rpc1.apply('missing')).rejects.toMatchObject({ await expect(rpc1.apply('missing')).rejects.toMatchObject({
code: ErrorCodes.MethodNotFound code: ErrorCodes.MethodNotFound,
}); });
}); });
}); });

@ -8,12 +8,17 @@ export const enum ErrorCodes {
InternalError = -32603, InternalError = -32603,
} }
type JsonRpcDestination = Window; type JsonRpcSource = EventTarget;
type JsonRpcDestination = MessageEventSource;
type Methods = {[key: string]: Function}; type Methods = {[key: string]: Function};
type JsonRpcResult = any; type Deferred = {resolve: Function; reject: Function};
type DeferredLookup = {[key: number]: Deferred};
interface JsonRpcConfig { interface JsonRpcConfig {
origin?: string;
source?: JsonRpcSource;
methods?: Methods; methods?: Methods;
destination?: JsonRpcDestination;
} }
interface JsonRpcError { interface JsonRpcError {
@ -32,11 +37,11 @@ interface JsonRpcRequest {
interface JsonRpcResponse { interface JsonRpcResponse {
jsonrpc: string; jsonrpc: string;
id: number; id: number;
result?: JsonRpcResult; result?: any;
error?: JsonRpcError; error?: JsonRpcError;
} }
const buildResponse = (id: number) => (result: JsonRpcResult) => ({ const buildResponse = (id: number) => (result: any) => ({
jsonrpc: VERSION, jsonrpc: VERSION,
id, id,
result, result,
@ -44,28 +49,31 @@ const buildResponse = (id: number) => (result: JsonRpcResult) => ({
const buildErrorResponse = (id: number) => (error: { const buildErrorResponse = (id: number) => (error: {
code?: ErrorCodes; code?: ErrorCodes;
rpcMessage?: string; message?: string;
}) => ({ }) => ({
jsonrpc: VERSION, jsonrpc: VERSION,
id, id,
error: { error: {
code: error.code || ErrorCodes.InternalError, 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 { class JsonRpc {
private methods: Methods; private methods: Methods;
private destination: JsonRpcDestination; private destination: JsonRpcDestination;
private source: JsonRpcSource;
private origin: string; private origin: string;
private sequence = 0; private sequence = 0;
private deferreds : DeferredLookup = {}; private deferreds: DeferredLookup = {};
constructor({methods}: JsonRpcConfig) { constructor({methods = {}, source, destination, origin}: JsonRpcConfig = {}) {
this.methods = methods || {}; this.methods = methods;
this.destination = destination;
this.origin = origin || '*';
if (source) {
this.mount(source);
}
} }
apply(method: string, params?: any[]) { apply(method: string, params?: any[]) {
@ -73,20 +81,17 @@ class JsonRpc {
const promise = new Promise((resolve, reject) => { const promise = new Promise((resolve, reject) => {
this.deferreds[id] = {resolve, reject}; this.deferreds[id] = {resolve, reject};
}); });
this.destination.postMessage( this.postMessage(this.destination, {
{ id,
id, jsonrpc: VERSION,
jsonrpc: VERSION, method,
method, params,
params, });
},
this.origin
);
return promise; return promise;
} }
handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> { private handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
const method = this.methods[request.method]; const method = this.methods[request.method];
@ -94,39 +99,47 @@ class JsonRpc {
? method.apply(null, request.params) ? method.apply(null, request.params)
: Promise.reject({ : Promise.reject({
code: ErrorCodes.MethodNotFound, code: ErrorCodes.MethodNotFound,
rpcMessage: 'Method not found', message: 'Method not found',
}); });
}) })
.then(buildResponse(request.id), buildErrorResponse(request.id)); .then(buildResponse(request.id), buildErrorResponse(request.id));
} }
handleResponse(response: JsonRpcResponse) { private handleResponse(response: JsonRpcResponse) {
const deferred = this.deferreds[response.id]; const deferred = this.deferreds[response.id];
delete this.deferreds[response.id]; delete this.deferreds[response.id];
if (!deferred) return; if (!deferred) return;
if (response.result) { if (response.result) {
deferred.resolve(response.result); deferred.resolve(response.result);
} } else {
else {
deferred.reject(response.error); 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) { if (e.data.method) {
this.handleRequest(e.data).then(response => this.handleRequest(e.data).then(response =>
this.destination.postMessage(response, this.origin) this.postMessage(e.source, response)
); );
} else { } else {
this.handleResponse(e.data); this.handleResponse(e.data);
} }
}; };
mount(source: EventTarget, destination: JsonRpcDestination, origin: string) { mount(source: EventTarget) {
this.source = source;
source.addEventListener('message', this.handleMessage); source.addEventListener('message', this.handleMessage);
this.destination = destination; }
this.origin = origin;
unmount() {
this.source.removeEventListener('message', this.handleMessage);
} }
} }

Loading…
Cancel
Save