/
index.ts
159 lines (128 loc) · 3.71 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import deprecate from 'util-deprecate';
import dedent from 'ts-dedent';
export type ChannelHandler = (event: ChannelEvent) => void;
export interface ChannelTransport {
send(event: ChannelEvent, options?: any): void;
setHandler(handler: ChannelHandler): void;
}
export interface ChannelEvent {
type: string; // eventName
from: string;
args: any[];
}
export interface Listener {
(...args: any[]): void;
}
interface EventsKeyValue {
[key: string]: Listener[];
}
interface ChannelArgs {
transport?: ChannelTransport;
async?: boolean;
}
const generateRandomId = () => {
// generates a random 13 character string
return Math.random().toString(16).slice(2);
};
export class Channel {
readonly isAsync: boolean;
private sender = generateRandomId();
private events: EventsKeyValue = {};
private data: Record<string, any> = {};
private readonly transport: ChannelTransport | undefined = undefined;
constructor({ transport, async = false }: ChannelArgs = {}) {
this.isAsync = async;
if (transport) {
this.transport = transport;
this.transport.setHandler((event) => this.handleEvent(event));
}
}
get hasTransport() {
return !!this.transport;
}
addListener(eventName: string, listener: Listener) {
this.events[eventName] = this.events[eventName] || [];
this.events[eventName].push(listener);
}
addPeerListener = deprecate(
(eventName: string, listener: Listener) => {
this.addListener(eventName, listener);
},
dedent`
channel.addPeerListener is deprecated
`
);
emit(eventName: string, ...args: any) {
const event: ChannelEvent = { type: eventName, args, from: this.sender };
let options = {};
if (args.length >= 1 && args[0] && args[0].options) {
options = args[0].options;
}
const handler = () => {
if (this.transport) {
this.transport.send(event, options);
}
this.handleEvent(event);
};
if (this.isAsync) {
// todo I'm not sure how to test this
setImmediate(handler);
} else {
handler();
}
}
last(eventName: string) {
return this.data[eventName];
}
eventNames() {
return Object.keys(this.events);
}
listenerCount(eventName: string) {
const listeners = this.listeners(eventName);
return listeners ? listeners.length : 0;
}
listeners(eventName: string): Listener[] | undefined {
const listeners = this.events[eventName];
return listeners || undefined;
}
once(eventName: string, listener: Listener) {
const onceListener: Listener = this.onceListener(eventName, listener);
this.addListener(eventName, onceListener);
}
removeAllListeners(eventName?: string) {
if (!eventName) {
this.events = {};
} else if (this.events[eventName]) {
delete this.events[eventName];
}
}
removeListener(eventName: string, listener: Listener) {
const listeners = this.listeners(eventName);
if (listeners) {
this.events[eventName] = listeners.filter((l) => l !== listener);
}
}
on(eventName: string, listener: Listener) {
this.addListener(eventName, listener);
}
off(eventName: string, listener: Listener) {
this.removeListener(eventName, listener);
}
private handleEvent(event: ChannelEvent) {
const listeners = this.listeners(event.type);
if (listeners && listeners.length) {
listeners.forEach((fn) => {
fn.apply(event, event.args);
});
}
this.data[event.type] = event.args;
}
private onceListener(eventName: string, listener: Listener) {
const onceListener: Listener = (...args: any[]) => {
this.removeListener(eventName, onceListener);
return listener(...args);
};
return onceListener;
}
}
export default Channel;