Gathering detailed insights and metrics for rpc-shooter
Gathering detailed insights and metrics for rpc-shooter
Gathering detailed insights and metrics for rpc-shooter
Gathering detailed insights and metrics for rpc-shooter
A tool library for handling window && iframe && worker communication based on the JSON RPC specification
npm install rpc-shooter
Typescript
Module System
Node Version
NPM Version
72.5
Supply Chain
98.9
Quality
76
Maintenance
100
Vulnerability
100
License
TypeScript (94.93%)
HTML (4.01%)
CSS (1.06%)
Total Downloads
10,523
Last Day
5
Last Week
20
Last Month
128
Last Year
1,961
Apache-2.0 License
106 Stars
50 Commits
10 Forks
2 Watchers
4 Branches
1 Contributors
Updated on Mar 20, 2025
Minified
Minified + Gzipped
Latest Version
0.0.14
Package Id
rpc-shooter@0.0.14
Unpacked Size
64.35 kB
Size
15.70 kB
File Count
8
NPM Version
8.1.2
Node Version
16.13.2
Cumulative downloads
Total Downloads
Last Day
66.7%
5
Compared to previous day
Last Week
-4.8%
20
Compared to previous week
Last Month
-31.6%
128
Compared to previous month
Last Year
-62.2%
1,961
Compared to previous year
A tool library for handling window && iframe && worker communication based on the JSON RPC specification
一个基于 JSON-RPC 规范用于处理 window && iframe && worker 通讯的工具库
使用 iframe
与 Web Worker
经常需要写如下代码:
iframe 中的服务调用
1// parent.js 2const childWindow = document.querySelector('iframe').contentWindow; 3window.addEventListener('message', function (event) { 4 const data = event.data; 5 if (data.event === 'do_something') { 6 // ... handle iframe data 7 childWindow.postMessage({ 8 event: 're:do_something', 9 data: 'some data', 10 }); 11 } 12}); 13 14// iframe.js 15window.top.postMessage( 16 { 17 event: 'do_something', 18 data: 'ifame data', 19 }, 20 '*' 21); 22window.addEventListener('message', function (event) { 23 const data = event.data; 24 if (data.event === 're:do_something') { 25 // ... handle parent data 26 } 27});
worker 服务调用
1// parent.js 2const worker = new Worker('worker.js'); 3worker.addEventListener('message', function (event) { 4 const data = event.data; 5 if (data.event === 'do_something') { 6 // ... handle worker data 7 worker.postMessage({ 8 event: 're:do_something', 9 data: 'some data', 10 }); 11 } 12}); 13 14// worker.js 15self.postMessage({ 16 event: 'do_something', 17 data: 'worker data', 18}); 19self.addEventListener('message', function (event) { 20 const data = event.data; 21 if (data.event === 're:do_something') { 22 // ... handle parent data 23 } 24});
上述的方式可以处理简单的事件通信,但针对复杂场景下跨页面(进程)通信需要一个简单的有效的处理方式,如果可以封装成异步函数调用方式,则会优雅很多,如下:
1// parent.js 2const parentRPC = new RPC({...}); 3parentRPC.registerMethod('parent.do_something', (data) => { 4 return Promise.resolve({...}); 5}); 6parentRPC.invoke('child.do_something', { data: 'xxx' }) 7 .then(res => { 8 console.error(res); 9 }) 10 .catch(error => { 11 console.error(error); 12 }); 13 14// child.js 15const childRPC = new RPC({...}); 16childRPC.registerMethod('child.do_something', (data) => { 17 return Promise.resolve({...}); 18}); 19childRPC.invoke('parent.do_something', { data: 'xxx' }) 20 .then(res => { 21 console.error(res); 22 }) 23 .catch(error => { 24 console.error(error); 25 });
使用 JSON-RPC 2.0 规范可以很清晰简单的描述两个服务间的调用,rpc-shooter
中使用 JSON-RPC
作为数据交互格式。
1yarn add rpc-shooter 2# or 3npm i rpc-shooter -S
使用 RPCMessageEvent
模块可以涵盖下列模块的消息通信:
如果有更复杂的事件交互场景,实现自己的 event
模块即可。
1// main.ts
2import { RPCMessageEvent, RPC } from 'rpc-shooter';
3
4(async function () {
5 const iframe = document.querySelector('iframe')!;
6 const rpc = new RPC({
7 event: new RPCMessageEvent({
8 currentEndpoint: window,
9 targetEndpoint: iframe.contentWindow!,
10 config: { targetOrigin: '*' },
11 }),
12 // 初始化时注册处理函数
13 methods: {
14 'Main.max': (a: number, b: number) => Math.max(a, b),
15 },
16 });
17 // 动态注册处理函数
18 rpc.registerMethod('Main.min', (a: number, b: number) => {
19 return Promise.resolve(Math.min(a, b));
20 });
21
22 // 检查链接,配置超时时间
23 await rpc.connect(2000);
24
25 // 调用 iframe 服务中的注册方法
26 const randomValue = await rpc.invoke('Child.random', null, { isNotify: false, timeout: 2000 });
27 console.log(`Main invoke Child.random result: ${randomValue}`);
28})();
1// child.ts 2import { RPCMessageEvent, RPC } from 'rpc-shooter'; 3(async function () { 4 const rpc = new RPC({ 5 event: new RPCMessageEvent({ 6 currentEndpoint: window, 7 targetEndpoint: window.top, 8 }), 9 }); 10 11 rpc.registerMethod('Child.random', () => Math.random()); 12 13 await rpc.connect(2000); 14 15 const max = await rpc.invoke('Main.max', [1, 2]); 16 const min = await rpc.invoke('Main.min', [1, 2]); 17 console.log({ max, min }); 18})();
1// main.ts 2import { RPCMessageEvent, RPC } from 'rpc-shooter'; 3(async function () { 4 const openNewWindow = (path: string) => { 5 return window.open(path, '_blank'); 6 }; 7 const newWindow = openNewWindow('new-window.html'); 8 const rpc = new RPC({ 9 event: new RPCMessageEvent({ 10 currentEndpoint: window, 11 targetEndpoint: newWindow, 12 config: { targetOrigin: '*' }, 13 }), 14 }); 15 rpc.registerMethod('Main.max', (a: number, b: number) => { 16 return Promise.resolve(Math.max(a, b)); 17 }); 18 await rpc.connect(2000); 19 await rpc.invoke('Child.random', null); 20})();
1// child.ts 2import { RPCMessageEvent, RPC } from 'rpc-shooter'; 3(async function () { 4 const rpc = new RPC({ 5 event: new RPCMessageEvent({ 6 currentEndpoint: window, 7 targetEndpoint: window.opener, 8 }), 9 }); 10 rpc.registerMethod('Child.random', () => Math.random()); 11 12 await rpc.connect(2000); 13 14 await rpc.invoke('Main.max', [1, 2]); 15})();
1// main.ts
2import { RPCMessageEvent, RPC } from 'rpc-shooter';
3
4(async function () {
5 const worker = new Worker('./slef.worker.ts');
6 const rpc = new RPC({
7 event: new RPCMessageEvent({
8 currentEndpoint: worker,
9 targetEndpoint: worker,
10 }),
11 });
12 rpc.registerMethod('Main.max', (a: number, b: number) => {
13 return Promise.resolve(Math.max(a, b));
14 });
15 await rpc.connect(2000);
16 await rpc.invoke('Child.random', null);
17})();
1// child.ts 2import { RPCMessageEvent, RPC } from 'rpc-shooter'; 3 4(async function () { 5 const ctx: Worker = self as any; 6 const rpc = new RPC({ 7 event: new RPCMessageEvent({ 8 currentEndpoint: ctx, 9 targetEndpoint: ctx, 10 }), 11 }); 12 rpc.registerMethod('Child.random', () => Math.random()); 13 14 await rpc.connect(2000); 15 16 await rpc.invoke('Main.max', [1, 2]); 17})();
1// main.ts
2import { RPCMessageEvent, RPC } from 'rpc-shooter';
3
4(async function () {
5 const worker: SharedWorker = new SharedWorker('./shared.worker.ts');
6 worker.port.start();
7 const rpc = new RPC({
8 event: new RPCMessageEvent({
9 currentEndpoint: worker.port,
10 targetEndpoint: worker.port,
11 }),
12 });
13 rpc.registerMethod('Main.max', (a: number, b: number) => {
14 return Promise.resolve(Math.max(a, b));
15 });
16 await rpc.connect(2000);
17 await rpc.invoke('Child.random', null);
18})();
1// child.ts 2import { RPCMessageEvent, RPC } from 'rpc-shooter'; 3 4interface SharedWorkerGlobalScope { 5 onconnect: (event: MessageEvent) => void; 6} 7 8const ctx: SharedWorkerGlobalScope = self as any; 9 10ctx.onconnect = async (event: MessageEvent) => { 11 const port = event.ports[0]; 12 port.start(); 13 const rpc = new RPC({ 14 event: new RPCMessageEvent({ 15 currentEndpoint: port, 16 targetEndpoint: port, 17 }), 18 }); 19 rpc.registerMethod('Child.random', () => Math.random()); 20 21 await rpc.connect(2000); 22 23 await rpc.invoke('Main.max', [1, 2]); 24};
rpc-shooter
由核心两个模块组成:
RPCMessageEvent
事件交互进行封装,提供方法注册与调用1const RPCInitOptions = {
2 timeout: 200,
3 event: new RPCMessageEvent({
4 currentEndpoint: window,
5 targetEndpoint: iframe.contentWindow!,
6 config: { targetOrigin: '*' },
7 }),
8 // 初始化时注册处理函数
9 methods: {
10 'Main.max': (a: number, b: number) => Math.max(a, b),
11 'Main.abs': (a: number) => Math.abs(a),
12 },
13};
14const rpc = new RPC(RPCInitOptions);
15// 动态注册处理函数
16rpc.registerMethod('Main.min', (a: number, b: number) => {
17 return Promise.resolve(Math.min(a, b));
18});
19// 移除注册函数
20rpc.removeMethod('Main.abs');
21// 检查链接,配置超时时间
22await rpc.connect(2000);
23// 调用 iframe 服务中的注册方法
24const value = await rpc.invoke('Child.random', null, { isNotify: false, timeout: 2000 });
1interface RPCEvent { 2 emit(event: string, ...args: any[]): void; 3 on(event: string, fn: RPCHandler): void; 4 off(event: string, fn?: RPCHandler): void; 5 onerror: null | ((error: RPCError) => void); 6 destroy?: () => void; 7} 8 9interface RPCHandler { 10 (...args: any[]): any; 11} 12 13interface RPCInitOptions { 14 event: RPCEvent; 15 methods?: Record<string, RPCHandler>; 16 timeout?: number; 17}
参数 | 类型 | 说明 |
---|---|---|
event | 必填 RPCEvent | 用于服务间通信的事件模块,可参考 RPCMessageEvent 实现,满足 RPCEvent 接口即可 |
methods | 可选 Record<string, RPCHandler> | 用于注册当前服务可调用的方法 |
timeout | 可选 number | 方法调用的全局超时时间,为 0 则不设置超时时间 |
connect
1connect(timeout?: number): Promise<void>;
timeout 超时设置,会覆盖全局设置
registerMethod
1registerMethod(method: string, handler: RPCHandler);
注册调用方法
removeMethod
1removeMethod(method: string);
移除调用方法
invoke
1invoke( 2 method: string, 3 ...args: any[] | [...any[], RPCInvokeOptions] 4): Promise<any>;
1rpc1.registerMethod('add', (a: number, b: number, c: number) => { 2 return new Promise((resolve) => { 3 setTimeout(() => resolve(a + b + c), 400); 4 }); 5}); 6// 可以通过 invokeOptions 配置调用超时 7const res1 = await rpc2.invoke('add', 1, 2, 3); 8const res2 = await rpc2.invoke('add', 4, 5, 6, { isNotify: true }); 9const res3 = await rpc2.invoke('add', 7, 8, 9, { timeout: 1000 }); 10const res4 = await rpc2.invoke('add', 10, 11, 12, { timeout: 4 }).catch((error) => error);
调用远程服务
string
方法名any
参数number
timeout 超时设置,会覆盖全局设置boolean
是否是个一个通知消息可以函数调用参数末尾配置 invokeOptions 选项,如果 invoke 配置了 isNotify,则作为一个通知消息,方法调用后会立即返回,不理会目标服务是否相应,目标也不会响应回复此消息。内部使用 JSON-PRC 的 id 进行标识。
没有包含“id”成员的请求对象为通知, 作为通知的请求对象表明客户端对相应的响应对象并不感兴趣,本身也没有响应对象需要返回给客户端。服务端必须不回复一个通知,包含那些批量请求中的。 由于通知没有返回的响应对象,所以通知不确定是否被定义。同样,客户端不会意识到任何错误(例如参数缺省,内部错误)。
RPCMessageEvent 实现一套与 socket.io 类似的事件接口,用于处理 window iframe worker 场景下消息通信:
1interface RPCHandler { 2 (...args: any[]): any; 3} 4 5interface RPCEvent { 6 emit(event: string, ...args: any[]): void; 7 on(event: string, fn: RPCHandler): void; 8 off(event: string, fn?: RPCHandler): void; 9 onerror: null | ((error: RPCError) => void); 10 destroy?: () => void; 11}
使用:
1// main.ts 2import { RPCMessageEvent } from 'rpc-shooter'; 3const mainEvent = new RPCMessageEvent({ 4 currentEndpoint: window, 5 targetEndpoint: iframe.contentWindow, 6 config: { 7 targetOrigin: '*', 8 }, 9 receiveAdapter(event) { 10 return event.data; 11 }, 12 receiveAdapter(data) { 13 return data; 14 }, 15}); 16 17mainEvent.on('Main.test', (data) => {}); 18mainEvent.emit('Child.test', (data) => {}); 19// mainEvent.off('Main.someMethod'); 20// mainEvent.destroy();
1// child.ts 2import { RPCMessageEvent } from 'rpc-shooter'; 3const childEvent = new RPCMessageEvent({ 4 currentEndpoint: window, 5 targetEndpoint: window.top, 6 config: { 7 targetOrigin: '*', 8 }, 9 receiveAdapter(event) { 10 return event.data; 11 }, 12 receiveAdapter(data) { 13 return data; 14 }, 15}); 16 17childEvent.emit('Main.test', (data) => {});
RPCMessageEvent 初始化选项定义如下:
1interface RPCMessageDataFormat { 2 event: string; 3 args: any[]; 4} 5 6interface RPCPostMessageConfig { 7 targetOrigin?: string; 8 transfer?: Transferable[]; 9} 10 11interface RPCMessageEventOptions { 12 currentEndpoint: RPCMessageReceiveEndpoint; 13 targetEndpoint: RPCMessageSendEndpoint; 14 config?: 15 | ((data: any, context: RPCMessageSendEndpoint) => RPCPostMessageConfig) 16 | RPCPostMessageConfig; 17 sendAdapter?: ( 18 data: RPCMessageDataFormat | any, 19 context: RPCMessageSendEndpoint 20 ) => { 21 data: RPCMessageDataFormat; 22 transfer?: Transferable[]; 23 }; 24 receiveAdapter?: (event: MessageEvent) => RPCMessageDataFormat; 25}
参数 | 类型 | 说明 |
---|---|---|
currentEndpoint | 必填 Window 、Worker 、MessagePort 等满足 RPCMessageReceiveEndpoint 接口对象 | 当前通信对象的上下文,可以是 Window 、Worker 或者 MessagePort 对象 |
targetEndpoint | 必填 Window 、Worker 、MessagePort 等满足 RPCMessageSendEndpoint 接口对象 | 目标通信对象的上下文,可以是 Window 、Worker 或者 MessagePort 对象 |
config | 可选 RPCPostMessageConfig or Function | 用于给 targetEndpoint.postMessage 方法配置参数 |
sendAdapter | 可选 Function | 消息发动前数据处理函数 |
receiveAdapter | 可选 Function | 消息接受前数据处理函数 |
config
1interface WindowPostMessageOptions { 2 transfer?: Transferable[]; 3 targetOrigin?: string; 4} 5 6interface RPCPostMessageConfig extends WindowPostMessageOptions { 7} 8 9type config?: 10 | ((data: any, context: RPCMessageSendEndpoint) => RPCPostMessageConfig) 11 | RPCPostMessageConfig;
用于给 targetEndpoint 的 postMessage
方法配置参数,可以直接配置一个对象,也可以通过函数动态返回一个配置。
window.postMessage
配置 targetOrigin
-> { targetOrigin: '*' }
worker.postMessage
配置 transfer
-> { transfer: [...] }
figma.ui.postMessage
配置 origin
-> { origin: '*' }
1new RPCMessageEvent({
2 currentEndpoint: worker,
3 targetEndpoint: worker,
4 config: {
5 targetOrigin: '*',
6 },
7});
8// window.postMessage(data, targetOrigin, [transfer]);
sendAdapter
1type sendAdapter = ( 2 data: RPCMessageDataFormat | any, 3 context: RPCMessageSendEndpoint 4) => { 5 data: RPCMessageDataFormat; 6 transfer?: Transferable[]; 7};
发送数据前的适配函数,在一些特殊环境下可以对发送的数据做一些处理:
1new RPCMessageEvent({
2 currentEndpoint: worker,
3 targetEndpoint: worker,
4 sendAdapter(data) {
5 const transfer = [];
6 // 将 ImageBitmap 添加至 transfer 优化数据传输
7 JSON.stringify(data, (_, value) => {
8 if (value?.constructor.name === 'ImageBitmap') {
9 transfer.push(value);
10 }
11 return value;
12 });
13 return { data, transfer };
14 },
15});
receiveAdapter
1type receiveAdapter = (event: MessageEvent) => RPCMessageDataFormat;
数据接收前的处理函数,一般情况不需要配置,在一些特殊场景下:
origin
来源过滤掉不安全消息请求1new RPCMessageEvent({ 2 currentEndpoint: window, 3 targetEndpoint: window.parent, 4 receiveAdapter(event) { 5 if (event.origin === 'xxx') { 6 return event.data; 7 } 8 return null; 9 }, 10});
如 figma 插件中 iframe 与主应用通信需要使用 pluginMessage
字段包裹。
1// figma plugin ifame
2new RPCMessageEvent({
3 currentEndpoint: window,
4 targetEndpoint: window.parent,
5 receiveAdapter(event) {
6 return event.data.pluginMessage;
7 },
8 sendAdapter(data) {
9 return { pluginMessage: data };
10 },
11});
1interface RPCHandler { 2 (...args: any[]): any; 3} 4 5interface RPCEvent { 6 emit(event: string, ...args: any[]): void; 7 on(event: string, fn: RPCHandler): void; 8 off(event: string, fn?: RPCHandler): void; 9 onerror: null | ((error: RPCError) => void); 10 destroy?: () => void; 11}
参考 socket.io API 不做赘述
方法 | 说明 |
---|---|
on | 设置本地服务事件监听 |
emit | 触发远程服务事件 |
off | 移除本地服务事件监听 |
onerror | 发生错误时触发 onerror 回调 |
destroy | 释放 RPCMessageEvent 资源与内部事件监听 |
onerror
1type onerror = null | ((error: RPCError) => void);
事件模块内部发生错误时的回调函数。
1const event = new RPCMessageEvent({...}); 2event.onerror = (error) => { 3 console.log(error); 4};
1# 依赖 2yarn 3# 开发 4yarn dev 5# 构建 6yarn build
No vulnerabilities found.
No security vulnerabilities found.