@design.estate/dees-comms 1.0.32 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_bundle/bundle.js +9 -9
- package/dist_bundle/bundle.js.map +1 -1
- package/dist_bundle/bundle.js.third-party-notices.json +408 -0
- package/dist_bundle/bundle.js.third-party-notices.txt +968 -0
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/dees-comms.classes.deescomms.d.ts +62 -2
- package/dist_ts/dees-comms.classes.deescomms.js +210 -20
- package/package.json +6 -6
- package/readme.md +71 -70
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/dees-comms.classes.deescomms.ts +305 -19
- package/readme.hints.md +0 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@design.estate/dees-comms',
|
|
6
|
-
version: '1.
|
|
6
|
+
version: '1.1.1',
|
|
7
7
|
description: 'A communications module for enabling DOM-based messaging and synchronization across browser tabs and workers.'
|
|
8
8
|
};
|
|
9
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwyQkFBMkI7SUFDakMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLCtHQUErRztDQUM3SCxDQUFBIn0=
|
|
@@ -1,12 +1,43 @@
|
|
|
1
1
|
import * as plugins from './dees-comms.plugins.js';
|
|
2
|
+
export interface IDeesCommsOptions {
|
|
3
|
+
/**
|
|
4
|
+
* The channel this instance talks on. Instances hear each other only when
|
|
5
|
+
* they use the same name, in any tab, frame or worker of the same origin.
|
|
6
|
+
* Defaults to `DeesComms.defaultChannelName`, the channel every instance
|
|
7
|
+
* used before names existed.
|
|
8
|
+
*/
|
|
9
|
+
channelName?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Receives the payload of one typed event, and the event itself.
|
|
13
|
+
*/
|
|
14
|
+
export type TDeesCommsEventHandler<T extends plugins.typedrequestInterfaces.ITypedEvent<unknown>> = (payloadArg: T['payload'], eventArg: T) => void | Promise<void>;
|
|
15
|
+
export interface IDeesCommsSubscription {
|
|
16
|
+
/**
|
|
17
|
+
* Stops this subscription. An event that is being delivered right now does
|
|
18
|
+
* not reach it any more; calling it again does nothing.
|
|
19
|
+
*/
|
|
20
|
+
unsubscribe(): void;
|
|
21
|
+
}
|
|
2
22
|
/**
|
|
3
23
|
* a comm class for client side communication between workers and tabs.
|
|
4
24
|
*/
|
|
5
25
|
export declare class DeesComms {
|
|
6
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The channel of every instance that is not given a name. Code that talks
|
|
28
|
+
* to other code on it, like a service worker and its pages, keeps working
|
|
29
|
+
* unchanged; an application gives its own messages a name of its own.
|
|
30
|
+
*/
|
|
31
|
+
static readonly defaultChannelName = "dees-comms";
|
|
32
|
+
readonly channelName: string;
|
|
33
|
+
private readonly channel;
|
|
34
|
+
private readonly eventHandlers;
|
|
35
|
+
private hasTypedHandlers;
|
|
36
|
+
private closed;
|
|
37
|
+
private closing;
|
|
7
38
|
typedrouter: plugins.typedrequest.TypedRouter<plugins.typedrequestInterfaces.ITypedRequest>;
|
|
8
39
|
typedtarget: plugins.typedrequest.TypedTarget;
|
|
9
|
-
constructor();
|
|
40
|
+
constructor(optionsArg?: IDeesCommsOptions);
|
|
10
41
|
/**
|
|
11
42
|
* creates a typedrequest with this classes postMessage as postMethod
|
|
12
43
|
*/
|
|
@@ -19,4 +50,33 @@ export declare class DeesComms {
|
|
|
19
50
|
* subscribe to messages
|
|
20
51
|
*/
|
|
21
52
|
createTypedHandler<T extends plugins.typedrequestInterfaces.ITypedRequest>(methodArg: T['method'], handlerFunction: plugins.typedrequest.THandlerFunction<T>): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Sends a typed event to every other instance on this channel and expects
|
|
55
|
+
* no answer. The instance that sends it does not receive it; any other
|
|
56
|
+
* instance on the same channel does, in this context or another one.
|
|
57
|
+
*/
|
|
58
|
+
broadcast<T extends plugins.typedrequestInterfaces.ITypedEvent<unknown>>(nameArg: T['name'], payloadArg: T['payload']): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Calls the handler for every typed event of that name another instance
|
|
61
|
+
* broadcasts on this channel. Handlers run in the order they subscribed; one
|
|
62
|
+
* that throws or rejects is reported and does not keep the others from
|
|
63
|
+
* running.
|
|
64
|
+
*/
|
|
65
|
+
subscribe<T extends plugins.typedrequestInterfaces.ITypedEvent<unknown>>(nameArg: T['name'], handlerArg: TDeesCommsEventHandler<T>): IDeesCommsSubscription;
|
|
66
|
+
/**
|
|
67
|
+
* Closes the channel. Nothing is received from then on, every subscription
|
|
68
|
+
* ends, and sending, broadcasting or subscribing throws. A typed request
|
|
69
|
+
* still waiting for its response when the channel closes cannot receive it
|
|
70
|
+
* and ends with its own timeout. Closing again does nothing.
|
|
71
|
+
*/
|
|
72
|
+
close(): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* In Node.js an instance keeps its process alive while something listens on
|
|
75
|
+
* it, as a MessagePort with a message listener does: a subscription, or a
|
|
76
|
+
* typed handler it created. One nobody listens on lets the process end.
|
|
77
|
+
*/
|
|
78
|
+
private holdProcessWhileListened;
|
|
79
|
+
private assertOpen;
|
|
80
|
+
private receive;
|
|
81
|
+
private dispatch;
|
|
22
82
|
}
|
|
@@ -1,34 +1,103 @@
|
|
|
1
1
|
import * as plugins from './dees-comms.plugins.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
const isProcessHolding = (channelArg) => 'ref' in channelArg &&
|
|
3
|
+
typeof channelArg.ref === 'function' &&
|
|
4
|
+
'unref' in channelArg &&
|
|
5
|
+
typeof channelArg.unref === 'function';
|
|
6
|
+
/**
|
|
7
|
+
* Opens a channel that hands every message it receives to `receiveArg` as the
|
|
8
|
+
* message itself: the platform's BroadcastChannel delivers a MessageEvent, the
|
|
9
|
+
* polyfill delivers the message.
|
|
10
|
+
*/
|
|
11
|
+
const openChannel = (nameArg, receiveArg) => {
|
|
12
|
+
if (typeof globalThis.BroadcastChannel === 'function') {
|
|
13
|
+
const channel = new globalThis.BroadcastChannel(nameArg);
|
|
14
|
+
channel.onmessage = (eventArg) => {
|
|
15
|
+
receiveArg(eventArg.data);
|
|
16
|
+
};
|
|
17
|
+
const processHolding = isProcessHolding(channel) ? channel : null;
|
|
18
|
+
// an open channel alone does not keep a Node.js process running
|
|
19
|
+
processHolding?.unref();
|
|
20
|
+
return {
|
|
21
|
+
post: async (messageArg) => {
|
|
22
|
+
channel.postMessage(messageArg);
|
|
23
|
+
},
|
|
24
|
+
close: async () => {
|
|
25
|
+
channel.close();
|
|
26
|
+
},
|
|
27
|
+
holdProcess: (holdArg) => {
|
|
28
|
+
if (holdArg) {
|
|
29
|
+
processHolding?.ref();
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
processHolding?.unref();
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const channel = new plugins.BroadCastChannelPolyfill(nameArg);
|
|
38
|
+
channel.onmessage = (messageArg) => {
|
|
39
|
+
receiveArg(messageArg);
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
post: (messageArg) => channel.postMessage(messageArg),
|
|
43
|
+
close: () => channel.close(),
|
|
44
|
+
// the polyfill stands in only where the platform has no channel, which
|
|
45
|
+
// Node.js always has
|
|
46
|
+
holdProcess: () => undefined,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
const isObject = (valueArg) => typeof valueArg === 'object' && valueArg !== null;
|
|
50
|
+
// `Object.hasOwn` is younger than some of the browsers the polyfill serves
|
|
51
|
+
const hasOwn = (objectArg, keyArg) => Object.prototype.hasOwnProperty.call(objectArg, keyArg);
|
|
52
|
+
/**
|
|
53
|
+
* The event a received message carries: `null` for a message that is no
|
|
54
|
+
* event, `'malformed'` for an envelope without a well-formed event in it.
|
|
55
|
+
*/
|
|
56
|
+
const eventOf = (messageArg) => {
|
|
57
|
+
if (!isObject(messageArg) || !hasOwn(messageArg, 'deesCommsEvent')) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const event = messageArg.deesCommsEvent;
|
|
61
|
+
if (!isObject(event) ||
|
|
62
|
+
typeof event.name !== 'string' ||
|
|
63
|
+
typeof event.uniqueEventId !== 'string' ||
|
|
64
|
+
!hasOwn(event, 'payload')) {
|
|
65
|
+
return 'malformed';
|
|
66
|
+
}
|
|
67
|
+
return { name: event.name, uniqueEventId: event.uniqueEventId, payload: event.payload };
|
|
68
|
+
};
|
|
69
|
+
const createEventId = () => {
|
|
70
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
71
|
+
return Array.from(bytes, (byteArg) => byteArg.toString(16).padStart(2, '0')).join('');
|
|
72
|
+
};
|
|
6
73
|
/**
|
|
7
74
|
* a comm class for client side communication between workers and tabs.
|
|
8
75
|
*/
|
|
9
76
|
export class DeesComms {
|
|
77
|
+
/**
|
|
78
|
+
* The channel of every instance that is not given a name. Code that talks
|
|
79
|
+
* to other code on it, like a service worker and its pages, keeps working
|
|
80
|
+
* unchanged; an application gives its own messages a name of its own.
|
|
81
|
+
*/
|
|
82
|
+
static { this.defaultChannelName = 'dees-comms'; }
|
|
10
83
|
// receiving messages
|
|
11
|
-
constructor() {
|
|
12
|
-
this.
|
|
84
|
+
constructor(optionsArg = {}) {
|
|
85
|
+
this.eventHandlers = new Map();
|
|
86
|
+
this.hasTypedHandlers = false;
|
|
87
|
+
this.closed = false;
|
|
88
|
+
this.closing = null;
|
|
13
89
|
// sending messages
|
|
14
90
|
this.typedrouter = new plugins.typedrequest.TypedRouter();
|
|
15
91
|
this.typedtarget = new plugins.typedrequest.TypedTarget({
|
|
16
92
|
postMethodWithTypedRouter: async (messageArg) => {
|
|
17
|
-
this.postMessage(messageArg);
|
|
93
|
+
await this.postMessage(messageArg);
|
|
18
94
|
},
|
|
19
95
|
typedRouterRef: this.typedrouter,
|
|
20
96
|
});
|
|
21
|
-
this.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const response = await this.typedrouter.routeAndAddResponse(message, {
|
|
26
|
-
skipHooks: true,
|
|
27
|
-
});
|
|
28
|
-
if (response && !response.error) {
|
|
29
|
-
this.postMessage(response);
|
|
30
|
-
}
|
|
31
|
-
};
|
|
97
|
+
this.channelName = optionsArg.channelName ?? DeesComms.defaultChannelName;
|
|
98
|
+
this.channel = openChannel(this.channelName, (messageArg) => {
|
|
99
|
+
void this.receive(messageArg);
|
|
100
|
+
});
|
|
32
101
|
}
|
|
33
102
|
/**
|
|
34
103
|
* creates a typedrequest with this classes postMessage as postMethod
|
|
@@ -41,13 +110,134 @@ export class DeesComms {
|
|
|
41
110
|
* posts a typedrequestmessage
|
|
42
111
|
*/
|
|
43
112
|
async postMessage(messageArg) {
|
|
44
|
-
this.
|
|
113
|
+
this.assertOpen();
|
|
114
|
+
await this.channel.post(messageArg);
|
|
45
115
|
}
|
|
46
116
|
/**
|
|
47
117
|
* subscribe to messages
|
|
48
118
|
*/
|
|
49
119
|
async createTypedHandler(methodArg, handlerFunction) {
|
|
50
120
|
this.typedrouter.addTypedHandler(new plugins.typedrequest.TypedHandler(methodArg, handlerFunction));
|
|
121
|
+
this.hasTypedHandlers = true;
|
|
122
|
+
this.holdProcessWhileListened();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Sends a typed event to every other instance on this channel and expects
|
|
126
|
+
* no answer. The instance that sends it does not receive it; any other
|
|
127
|
+
* instance on the same channel does, in this context or another one.
|
|
128
|
+
*/
|
|
129
|
+
async broadcast(nameArg, payloadArg) {
|
|
130
|
+
const message = {
|
|
131
|
+
deesCommsEvent: {
|
|
132
|
+
name: nameArg,
|
|
133
|
+
uniqueEventId: createEventId(),
|
|
134
|
+
payload: payloadArg,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
await this.postMessage(message);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Calls the handler for every typed event of that name another instance
|
|
141
|
+
* broadcasts on this channel. Handlers run in the order they subscribed; one
|
|
142
|
+
* that throws or rejects is reported and does not keep the others from
|
|
143
|
+
* running.
|
|
144
|
+
*/
|
|
145
|
+
subscribe(nameArg, handlerArg) {
|
|
146
|
+
this.assertOpen();
|
|
147
|
+
// the name is the contract: what arrives under it is taken as its type,
|
|
148
|
+
// as a typed handler takes the request of its method
|
|
149
|
+
const handler = (payloadArg, eventArg) => handlerArg(payloadArg, eventArg);
|
|
150
|
+
let handlers = this.eventHandlers.get(nameArg);
|
|
151
|
+
if (!handlers) {
|
|
152
|
+
handlers = new Set();
|
|
153
|
+
this.eventHandlers.set(nameArg, handlers);
|
|
154
|
+
}
|
|
155
|
+
handlers.add(handler);
|
|
156
|
+
this.holdProcessWhileListened();
|
|
157
|
+
return {
|
|
158
|
+
unsubscribe: () => {
|
|
159
|
+
const current = this.eventHandlers.get(nameArg);
|
|
160
|
+
if (!current) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
current.delete(handler);
|
|
164
|
+
if (current.size === 0) {
|
|
165
|
+
this.eventHandlers.delete(nameArg);
|
|
166
|
+
this.holdProcessWhileListened();
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Closes the channel. Nothing is received from then on, every subscription
|
|
173
|
+
* ends, and sending, broadcasting or subscribing throws. A typed request
|
|
174
|
+
* still waiting for its response when the channel closes cannot receive it
|
|
175
|
+
* and ends with its own timeout. Closing again does nothing.
|
|
176
|
+
*/
|
|
177
|
+
async close() {
|
|
178
|
+
if (!this.closing) {
|
|
179
|
+
this.closed = true;
|
|
180
|
+
this.eventHandlers.clear();
|
|
181
|
+
this.closing = this.channel.close();
|
|
182
|
+
}
|
|
183
|
+
await this.closing;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* In Node.js an instance keeps its process alive while something listens on
|
|
187
|
+
* it, as a MessagePort with a message listener does: a subscription, or a
|
|
188
|
+
* typed handler it created. One nobody listens on lets the process end.
|
|
189
|
+
*/
|
|
190
|
+
holdProcessWhileListened() {
|
|
191
|
+
if (this.closed) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
this.channel.holdProcess(this.hasTypedHandlers || this.eventHandlers.size > 0);
|
|
195
|
+
}
|
|
196
|
+
assertOpen() {
|
|
197
|
+
if (this.closed) {
|
|
198
|
+
throw new Error(`DeesComms channel "${this.channelName}" is closed`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async receive(messageArg) {
|
|
202
|
+
if (this.closed) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const event = eventOf(messageArg);
|
|
206
|
+
if (event === 'malformed') {
|
|
207
|
+
console.log('DeesComms received a malformed event');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (event) {
|
|
211
|
+
this.dispatch(event);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// Skip hooks for broadcast-received messages to prevent infinite loops
|
|
215
|
+
// when traffic logging hooks are set up globally. The router validates
|
|
216
|
+
// the shape of what it is given.
|
|
217
|
+
const response = await this.typedrouter.routeAndAddResponse(messageArg, { skipHooks: true });
|
|
218
|
+
if (response && !response.error && !this.closed) {
|
|
219
|
+
await this.postMessage(response);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
dispatch(eventArg) {
|
|
223
|
+
const handlers = this.eventHandlers.get(eventArg.name);
|
|
224
|
+
if (!handlers) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
for (const handler of [...handlers]) {
|
|
228
|
+
// a handler that closed the channel ends the delivery, and one an
|
|
229
|
+
// earlier handler unsubscribed is not called any more
|
|
230
|
+
if (this.closed) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!handlers.has(handler)) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
void (async () => handler(eventArg.payload, eventArg))().catch((errorArg) => {
|
|
237
|
+
// the event's name and the failure, never its payload
|
|
238
|
+
console.error(`DeesComms: a handler of the event "${eventArg.name}" failed`, errorArg);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
51
241
|
}
|
|
52
242
|
}
|
|
53
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
243
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVlcy1jb21tcy5jbGFzc2VzLmRlZXNjb21tcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2RlZXMtY29tbXMuY2xhc3Nlcy5kZWVzY29tbXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSx5QkFBeUIsQ0FBQztBQWdFbkQsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLFVBQWtCLEVBQXdDLEVBQUUsQ0FDcEYsS0FBSyxJQUFJLFVBQVU7SUFDbkIsT0FBTyxVQUFVLENBQUMsR0FBRyxLQUFLLFVBQVU7SUFDcEMsT0FBTyxJQUFJLFVBQVU7SUFDckIsT0FBTyxVQUFVLENBQUMsS0FBSyxLQUFLLFVBQVUsQ0FBQztBQUV6Qzs7OztHQUlHO0FBQ0gsTUFBTSxXQUFXLEdBQUcsQ0FDbEIsT0FBZSxFQUNmLFVBQXlDLEVBQ3RCLEVBQUU7SUFDckIsSUFBSSxPQUFPLFVBQVUsQ0FBQyxnQkFBZ0IsS0FBSyxVQUFVLEVBQUUsQ0FBQztRQUN0RCxNQUFNLE9BQU8sR0FBRyxJQUFJLFVBQVUsQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUN6RCxPQUFPLENBQUMsU0FBUyxHQUFHLENBQUMsUUFBK0IsRUFBRSxFQUFFO1lBQ3RELFVBQVUsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDNUIsQ0FBQyxDQUFDO1FBQ0YsTUFBTSxjQUFjLEdBQUcsZ0JBQWdCLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO1FBQ2xFLGdFQUFnRTtRQUNoRSxjQUFjLEVBQUUsS0FBSyxFQUFFLENBQUM7UUFDeEIsT0FBTztZQUNMLElBQUksRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLEVBQUU7Z0JBQ3pCLE9BQU8sQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUM7WUFDbEMsQ0FBQztZQUNELEtBQUssRUFBRSxLQUFLLElBQUksRUFBRTtn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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@design.estate/dees-comms",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A communications module for enabling DOM-based messaging and synchronization across browser tabs and workers.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"author": "Lossless GmbH",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@git.zone/tsbuild": "^
|
|
13
|
-
"@git.zone/tsbundle": "^
|
|
14
|
-
"@git.zone/tsrun": "^
|
|
15
|
-
"@git.zone/tstest": "^3.
|
|
16
|
-
"@types/node": "^26.
|
|
12
|
+
"@git.zone/tsbuild": "^5.0.0",
|
|
13
|
+
"@git.zone/tsbundle": "^3.0.0",
|
|
14
|
+
"@git.zone/tsrun": "^3.0.1",
|
|
15
|
+
"@git.zone/tstest": "^6.3.2",
|
|
16
|
+
"@types/node": "^26.6.2"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@api.global/typedrequest": "^8.0.3",
|
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @design.estate/dees-comms
|
|
2
2
|
|
|
3
|
-
a
|
|
3
|
+
Typed messaging between the tabs, frames and workers of one origin over a BroadcastChannel: typed requests that expect an answer, and typed events that do not.
|
|
4
4
|
|
|
5
5
|
## Issue Reporting and Security
|
|
6
6
|
|
|
@@ -8,113 +8,114 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
|
-
To integrate `@design.estate/dees-comms` into your project, run the following command in your terminal:
|
|
12
|
-
|
|
13
11
|
```bash
|
|
14
12
|
pnpm add @design.estate/dees-comms
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
This will add the package to your project dependencies.
|
|
18
|
-
|
|
19
15
|
## Usage
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
### Channels
|
|
18
|
+
|
|
19
|
+
Every `DeesComms` instance talks on one BroadcastChannel. Instances hear each other only when they use the same channel name, in any tab, frame or worker of the same origin.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { DeesComms } from '@design.estate/dees-comms';
|
|
23
|
+
|
|
24
|
+
// the default channel, 'dees-comms'
|
|
25
|
+
const comms = new DeesComms();
|
|
26
|
+
|
|
27
|
+
// a channel of this application's own
|
|
28
|
+
const appComms = new DeesComms({ channelName: 'my-app' });
|
|
29
|
+
|
|
30
|
+
console.log(DeesComms.defaultChannelName); // 'dees-comms'
|
|
31
|
+
console.log(appComms.channelName); // 'my-app'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
An instance created without a name uses `DeesComms.defaultChannelName`, the channel every instance used before names existed. Other packages talk on it: `DomTools` of `@design.estate/dees-domtools` opens an instance on it, and the service worker of `@api.global/typedserver` exchanges its messages with its pages there. Give your application's messages a channel of their own, so that they neither reach nor disturb that traffic.
|
|
35
|
+
|
|
36
|
+
The platform's `BroadcastChannel` is used wherever there is one (browsers, workers, Node.js); elsewhere the [`broadcast-channel`](https://www.npmjs.com/package/broadcast-channel) package stands in for it.
|
|
22
37
|
|
|
23
|
-
###
|
|
38
|
+
### Typed requests
|
|
24
39
|
|
|
25
|
-
|
|
40
|
+
A typed request goes to every other instance on the channel; an instance with a handler for its method answers it.
|
|
26
41
|
|
|
27
42
|
```typescript
|
|
28
43
|
import { DeesComms } from '@design.estate/dees-comms';
|
|
44
|
+
import type { ITypedRequest } from '@api.global/typedrequest-interfaces';
|
|
29
45
|
|
|
30
|
-
interface
|
|
31
|
-
method: '
|
|
46
|
+
interface IGreetRequest extends ITypedRequest {
|
|
47
|
+
method: 'greet';
|
|
32
48
|
request: {
|
|
33
|
-
|
|
49
|
+
name: string;
|
|
34
50
|
};
|
|
35
51
|
response: {
|
|
36
52
|
reply: string;
|
|
37
53
|
};
|
|
38
54
|
}
|
|
39
|
-
```
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
// in one tab or worker
|
|
57
|
+
const answering = new DeesComms({ channelName: 'my-app' });
|
|
58
|
+
answering.createTypedHandler<IGreetRequest>('greet', async (request) => {
|
|
59
|
+
return { reply: `Hello ${request.name}, nice to meet you!` };
|
|
60
|
+
});
|
|
44
61
|
|
|
45
|
-
|
|
46
|
-
const
|
|
62
|
+
// in another one
|
|
63
|
+
const asking = new DeesComms({ channelName: 'my-app' });
|
|
64
|
+
const response = await asking.createTypedRequest<IGreetRequest>('greet').fire({ name: 'Alice' });
|
|
65
|
+
console.log(response.reply); // 'Hello Alice, nice to meet you!'
|
|
47
66
|
```
|
|
48
67
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
To send messages, you'll need to create a typed request. Let's assume we are sending a message that requires a response:
|
|
68
|
+
`postMessage()` posts a typed request object as it is; `typedrouter` and `typedtarget` are the `@api.global/typedrequest` router and target the instance routes with.
|
|
52
69
|
|
|
53
|
-
|
|
54
|
-
// Creating a typed request
|
|
55
|
-
const myTypedRequest = myDeesComms.createTypedRequest<IMyMessage>('myMethod');
|
|
70
|
+
### Typed events
|
|
56
71
|
|
|
57
|
-
|
|
58
|
-
const response = await myTypedRequest.fire({
|
|
59
|
-
someData: 'Hello, World!',
|
|
60
|
-
});
|
|
61
|
-
console.log('Received response:', response.reply);
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Receiving and Handling Messages
|
|
65
|
-
|
|
66
|
-
To handle incoming messages, you need to define handlers for the types of messages you expect to receive.
|
|
72
|
+
A typed event is sent to every other instance on the channel and expects no answer. It is declared as an `ITypedEvent` of `@api.global/typedrequest-interfaces`: its `name` is the contract, its `payload` is what travels.
|
|
67
73
|
|
|
68
74
|
```typescript
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
reply: 'Message received loud and clear!'
|
|
75
|
+
import { DeesComms } from '@design.estate/dees-comms';
|
|
76
|
+
import type { ITypedEvent } from '@api.global/typedrequest-interfaces';
|
|
77
|
+
|
|
78
|
+
interface ISettingsChangedEvent extends ITypedEvent<{ section: string }> {
|
|
79
|
+
name: 'settingsChanged';
|
|
80
|
+
uniqueEventId: string;
|
|
81
|
+
payload: {
|
|
82
|
+
section: string;
|
|
78
83
|
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// in every tab that has to follow
|
|
87
|
+
const listening = new DeesComms({ channelName: 'my-app' });
|
|
88
|
+
const subscription = listening.subscribe<ISettingsChangedEvent>('settingsChanged', (payload, event) => {
|
|
89
|
+
console.log(`settings changed in ${payload.section}`, event.uniqueEventId);
|
|
79
90
|
});
|
|
80
|
-
```
|
|
81
91
|
|
|
82
|
-
|
|
92
|
+
// in the tab where it happened
|
|
93
|
+
const telling = new DeesComms({ channelName: 'my-app' });
|
|
94
|
+
await telling.broadcast<ISettingsChangedEvent>('settingsChanged', { section: 'profile' });
|
|
83
95
|
|
|
84
|
-
|
|
96
|
+
// later
|
|
97
|
+
subscription.unsubscribe();
|
|
98
|
+
```
|
|
85
99
|
|
|
86
|
-
|
|
100
|
+
- The instance that broadcasts an event does not receive it; every other instance on the channel does, in the same context or another one.
|
|
101
|
+
- `broadcast()` gives every event a new `uniqueEventId`.
|
|
102
|
+
- Handlers of one name run in the order they subscribed. A handler that throws or rejects is reported with `console.error`, naming the event and the error but never the payload, and does not keep the others from running. A handler that an earlier one unsubscribes during the same event is not called for it.
|
|
103
|
+
- Events and typed requests stay apart: an event never reaches a typed handler, and a typed request never reaches a subscription, whatever their names.
|
|
104
|
+
- An instance of 1.0.32 routes every message it receives as a typed request; on the platform's own BroadcastChannel, `@api.global/typedrequest` 8 then logs `TypedRouter received a malformed request DTO` for every event. That is one more reason to broadcast on a channel of your application's own.
|
|
87
105
|
|
|
88
|
-
|
|
106
|
+
### Closing
|
|
89
107
|
|
|
90
108
|
```typescript
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
interface IGreetRequest {
|
|
94
|
-
method: 'greet';
|
|
95
|
-
request: {
|
|
96
|
-
name: string;
|
|
97
|
-
};
|
|
98
|
-
response: {
|
|
99
|
-
reply: string;
|
|
100
|
-
};
|
|
101
|
-
}
|
|
109
|
+
await comms.close();
|
|
110
|
+
```
|
|
102
111
|
|
|
103
|
-
|
|
112
|
+
A closed instance receives nothing: its subscriptions end and its typed handlers are no longer asked. `postMessage()`, `broadcast()` and the typed requests it fires reject, and `subscribe()` throws. A typed request that is still waiting for its response when the channel closes cannot receive it and ends with its own timeout. Closing again does nothing.
|
|
104
113
|
|
|
105
|
-
|
|
106
|
-
appDeesComms.createTypedHandler<IGreetRequest>('greet', async (request) => {
|
|
107
|
-
console.log(`Greetings received from ${request.name}`);
|
|
108
|
-
return { reply: `Hello ${request.name}, nice to meet you!` };
|
|
109
|
-
});
|
|
114
|
+
In Node.js an instance keeps the process alive only while something listens on it, as a `MessagePort` with a message listener does: while it has a subscription, or once it has created a typed handler. An instance nobody listens on, such as the one `@design.estate/dees-domtools` opens on import, lets the process end. An unsubscribed or closed instance lets it end too, so close every instance with a typed handler that a test or a script opens. In browsers and workers nothing changes.
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
const greetRequest = appDeesComms.createTypedRequest<IGreetRequest>('greet');
|
|
113
|
-
const response = await greetRequest.fire({ name: 'Alice' });
|
|
114
|
-
console.log(response.reply); // Expected output: "Hello Alice, nice to meet you!"
|
|
115
|
-
```
|
|
116
|
+
### What travels
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
Requests, responses and events are copied with the structured clone algorithm, so they carry data, not functions or class instances. A BroadcastChannel is confined to its origin, but every script of that origin can listen on a channel whose name it knows: send no secrets, and nothing that identifies a person, on it.
|
|
118
119
|
|
|
119
120
|
## License and Legal Information
|
|
120
121
|
|
package/ts/00_commitinfo_data.ts
CHANGED