Skip to content

Commit a45411d

Browse files
committed
chore: add test
1 parent 91ee549 commit a45411d

File tree

3 files changed

+80
-61
lines changed

3 files changed

+80
-61
lines changed

README.md

+55-59
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
Port = f(types, channel)
1818
```
1919

20-
Unport is designed to simplify the complexity revolving around various JSContext environments. These environments encompass a wide range of technologies, including [Node.js](https://nodejs.org/), [ChildProcess](https://nodejs.org/api/child_process.html), [Webview](https://en.wikipedia.org/wiki/WebView), [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers), [worker_threads](https://nodejs.org/api/worker_threads.html), [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API), [iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe), [MessageChannel](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel), [ServiceWorker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), and much more.
20+
Unport is designed to simplify the complexity revolving around various JSContext environments. These environments encompass a wide range of technologies, including [Node.js](https://nodejs.org/), [ChildProcess](https://nodejs.org/api/child_process.html), [Webview](https://en.wikipedia.org/wiki/WebView), [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers), [worker_threads](https://nodejs.org/api/worker_threads.html), [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API), [iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe), [MessageChannel](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel), [ServiceWorker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), and much more.
2121

2222
Each of these JSContexts exhibits distinct methods of communicating with the external world. Still, the lack of defined types can make handling the code for complex projects an arduous task. In the context of intricate and large-scale projects, it's often challenging to track the message's trajectory and comprehend the fields that the recipient necessitates.
2323

@@ -41,7 +41,6 @@ Each of these JSContexts exhibits distinct methods of communicating with the ext
4141
- [🤝 Credits](#-credits)
4242
- [LICENSE](#license)
4343

44-
4544
## 💡 Features
4645

4746
1. Provides a unified Port paradigm. You only need to define the message types ([MessageDefinition](#messagedefinition)) and Intermediate communication channel ([Channel](#channel)) that different JSContexts need to pass, and you will get a unified type of Port:
@@ -50,7 +49,6 @@ Each of these JSContexts exhibits distinct methods of communicating with the ext
5049

5150
![IPC](https://github.com/ulivz/unport/blob/main/.media/ipc.png?raw=true)
5251

53-
5452
## 🛠️ Install
5553

5654
```bash
@@ -64,7 +62,7 @@ Let's take ChildProcess as an example to implement a process of sending messages
6462
1. Define Message Definition:
6563

6664
```ts
67-
import { Unport } from 'unport';
65+
import { Unport } from "unport";
6866

6967
export type Definition = {
7068
parent2child: {
@@ -74,7 +72,7 @@ export type Definition = {
7472
body: {
7573
name: string;
7674
path: string;
77-
}
75+
};
7876
};
7977
child2parent: {
8078
ack: {
@@ -83,61 +81,61 @@ export type Definition = {
8381
};
8482
};
8583

86-
export type ChildPort = Unport<Definition, 'child'>;
87-
export type ParentPort = Unport<Definition, 'parent'>;
84+
export type ChildPort = Unport<Definition, "child">;
85+
export type ParentPort = Unport<Definition, "parent">;
8886
```
8987

9088
2. Parent process implementation:
9189

