@design.estate/dees-comms 2.0.0 → 2.1.0

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.
@@ -3,7 +3,7 @@
3
3
  "packages": [
4
4
  {
5
5
  "name": "@api.global/typedrequest",
6
- "version": "9.0.2",
6
+ "version": "9.1.2",
7
7
  "license": "MIT",
8
8
  "files": [
9
9
  {
@@ -6,7 +6,7 @@ the license and notice files the package ships, or the license text the project
6
6
  or a bundled package supplied for it.
7
7
 
8
8
  ================================================================================
9
- @api.global/typedrequest@9.0.2
9
+ @api.global/typedrequest@9.1.2
10
10
  License: MIT
11
11
 
12
12
  --- license.md ---
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-comms',
6
- version: '2.0.0',
6
+ version: '2.1.0',
7
7
  description: 'A communications module for enabling DOM-based messaging and synchronization across browser tabs and workers.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwyQkFBMkI7SUFDakMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLCtHQUErRztDQUM3SCxDQUFBIn0=
@@ -7,6 +7,34 @@ export interface IDeesCommsOptions {
7
7
  * used before names existed.
8
8
  */
9
9
  channelName?: string;
10
+ /**
11
+ * How long a typed request this instance fires waits for its answer, in
12
+ * milliseconds. Defaults to 30 seconds. A request fired with its own
13
+ * `timeoutMs` ends at the earlier of the two.
14
+ */
15
+ responseTimeoutMs?: number;
16
+ }
17
+ /**
18
+ * What a typed request fired on a DeesComms instance throws when a handler
19
+ * refuses it, and what a handler throws to refuse one. These are the classes
20
+ * of the `@api.global/typedrequest` the instance routes with, so `instanceof`
21
+ * holds whichever version the application itself depends on.
22
+ */
23
+ export type TypedResponseError = plugins.typedrequest.TypedResponseError;
24
+ export declare const TypedResponseError: typeof plugins.typedrequest.TypedResponseError;
25
+ /**
26
+ * What a typed request fired with `timeoutMs` throws when no answer came in
27
+ * time.
28
+ */
29
+ export type TypedRequestTimeoutError = plugins.typedrequest.TypedRequestTimeoutError;
30
+ export declare const TypedRequestTimeoutError: typeof plugins.typedrequest.TypedRequestTimeoutError;
31
+ export interface IDeesCommsTypedHandler {
32
+ /**
33
+ * Stops answering. A request that arrives from then on is left to the
34
+ * other instances, as if this one never had the handler; one it is
35
+ * answering right now still gets its answer. Calling it again does nothing.
36
+ */
37
+ remove(): void;
10
38
  }
11
39
  /**
12
40
  * Receives the payload of one typed event, and the event itself.
@@ -32,7 +60,7 @@ export declare class DeesComms {
32
60
  readonly channelName: string;
33
61
  private readonly channel;
34
62
  private readonly eventHandlers;
35
- private hasTypedHandlers;
63
+ private typedHandlerCount;
36
64
  private closed;
37
65
  private closing;
38
66
  typedrouter: plugins.typedrequest.TypedRouter<plugins.typedrequestInterfaces.ITypedRequest>;
@@ -47,9 +75,13 @@ export declare class DeesComms {
47
75
  */
48
76
  postMessage<T = plugins.typedrequestInterfaces.ITypedRequest>(messageArg: T): Promise<void>;
49
77
  /**
50
- * subscribe to messages
78
+ * Answers the typed requests of a method that other instances on this
79
+ * channel fire. The handler answers with what it returns; one that throws a
80
+ * `TypedResponseError` refuses the request, and its caller gets that error
81
+ * at once instead of waiting for its timeout. An instance has at most one
82
+ * handler per method.
51
83
  */
52
- createTypedHandler<T extends plugins.typedrequestInterfaces.ITypedRequest>(methodArg: T['method'], handlerFunction: plugins.typedrequest.THandlerFunction<T>): Promise<void>;
84
+ createTypedHandler<T extends plugins.typedrequestInterfaces.ITypedRequest>(methodArg: T['method'], handlerFunction: plugins.typedrequest.THandlerFunction<T>): Promise<IDeesCommsTypedHandler>;
53
85
  /**
54
86
  * Sends a typed event to every other instance on this channel and expects
55
87
  * no answer. The instance that sends it does not receive it; any other
@@ -1,4 +1,6 @@
1
1
  import * as plugins from './dees-comms.plugins.js';
2
+ export const TypedResponseError = plugins.typedrequest.TypedResponseError;
3
+ export const TypedRequestTimeoutError = plugins.typedrequest.TypedRequestTimeoutError;
2
4
  const isProcessHolding = (channelArg) => 'ref' in channelArg &&
3
5
  typeof channelArg.ref === 'function' &&
4
6
  'unref' in channelArg &&
@@ -83,18 +85,22 @@ export class DeesComms {
83
85
  // receiving messages
84
86
  constructor(optionsArg = {}) {
85
87
  this.eventHandlers = new Map();
86
- this.hasTypedHandlers = false;
88
+ this.typedHandlerCount = 0;
87
89
  this.closed = false;
88
90
  this.closing = null;
89
91
  // sending messages
90
92
  this.typedrouter = new plugins.typedrequest.TypedRouter();
93
+ this.channelName = optionsArg.channelName ?? DeesComms.defaultChannelName;
91
94
  this.typedtarget = new plugins.typedrequest.TypedTarget({
95
+ // posting is done once it returns, so a request aborted later has no
96
+ // transport work left to release; the target stops its wait
92
97
  postMethodWithTypedRouter: async (messageArg) => {
93
98
  await this.postMessage(messageArg);
94
99
  },
95
100
  typedRouterRef: this.typedrouter,
101
+ supportsAbortSignal: true,
102
+ responseTimeoutMs: optionsArg.responseTimeoutMs,
96
103
  });
97
- this.channelName = optionsArg.channelName ?? DeesComms.defaultChannelName;
98
104
  this.channel = openChannel(this.channelName, (messageArg) => {
99
105
  void this.receive(messageArg);
100
106
  });
@@ -114,12 +120,32 @@ export class DeesComms {
114
120
  await this.channel.post(messageArg);
115
121
  }
116
122
  /**
117
- * subscribe to messages
123
+ * Answers the typed requests of a method that other instances on this
124
+ * channel fire. The handler answers with what it returns; one that throws a
125
+ * `TypedResponseError` refuses the request, and its caller gets that error
126
+ * at once instead of waiting for its timeout. An instance has at most one
127
+ * handler per method.
118
128
  */
119
129
  async createTypedHandler(methodArg, handlerFunction) {
120
- this.typedrouter.addTypedHandler(new plugins.typedrequest.TypedHandler(methodArg, handlerFunction));
121
- this.hasTypedHandlers = true;
130
+ // each handler sits in a router of its own, so that removing it detaches
131
+ // that router from the instance's
132
+ const handlerRouter = new plugins.typedrequest.TypedRouter();
133
+ handlerRouter.addTypedHandler(new plugins.typedrequest.TypedHandler(methodArg, handlerFunction));
134
+ const detach = this.typedrouter.addTypedRouter(handlerRouter);
135
+ this.typedHandlerCount++;
122
136
  this.holdProcessWhileListened();
137
+ let removed = false;
138
+ return {
139
+ remove: () => {
140
+ if (removed) {
141
+ return;
142
+ }
143
+ removed = true;
144
+ detach();
145
+ this.typedHandlerCount--;
146
+ this.holdProcessWhileListened();
147
+ },
148
+ };
123
149
  }
124
150
  /**
125
151
  * Sends a typed event to every other instance on this channel and expects
@@ -191,7 +217,7 @@ export class DeesComms {
191
217
  if (this.closed) {
192
218
  return;
193
219
  }
194
- this.channel.holdProcess(this.hasTypedHandlers || this.eventHandlers.size > 0);
220
+ this.channel.holdProcess(this.typedHandlerCount > 0 || this.eventHandlers.size > 0);
195
221
  }
196
222
  assertOpen() {
197
223
  if (this.closed) {
@@ -211,11 +237,21 @@ export class DeesComms {
211
237
  this.dispatch(event);
212
238
  return;
213
239
  }
240
+ // Every instance on the channel hears a request; only one with a handler
241
+ // for its method answers it, so the others do not refuse it in its place.
242
+ if (isObject(messageArg) &&
243
+ isObject(messageArg.correlation) &&
244
+ messageArg.correlation.phase === 'request' &&
245
+ typeof messageArg.method === 'string' &&
246
+ !this.typedrouter.checkForTypedHandler(messageArg.method)) {
247
+ return;
248
+ }
214
249
  // Skip hooks for broadcast-received messages to prevent infinite loops
215
250
  // when traffic logging hooks are set up globally. The router validates
216
251
  // the shape of what it is given.
217
252
  const response = await this.typedrouter.routeAndAddResponse(messageArg, { skipHooks: true });
218
- if (response && !response.error && !this.closed) {
253
+ // a refusal travels like an answer
254
+ if (response && !this.closed) {
219
255
  await this.postMessage(response);
220
256
  }
221
257
  }
@@ -240,4 +276,4 @@ export class DeesComms {
240
276
  }
241
277
  }
242
278
  }
243
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVlcy1jb21tcy5jbGFzc2VzLmRlZXNjb21tcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2RlZXMtY29tbXMuY2xhc3Nlcy5kZWVzY29tbXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSx5QkFBeUIsQ0FBQztBQWdFbkQsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLFVBQWtCLEVBQXdDLEVBQUUsQ0FDcEYsS0FBSyxJQUFJLFVBQVU7SUFDbkIsT0FBTyxVQUFVLENBQUMsR0FBRyxLQUFLLFVBQVU7SUFDcEMsT0FBTyxJQUFJLFVBQVU7SUFDckIsT0FBTyxVQUFVLENBQUMsS0FBSyxLQUFLLFVBQVUsQ0FBQztBQUV6Qzs7OztHQUlHO0FBQ0gsTUFBTSxXQUFXLEdBQUcsQ0FDbEIsT0FBZSxFQUNmLFVBQXlDLEVBQ3RCLEVBQUU7SUFDckIsSUFBSSxPQUFPLFVBQVUsQ0FBQyxnQkFBZ0IsS0FBSyxVQUFVLEVBQUUsQ0FBQztRQUN0RCxNQUFNLE9BQU8sR0FBRyxJQUFJLFVBQVUsQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUN6RCxPQUFPLENBQUMsU0FBUyxHQUFHLENBQUMsUUFBK0IsRUFBRSxFQUFFO1lBQ3RELFVBQVUsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDNUIsQ0FBQyxDQUFDO1FBQ0YsTUFBTSxjQUFjLEdBQUcsZ0JBQWdCLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO1FBQ2xFLGdFQUFnRTtRQUNoRSxjQUFjLEVBQUUsS0FBSyxFQUFFLENBQUM7UUFDeEIsT0FBTztZQUNMLElBQUksRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLEVBQUU7Z0JBQ3pCLE9BQU8sQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUM7WUFDbEMsQ0FBQztZQUNELEtBQUssRUFBRSxLQUFLLElBQUksRUFBRTtn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
279
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVlcy1jb21tcy5jbGFzc2VzLmRlZXNjb21tcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2RlZXMtY29tbXMuY2xhc3Nlcy5kZWVzY29tbXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSx5QkFBeUIsQ0FBQztBQXlCbkQsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQztBQU8xRSxNQUFNLENBQUMsTUFBTSx3QkFBd0IsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLHdCQUF3QixDQUFDO0FBK0R0RixNQUFNLGdCQUFnQixHQUFHLENBQUMsVUFBa0IsRUFBd0MsRUFBRSxDQUNwRixLQUFLLElBQUksVUFBVTtJQUNuQixPQUFPLFVBQVUsQ0FBQyxHQUFHLEtBQUssVUFBVTtJQUNwQyxPQUFPLElBQUksVUFBVTtJQUNyQixPQUFPLFVBQVUsQ0FBQyxLQUFLLEtBQUssVUFBVSxDQUFDO0FBRXpDOzs7O0dBSUc7QUFDSCxNQUFNLFdBQVcsR0FBRyxDQUNsQixPQUFlLEVBQ2YsVUFBeUMsRUFDdEIsRUFBRTtJQUNyQixJQUFJLE9BQU8sVUFBVSxDQUFDLGdCQUFnQixLQUFLLFVBQVUsRUFBRSxDQUFDO1FBQ3RELE1BQU0sT0FBTyxHQUFHLElBQUksVUFBVSxDQUFDLGdCQUFnQixDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ3pELE9BQU8sQ0FBQyxTQUFTLEdBQUcsQ0FBQyxRQUErQixFQUFFLEVBQUU7WUFDdEQsVUFBVSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM1QixDQUFDLENBQUM7UUFDRixNQUFNLGNBQWMsR0FBRyxnQkFBZ0IsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7UUFDbEUsZ0VBQWdFO1FBQ2hFLGNBQWMsRUFBRSxLQUFLLEVBQUUsQ0FBQztRQUN4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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@design.estate/dees-comms",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
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,14 +9,14 @@
9
9
  "author": "Lossless GmbH",
10
10
  "license": "MIT",
11
11
  "devDependencies": {
12
- "@git.zone/tsbuild": "^5.1.2",
13
- "@git.zone/tsbundle": "^3.1.3",
14
- "@git.zone/tsrun": "^3.0.2",
15
- "@git.zone/tstest": "^7.0.0",
16
- "@types/node": "^26.6.3"
12
+ "@git.zone/tsbuild": "^6.0.2",
13
+ "@git.zone/tsbundle": "^3.2.1",
14
+ "@git.zone/tsrun": "^3.0.3",
15
+ "@git.zone/tstest": "^7.2.1",
16
+ "@types/node": "^26.6.4"
17
17
  },
18
18
  "dependencies": {
19
- "@api.global/typedrequest": "^9.0.2",
19
+ "@api.global/typedrequest": "^9.1.2",
20
20
  "@api.global/typedrequest-interfaces": "^7.1.0",
21
21
  "broadcast-channel": "^7.2.0"
22
22
  },
package/readme.md CHANGED
@@ -67,6 +67,81 @@ console.log(response.reply); // 'Hello Alice, nice to meet you!'
67
67
 
68
68
  `postMessage()` posts a typed request object as it is; `typedrouter` and `typedtarget` are the `@api.global/typedrequest` 9 router and target the instance routes with. A handler answers with what it returns, `false`, `0` and `''` included; a handler that returns nothing answers `response: null`.
69
69
 
70
+ - Every other instance on the channel hears a request, but only one with a handler for its method answers it; the others stay silent. When several instances have a handler for it, the first answer to arrive is the response and the later ones are dropped.
71
+ - An instance has at most one handler per method: `createTypedHandler()` rejects a second one.
72
+ - The instance that fires a request does not answer it itself, even when it has a handler for its method.
73
+
74
+ #### Waiting for the answer
75
+
76
+ A request waits 30 seconds for its answer by default. `responseTimeoutMs` sets that limit for every request an instance fires; it must be a whole number of milliseconds from 1 to 2147483647, or the constructor throws a `RangeError`. A request that reaches the limit rejects with an `Error` (`TypedTarget response timed out after …ms`).
77
+
78
+ A single request can wait less with `timeoutMs`, and its caller can cancel it with an `AbortSignal`:
79
+
80
+ ```typescript
81
+ import { DeesComms, TypedRequestTimeoutError } from '@design.estate/dees-comms';
82
+
83
+ const asking = new DeesComms({ channelName: 'my-app', responseTimeoutMs: 10_000 });
84
+ const greet = asking.createTypedRequest<IGreetRequest>('greet');
85
+
86
+ try {
87
+ await greet.fire({ name: 'Alice' }, { timeoutMs: 1_000 });
88
+ } catch (error) {
89
+ if (error instanceof TypedRequestTimeoutError) {
90
+ console.log(`nobody answered within ${error.timeoutMs} ms`);
91
+ }
92
+ }
93
+
94
+ const controller = new AbortController();
95
+ const pending = greet.fire({ name: 'Bob' }, { abortSignal: controller.signal });
96
+ controller.abort(new Error('the tab is closing'));
97
+ await pending.catch((reason) => console.log(reason.message)); // 'the tab is closing'
98
+ ```
99
+
100
+ - A request with its own `timeoutMs` ends at the earlier of its own deadline and the instance's `responseTimeoutMs`, and rejects with a `TypedRequestTimeoutError` (carrying `timeoutMs`) when its own deadline comes first.
101
+ - An aborted request rejects with the signal's reason at once. A request fired with a signal that is already aborted is never sent. A handler that is already working on an aborted request still finishes; its answer is dropped.
102
+
103
+ #### Refusing a request
104
+
105
+ A handler refuses a request by throwing a `TypedResponseError`. Its caller rejects with that error at once, with its `errorText` and `errorData`, instead of waiting for its timeout:
106
+
107
+ ```typescript
108
+ import { DeesComms, TypedResponseError } from '@design.estate/dees-comms';
109
+
110
+ answering.createTypedHandler<IGreetRequest>('greet', async (request) => {
111
+ if (request.name === '') {
112
+ throw new TypedResponseError('name-required', { field: 'name' });
113
+ }
114
+ return { reply: `Hello ${request.name}, nice to meet you!` };
115
+ });
116
+
117
+ try {
118
+ await asking.createTypedRequest<IGreetRequest>('greet').fire({ name: '' });
119
+ } catch (error) {
120
+ if (error instanceof TypedResponseError) {
121
+ console.log(error.errorText, error.errorData); // 'name-required' { field: 'name' }
122
+ }
123
+ }
124
+ ```
125
+
126
+ - A handler that throws any other error answers with a `TypedResponseError` whose `errorText` is `Internal server error`; the error itself does not travel.
127
+ - A refusal is an answer: when several instances have a handler for the method, the first answer wins, whether it is a response or a refusal. An instance that cannot help but wants to leave the request to the others removes its handler instead of refusing.
128
+ - `TypedResponseError` and `TypedRequestTimeoutError` are exported by `@design.estate/dees-comms` and are the classes of the `@api.global/typedrequest` it routes with. Use these exports for `instanceof`: an application that depends on another version of `@api.global/typedrequest` has classes of its own that the errors are not instances of.
129
+
130
+ #### Removing a handler
131
+
132
+ `createTypedHandler()` resolves with a handle whose `remove()` stops the handler:
133
+
134
+ ```typescript
135
+ const handler = await answering.createTypedHandler<IGreetRequest>('greet', async (request) => {
136
+ return { reply: `Hello ${request.name}!` };
137
+ });
138
+
139
+ // later: the requests are left to the other instances
140
+ handler.remove();
141
+ ```
142
+
143
+ A request that arrives after `remove()` is left to the other instances, as if this one never had the handler; one the handler is working on still gets its answer. The method can be handled again with a new `createTypedHandler()`. Calling `remove()` again does nothing.
144
+
70
145
  ### Typed events
71
146
 
72
147
  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.
@@ -109,9 +184,9 @@ subscription.unsubscribe();
109
184
  await comms.close();
110
185
  ```
111
186
 
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.
187
+ 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 at its timeout; abort it with its `AbortSignal` to end it at once. Closing again does nothing.
113
188
 
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.
189
+ 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 a typed handler. An instance nobody listens on, such as the one `@design.estate/dees-domtools` opens on import, lets the process end. An instance whose subscriptions ended and whose handlers were removed lets it end too, as a closed one does, so remove every typed handler or close every instance with one that a test or a script opens. In browsers and workers nothing changes.
115
190
 
116
191
  ### What travels
117
192
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-comms',
6
- version: '2.0.0',
6
+ version: '2.1.0',
7
7
  description: 'A communications module for enabling DOM-based messaging and synchronization across browser tabs and workers.'
8
8
  }
@@ -8,6 +8,37 @@ export interface IDeesCommsOptions {
8
8
  * used before names existed.
9
9
  */
10
10
  channelName?: string;
11
+ /**
12
+ * How long a typed request this instance fires waits for its answer, in
13
+ * milliseconds. Defaults to 30 seconds. A request fired with its own
14
+ * `timeoutMs` ends at the earlier of the two.
15
+ */
16
+ responseTimeoutMs?: number;
17
+ }
18
+
19
+ /**
20
+ * What a typed request fired on a DeesComms instance throws when a handler
21
+ * refuses it, and what a handler throws to refuse one. These are the classes
22
+ * of the `@api.global/typedrequest` the instance routes with, so `instanceof`
23
+ * holds whichever version the application itself depends on.
24
+ */
25
+ export type TypedResponseError = plugins.typedrequest.TypedResponseError;
26
+ export const TypedResponseError = plugins.typedrequest.TypedResponseError;
27
+
28
+ /**
29
+ * What a typed request fired with `timeoutMs` throws when no answer came in
30
+ * time.
31
+ */
32
+ export type TypedRequestTimeoutError = plugins.typedrequest.TypedRequestTimeoutError;
33
+ export const TypedRequestTimeoutError = plugins.typedrequest.TypedRequestTimeoutError;
34
+
35
+ export interface IDeesCommsTypedHandler {
36
+ /**
37
+ * Stops answering. A request that arrives from then on is left to the
38
+ * other instances, as if this one never had the handler; one it is
39
+ * answering right now still gets its answer. Calling it again does nothing.
40
+ */
41
+ remove(): void;
11
42
  }
12
43
 
13
44
  /**
@@ -162,22 +193,27 @@ export class DeesComms {
162
193
  public readonly channelName: string;
163
194
  private readonly channel: IDeesCommsChannel;
164
195
  private readonly eventHandlers = new Map<string, Set<TStoredEventHandler>>();
165
- private hasTypedHandlers = false;
196
+ private typedHandlerCount = 0;
166
197
  private closed = false;
167
198
  private closing: Promise<void> | null = null;
168
199
 
169
200
  // sending messages
170
201
  public typedrouter = new plugins.typedrequest.TypedRouter();
171
- public typedtarget = new plugins.typedrequest.TypedTarget({
172
- postMethodWithTypedRouter: async (messageArg) => {
173
- await this.postMessage(messageArg);
174
- },
175
- typedRouterRef: this.typedrouter,
176
- });
202
+ public typedtarget: plugins.typedrequest.TypedTarget;
177
203
 
178
204
  // receiving messages
179
205
  constructor(optionsArg: IDeesCommsOptions = {}) {
180
206
  this.channelName = optionsArg.channelName ?? DeesComms.defaultChannelName;
207
+ this.typedtarget = new plugins.typedrequest.TypedTarget({
208
+ // posting is done once it returns, so a request aborted later has no
209
+ // transport work left to release; the target stops its wait
210
+ postMethodWithTypedRouter: async (messageArg) => {
211
+ await this.postMessage(messageArg);
212
+ },
213
+ typedRouterRef: this.typedrouter,
214
+ supportsAbortSignal: true,
215
+ responseTimeoutMs: optionsArg.responseTimeoutMs,
216
+ });
181
217
  this.channel = openChannel(this.channelName, (messageArg) => {
182
218
  void this.receive(messageArg);
183
219
  });
@@ -204,17 +240,37 @@ export class DeesComms {
204
240
  }
205
241
 
206
242
  /**
207
- * subscribe to messages
243
+ * Answers the typed requests of a method that other instances on this
244
+ * channel fire. The handler answers with what it returns; one that throws a
245
+ * `TypedResponseError` refuses the request, and its caller gets that error
246
+ * at once instead of waiting for its timeout. An instance has at most one
247
+ * handler per method.
208
248
  */
209
249
  public async createTypedHandler<T extends plugins.typedrequestInterfaces.ITypedRequest>(
210
250
  methodArg: T['method'],
211
251
  handlerFunction: plugins.typedrequest.THandlerFunction<T>
212
- ) {
213
- this.typedrouter.addTypedHandler(
252
+ ): Promise<IDeesCommsTypedHandler> {
253
+ // each handler sits in a router of its own, so that removing it detaches
254
+ // that router from the instance's
255
+ const handlerRouter = new plugins.typedrequest.TypedRouter();
256
+ handlerRouter.addTypedHandler(
214
257
  new plugins.typedrequest.TypedHandler<T>(methodArg, handlerFunction)
215
258
  );
216
- this.hasTypedHandlers = true;
259
+ const detach = this.typedrouter.addTypedRouter(handlerRouter);
260
+ this.typedHandlerCount++;
217
261
  this.holdProcessWhileListened();
262
+ let removed = false;
263
+ return {
264
+ remove: () => {
265
+ if (removed) {
266
+ return;
267
+ }
268
+ removed = true;
269
+ detach();
270
+ this.typedHandlerCount--;
271
+ this.holdProcessWhileListened();
272
+ },
273
+ };
218
274
  }
219
275
 
220
276
  /**
@@ -297,7 +353,7 @@ export class DeesComms {
297
353
  if (this.closed) {
298
354
  return;
299
355
  }
300
- this.channel.holdProcess(this.hasTypedHandlers || this.eventHandlers.size > 0);
356
+ this.channel.holdProcess(this.typedHandlerCount > 0 || this.eventHandlers.size > 0);
301
357
  }
302
358
 
303
359
  private assertOpen(): void {
@@ -319,6 +375,17 @@ export class DeesComms {
319
375
  this.dispatch(event);
320
376
  return;
321
377
  }
378
+ // Every instance on the channel hears a request; only one with a handler
379
+ // for its method answers it, so the others do not refuse it in its place.
380
+ if (
381
+ isObject(messageArg) &&
382
+ isObject(messageArg.correlation) &&
383
+ messageArg.correlation.phase === 'request' &&
384
+ typeof messageArg.method === 'string' &&
385
+ !this.typedrouter.checkForTypedHandler(messageArg.method)
386
+ ) {
387
+ return;
388
+ }
322
389
  // Skip hooks for broadcast-received messages to prevent infinite loops
323
390
  // when traffic logging hooks are set up globally. The router validates
324
391
  // the shape of what it is given.
@@ -326,7 +393,8 @@ export class DeesComms {
326
393
  messageArg as plugins.typedrequestInterfaces.ITypedRequest,
327
394
  { skipHooks: true }
328
395
  );
329
- if (response && !response.error && !this.closed) {
396
+ // a refusal travels like an answer
397
+ if (response && !this.closed) {
330
398
  await this.postMessage(response);
331
399
  }
332
400
  }