9290
```ts
9391
// parent.ts
94-
import { join } from 'path';
95-
import { fork } from 'child_process';
96-
import { Unport, ChannelMessage } from 'unport';
97-
import { ParentPort } from './port';
92+
import { join } from "path";
93+
import { fork } from "child_process";
94+
import { Unport, ChannelMessage } from "unport";
95+
import { ParentPort } from "./port";
9896

9997
// 1. Initialize a port
10098
const parentPort: ParentPort = new Unport();
10199

102100
// 2. Implement a Channel based on underlying IPC capabilities
103-
const childProcess = fork(join(__dirname, './child.js'));
101+
const childProcess = fork(join(__dirname, "./child.js"));
104102
parentPort.implementChannel({
105103
send(message) {
106104
childProcess.send(message);
107105
},
108106
accept(pipe) {
109-
childProcess.on('message', (message: ChannelMessage) => {
107+
childProcess.on("message", (message: ChannelMessage) => {
110108
pipe(message);
111109
});
112110
},
113111
});
114112

115113
// 3. You get a complete typed Port with a unified interface 🤩
116-
parentPort.postMessage('syn', { pid: 'parent' });
117-
parentPort.onMessage('ack', payload => {
118-
console.log('[parent] [ack]', payload.pid);
119-
parentPort.postMessage('body', {
120-
name: 'index',
121-
path: ' /',
114+
parentPort.postMessage("syn", { pid: "parent" });
115+
parentPort.onMessage("ack", (payload) => {
116+
console.log("[parent] [ack]", payload.pid);
117+
parentPort.postMessage("body", {
118+
name: "index",
119+
path: " /",
122120
});
123121
});
124122

125123
// 4. If you want to remove some listeners
126124
const handleAck = (payload) => {
127-
console.log('[parent] [syn]');
125+
console.log("[parent] [syn]");
128126
};
129-
parentPort.onMessage('ack', handleAck);
130-
parentPort.offListener('ack', handleAck);
131-
// Note: if the second param of `offListener` is omitted, all listeners will be removed.
132-
parentPort.offMessage('ack');
127+
parentPort.onMessage("ack", handleAck);
128+
parentPort.removeMessageListener("ack", handleAck);
129+
// Note: if the second param of `removeMessageListener` is omitted, all listeners will be removed.
130+
parentPort.removeMessageList("ack");
133131
```
134132

135133
3. Child process implementation:
136134

137135
```ts
138136
// child.ts
139-
import { Unport, ChannelMessage } from 'unport';
140-
import { ChildPort } from './port';
137+
import { Unport, ChannelMessage } from "unport";
138+
import { ChildPort } from "./port";
141139

142140
// 1. Initialize a port
143141
const childPort: ChildPort = new Unport();
@@ -148,30 +146,30 @@ childPort.implementChannel({
148146
process.send && process.send(message);
149147
},
150148
accept(pipe) {
151-
process.on('message', (message: ChannelMessage) => {
149+
process.on("message", (message: ChannelMessage) => {
152150
pipe(message);
153151
});
154152
},
155153
});
156154

157155
// 3. You get a complete typed Port with a unified interface 🤩
158-
childPort.onMessage('syn', payload => {
159-
console.log('[child] [syn]', payload.pid);
160-
childPort.postMessage('ack', { pid: 'child' });
156+
childPort.onMessage("syn", (payload) => {
157+
console.log("[child] [syn]", payload.pid);
158+
childPort.postMessage("ack", { pid: "child" });
161159
});
162160

163-
childPort.onMessage('body', payload => {
164-
console.log('[child] [body]', JSON.stringify(payload));
161+
childPort.onMessage("body", (payload) => {
162+
console.log("[child] [body]", JSON.stringify(payload));
165163
});
166164

167-
// 4. If you want to remove some listeners by `offMessage`
165+
// 4. If you want to remove some listeners by `removeMessageList`
168166
const handleSyn = (payload) => {
169-
console.log('[child] [syn]');
167+
console.log("[child] [syn]");
170168
};
171-
childPort.onMessage('syn', handleSyn);
172-
childPort.offListener('syn', handleSyn);
173-
// Note: if the second param of `offListener` is omitted, all listeners will be removed.
174-
childPort.offMessage('syn');
169+
childPort.onMessage("syn", handleSyn);
170+
childPort.removeMessageListener("syn", handleSyn);
171+
// Note: if the second param of `removeMessageListener` is omitted, all listeners will be removed.
172+
childPort.removeMessageList("syn");
175173
```
176174

177175
## 📖 Basic Concepts
@@ -193,7 +191,7 @@ export type Definition = {
193191
body: {
194192
name: string;
195193
path: string;
196-
}
194+
};
197195
};
198196
child2parent: {
199197
ack: {
@@ -225,7 +223,7 @@ parentPort.implementChannel({
225223
childProcess.send(message);
226224
},
227225
accept(pipe) {
228-
childProcess.on('message', (message: ChannelMessage) => {
226+
childProcess.on("message", (message: ChannelMessage) => {
229227
pipe(message);
230228
});
231229
},
@@ -243,7 +241,7 @@ By abstracting the details of the underlying communication mechanism, Unport all
243241
The `Unport` class is used to create a new port.
244242

245243
```ts
246-
import { Unport } from 'unport';
244+
import { Unport } from "unport";
247245
```
248246

249247
#### .implementChannel()
@@ -256,7 +254,7 @@ parentPort.implementChannel({
256254
childProcess.send(message);
257255
},
258256
accept(pipe) {
259-
childProcess.on('message', (message: ChannelMessage) => {
257+
childProcess.on("message", (message: ChannelMessage) => {
260258
pipe(message);
261259
});
262260
},
@@ -268,19 +266,19 @@ parentPort.implementChannel({
268266
This method is used to post a message.
269267

270268
```ts
271-
parentPort.postMessage('syn', { pid: 'parent' });
269+
parentPort.postMessage("syn", { pid: "parent" });
272270
```
273271

274272
#### .onMessage()
275273

276274
This method is used to listen for a message.
277275

278276
```ts
279-
parentPort.onMessage('ack', payload => {
280-
console.log('[parent] [ack]', payload.pid);
281-
parentPort.postMessage('body', {
282-
name: 'index',
283-
path: ' /',
277+
parentPort.onMessage("ack", (payload) => {
278+
console.log("[parent] [ack]", payload.pid);
279+
parentPort.postMessage("body", {
280+
name: "index",
281+
path: " /",
284282
});
285283
});
286284
```
@@ -315,7 +313,7 @@ See our [Web Socket](./examples/web-socket/) example to check more details.
315313
The `ChannelMessage` type is used for the message in the `onMessage` method.
316314

317315
```ts
318-
import { ChannelMessage } from 'unport';
316+
import { ChannelMessage } from "unport";
319317
```
320318

321319
### Unrpc (Experimental)
@@ -342,7 +340,7 @@ export type IpcDefinition = {
342340
In the case where an RPC call needs to be encapsulated, the API might look like this:
343341

344342
```ts
345-
function rpcCall(request: { input: string; }): Promise<{ result: string; }>;
343+
function rpcCall(request: { input: string }): Promise<{ result: string }>;
346344
```
347345

348346
Consequently, to associate a callback function, it becomes a requirement to include a `CallbackId` at the **application layer** for every RPC method:
@@ -371,8 +369,8 @@ Consequently, to associate a callback function, it becomes a requirement to incl
371369
const parent = new Unrpc(parentPort);
372370

373371
// Implementing an RPC method.
374-
parent.implement('getParentInfo', request => ({
375-
id: 'parent',
372+
parent.implement("getParentInfo", (request) => ({
373+
id: "parent",
376374
from: request.user,
377375
}));
378376
```
@@ -382,13 +380,13 @@ The implementation on the `child` side is as follows:
382380
```ts
383381
// "parentPort" is a Port also defined based on Unport.
384382
const child = new Unrpc(childPort);
385-
const response = await child.call('getParentInfo', { user: "child" }); // => { id: "parent", from: "child" }
383+
const response = await child.call("getParentInfo", { user: "child" }); // => { id: "parent", from: "child" }
386384
```
387385

388386
The types are defined as such:
389387

390388
```ts
391-
import { Unport } from 'unport';
389+
import { Unport } from "unport";
392390

393391
export type Definition = {
394392
parent2child: {
@@ -403,15 +401,14 @@ export type Definition = {
403401
};
404402
};
405403

406-
export type ChildPort = Unport<Definition, 'child'>;
407-
export type ParentPort = Unport<Definition, 'parent'>;
404+
export type ChildPort = Unport<Definition, "child">;
405+
export type ParentPort = Unport<Definition, "parent">;
408406
```
409407

410408
In comparison to Unport, the only new concept to grasp is that the RPC response message key must end with `__callback`. Other than that, no additional changes are necessary! `Unrpc` also offers comprehensive type inference based on this convention; for instance, you won't be able to implement an RPC method that is meant to serve as a response.
411409

412410
> [!NOTE]
413411
> You can find the full code example here: [child-process-rpc](https://github.com/web-infra-dev/unport/tree/main/examples/child-process-rpc).
414-
>
415412
416413
## 🤝 Contributing
417414

@@ -429,7 +426,6 @@ Here are some ways you can contribute:
429426

430427
The birth of this project is inseparable from the complex IPC problems we encountered when working in large companies. The previous name of this project was `Multidirectional Typed Port`, and we would like to thank [ahaoboy](https://github.com/ahaoboy) for his previous ideas on this matter.
431428

432-
433429
## LICENSE
434430

435431
MIT License © [ULIVZ](https://github.com/ulivz)
@@ -439,4 +435,4 @@ MIT License © [ULIVZ](https://github.com/ulivz)
439435
[ci-badge]: https://github.com/ulivz/unport/actions/workflows/ci.yml/badge.svg?event=push&branch=main
440436
[ci-url]: https://github.com/ulivz/unport/actions/workflows/ci.yml?query=event%3Apush+branch%3Amain
441437
[code-coverage-badge]: https://codecov.io/github/ulivz/unport/branch/main/graph/badge.svg
442-
[code-coverage-url]: https://codecov.io/gh/ulivz/unport
438+
[code-coverage-url]: https://codecov.io/gh/ulivz/unport

__tests__/rpc.test.ts

+23
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,27 @@ describe('Unrpc', () => {
149149
expect(response1).toMatchObject({ user: 'child' });
150150
expect(response2).toMatchObject({ clientKey: 'parent' });
151151
});
152+
153+
it('removeMessageListener - remove specific callback', () => {
154+
const callback = vi.fn();
155+
parent.port.onMessage('getInfo', callback);
156+
parent.port.removeMessageListener('getInfo', callback);
157+
child.port.postMessage('getInfo', {
158+
id: 'child',
159+
});
160+
expect(callback).not.toHaveBeenCalled();
161+
});
162+
163+
it('removeMessageListener - remove all callbacks for an event', () => {
164+
const callback1 = vi.fn();
165+
const callback2 = vi.fn();
166+
parent.port.onMessage('getInfo', callback1);
167+
parent.port.onMessage('getInfo', callback2);
168+
parent.port.removeMessageListener('getInfo');
169+
child.port.postMessage('getInfo', {
170+
id: 'child',
171+
});
172+
expect(callback1).not.toHaveBeenCalled();
173+
expect(callback2).not.toHaveBeenCalled();
174+
});
152175
});

src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ interface Port<T extends MessageDefinition, D extends Direction<T>> {
116116
t: U,
117117
handler: Callback<[Payload<T, ReverseDirection<T, D>, U>]>,
118118
): void;
119-
offListener<U extends keyof T[ReverseDirection<T, D>]>(
119+
removeMessageListener<U extends keyof T[ReverseDirection<T, D>]>(
120120
t: U,
121121
handler?: Callback<[Payload<T, ReverseDirection<T, D>, U>]>,
122122
): void;
@@ -262,7 +262,7 @@ export class Unport<
262262
this.handlers[t].push(handler);
263263
};
264264

265-
public offListener: Port<T, InferDirectionByPort<T, U>>['offListener'] = (t, handler) => {
265+
public removeMessageListener: Port<T, InferDirectionByPort<T, U>>['removeMessageListener'] = (t, handler) => {
266266
if (!this.handlers[t]) {
267267
return;
268268
}

0 commit comments

Comments
 (0)