@solana/subscribable 2.0.0-20241006045741
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/LICENSE +20 -0
- package/README.md +124 -0
- package/dist/index.browser.cjs +197 -0
- package/dist/index.browser.cjs.map +1 -0
- package/dist/index.browser.mjs +193 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.native.mjs +193 -0
- package/dist/index.native.mjs.map +1 -0
- package/dist/index.node.cjs +197 -0
- package/dist/index.node.cjs.map +1 -0
- package/dist/index.node.mjs +193 -0
- package/dist/index.node.mjs.map +1 -0
- package/dist/types/async-iterable.d.ts +10 -0
- package/dist/types/async-iterable.d.ts.map +1 -0
- package/dist/types/data-publisher.d.ts +12 -0
- package/dist/types/data-publisher.d.ts.map +1 -0
- package/dist/types/demultiplex.d.ts +3 -0
- package/dist/types/demultiplex.d.ts.map +1 -0
- package/dist/types/event-emitter.d.ts +19 -0
- package/dist/types/event-emitter.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +89 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2023 Solana Labs, Inc
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
[![npm][npm-image]][npm-url]
|
|
2
|
+
[![npm-downloads][npm-downloads-image]][npm-url]
|
|
3
|
+
[![semantic-release][semantic-release-image]][semantic-release-url]
|
|
4
|
+
<br />
|
|
5
|
+
[![code-style-prettier][code-style-prettier-image]][code-style-prettier-url]
|
|
6
|
+
|
|
7
|
+
[code-style-prettier-image]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square
|
|
8
|
+
[code-style-prettier-url]: https://github.com/prettier/prettier
|
|
9
|
+
[npm-downloads-image]: https://img.shields.io/npm/dm/@solana/subscribable/rc.svg?style=flat
|
|
10
|
+
[npm-image]: https://img.shields.io/npm/v/@solana/subscribable/rc.svg?style=flat
|
|
11
|
+
[npm-url]: https://www.npmjs.com/package/@solana/subscribable/v/rc
|
|
12
|
+
[semantic-release-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
|
|
13
|
+
[semantic-release-url]: https://github.com/semantic-release/semantic-release
|
|
14
|
+
|
|
15
|
+
# @solana/subscribable
|
|
16
|
+
|
|
17
|
+
This package contains utilities for creating subscription-based event targets. These differ from the `EventTarget` interface in that the method you use to add a listener returns an unsubscribe function. It is primarily intended for internal use – particularly for those building `RpcSubscriptionChannels` and associated infrastructure.
|
|
18
|
+
|
|
19
|
+
## Types
|
|
20
|
+
|
|
21
|
+
### `DataPublisher<TDataByChannelName>`
|
|
22
|
+
|
|
23
|
+
This type represents an object with an `on` function that you can call to subscribe to certain data over a named channel.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
let dataPublisher: DataPublisher<{ error: SolanaError }>;
|
|
27
|
+
dataPublisher.on('data', handleData); // ERROR. `data` is not a known channel name.
|
|
28
|
+
dataPublisher.on('error', e => {
|
|
29
|
+
console.error(e);
|
|
30
|
+
}); // OK.
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### `TypedEventEmitter<TEventMap>`
|
|
34
|
+
|
|
35
|
+
This type allows you to type `addEventListener` and `removeEventListener` so that the call signature of the listener matches the event type given.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const emitter: TypedEventEmitter<{ message: MessageEvent }> = new WebSocket('wss://api.devnet.solana.com');
|
|
39
|
+
emitter.addEventListener('data', handleData); // ERROR. `data` is not a known event type.
|
|
40
|
+
emitter.addEventListener('message', message => {
|
|
41
|
+
console.log(message.origin); // OK. `message` is a `MessageEvent` so it has an `origin` property.
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### `TypedEventTarget<TEventMap>`
|
|
46
|
+
|
|
47
|
+
This type is a superset of `TypedEventEmitter` that allows you to constrain calls to `dispatchEvent`.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const target: TypedEventTarget<{ candyVended: CustomEvent<{ flavour: string }> }> = new EventTarget();
|
|
51
|
+
target.dispatchEvent(new CustomEvent('candyVended', { detail: { flavour: 'raspberry' } })); // OK.
|
|
52
|
+
target.dispatchEvent(new CustomEvent('candyVended', { detail: { flavor: 'raspberry' } })); // ERROR. Misspelling in detail.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Functions
|
|
56
|
+
|
|
57
|
+
### `createAsyncIterableFromDataPublisher({ abortSignal, dataChannelName, dataPublisher, errorChannelName })`
|
|
58
|
+
|
|
59
|
+
Returns an `AsyncIterable` given a data publisher. The iterable will produce iterators that vend messages published to `dataChannelName` and will throw the first time a message is published to `errorChannelName`. Triggering the abort signal will cause all iterators spawned from this iterator to return once they have published all queued messages.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const iterable = createAsyncIterableFromDataPublisher({
|
|
63
|
+
abortSignal: AbortSignal.timeout(10_000),
|
|
64
|
+
dataChannelName: 'message',
|
|
65
|
+
dataPublisher,
|
|
66
|
+
errorChannelName: 'error',
|
|
67
|
+
});
|
|
68
|
+
try {
|
|
69
|
+
for await (const message of iterable) {
|
|
70
|
+
console.log('Got message', message);
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.error('An error was published to the error channel', e);
|
|
74
|
+
} finally {
|
|
75
|
+
console.log("It's been 10 seconds; that's enough for now.");
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Things to note:
|
|
80
|
+
|
|
81
|
+
- If a message is published over a channel before the `AsyncIterator` attached to it has polled for the next result, the message will be queued in memory.
|
|
82
|
+
- Messages only begin to be queued after the first time an iterator begins to poll. Channel messages published before that time will be dropped.
|
|
83
|
+
- If there are messages in the queue and an error occurs, all queued messages will be vended to the iterator before the error is thrown.
|
|
84
|
+
- If there are messages in the queue and the abort signal fires, all queued messages will be vended to the iterator after which it will return.
|
|
85
|
+
- Any new iterators created after the first error is encountered will reject with that error when polled.
|
|
86
|
+
|
|
87
|
+
### `demultiplexDataPublisher(publisher, sourceChannelName, messageTransformer)`
|
|
88
|
+
|
|
89
|
+
Given a channel that carries messages for multiple subscribers on a single channel name, this function returns a new `DataPublisher` that splits them into multiple channel names.
|
|
90
|
+
|
|
91
|
+
Imagine a channel that carries multiple notifications whose destination is contained within the message itself.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const demuxedDataPublisher = demultiplexDataPublisher(channel, 'message', message => {
|
|
95
|
+
const destinationChannelName = `notification-for:${message.subscriberId}`;
|
|
96
|
+
return [destinationChannelName, message];
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Now you can subscribe to _only_ the messages you are interested in, without having to subscribe to the entire `'message'` channel and filter out the messages that are not for you.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
demuxedDataPublisher.on(
|
|
104
|
+
'notification-for:123',
|
|
105
|
+
message => {
|
|
106
|
+
console.log('Got a message for subscriber 123', message);
|
|
107
|
+
},
|
|
108
|
+
{ signal: AbortSignal.timeout(5_000) },
|
|
109
|
+
);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `getDataPublisherFromEventEmitter(emitter)`
|
|
113
|
+
|
|
114
|
+
Returns an object with an `on` function that you can call to subscribe to certain data over a named channel. The `on` function returns an unsubscribe function.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
const socketDataPublisher = getDataPublisherFromEventEmitter(new WebSocket('wss://api.devnet.solana.com'));
|
|
118
|
+
const unsubscribe = socketDataPublisher.on('message', message => {
|
|
119
|
+
if (JSON.parse(message.data).id === 42) {
|
|
120
|
+
console.log('Got response 42');
|
|
121
|
+
unsubscribe();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
```
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@solana/errors');
|
|
4
|
+
|
|
5
|
+
// src/async-iterable.ts
|
|
6
|
+
var EXPLICIT_ABORT_TOKEN;
|
|
7
|
+
function createExplicitAbortToken() {
|
|
8
|
+
return Symbol(
|
|
9
|
+
process.env.NODE_ENV !== "production" ? "This symbol is thrown from a socket's iterator when the connection is explicitly aborted by the user" : void 0
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
var UNINITIALIZED = Symbol();
|
|
13
|
+
function createAsyncIterableFromDataPublisher({
|
|
14
|
+
abortSignal,
|
|
15
|
+
dataChannelName,
|
|
16
|
+
dataPublisher,
|
|
17
|
+
errorChannelName
|
|
18
|
+
}) {
|
|
19
|
+
const iteratorState = /* @__PURE__ */ new Map();
|
|
20
|
+
function publishErrorToAllIterators(reason) {
|
|
21
|
+
for (const [iteratorKey, state] of iteratorState.entries()) {
|
|
22
|
+
if (state.__hasPolled) {
|
|
23
|
+
iteratorState.delete(iteratorKey);
|
|
24
|
+
state.onError(reason);
|
|
25
|
+
} else {
|
|
26
|
+
state.publishQueue.push({
|
|
27
|
+
__type: 1 /* ERROR */,
|
|
28
|
+
err: reason
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const abortController = new AbortController();
|
|
34
|
+
abortSignal.addEventListener("abort", () => {
|
|
35
|
+
abortController.abort();
|
|
36
|
+
publishErrorToAllIterators(EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken());
|
|
37
|
+
});
|
|
38
|
+
const options = { signal: abortController.signal };
|
|
39
|
+
let firstError = UNINITIALIZED;
|
|
40
|
+
dataPublisher.on(
|
|
41
|
+
errorChannelName,
|
|
42
|
+
(err) => {
|
|
43
|
+
if (firstError === UNINITIALIZED) {
|
|
44
|
+
firstError = err;
|
|
45
|
+
abortController.abort();
|
|
46
|
+
publishErrorToAllIterators(err);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
options
|
|
50
|
+
);
|
|
51
|
+
dataPublisher.on(
|
|
52
|
+
dataChannelName,
|
|
53
|
+
(data) => {
|
|
54
|
+
iteratorState.forEach((state, iteratorKey) => {
|
|
55
|
+
if (state.__hasPolled) {
|
|
56
|
+
const { onData } = state;
|
|
57
|
+
iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] });
|
|
58
|
+
onData(data);
|
|
59
|
+
} else {
|
|
60
|
+
state.publishQueue.push({
|
|
61
|
+
__type: 0 /* DATA */,
|
|
62
|
+
data
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
options
|
|
68
|
+
);
|
|
69
|
+
return {
|
|
70
|
+
async *[Symbol.asyncIterator]() {
|
|
71
|
+
if (abortSignal.aborted) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (firstError !== UNINITIALIZED) {
|
|
75
|
+
throw firstError;
|
|
76
|
+
}
|
|
77
|
+
const iteratorKey = Symbol();
|
|
78
|
+
iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] });
|
|
79
|
+
try {
|
|
80
|
+
while (true) {
|
|
81
|
+
const state = iteratorState.get(iteratorKey);
|
|
82
|
+
if (!state) {
|
|
83
|
+
throw new errors.SolanaError(errors.SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING);
|
|
84
|
+
}
|
|
85
|
+
if (state.__hasPolled) {
|
|
86
|
+
throw new errors.SolanaError(
|
|
87
|
+
errors.SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const publishQueue = state.publishQueue;
|
|
91
|
+
try {
|
|
92
|
+
if (publishQueue.length) {
|
|
93
|
+
state.publishQueue = [];
|
|
94
|
+
for (const item of publishQueue) {
|
|
95
|
+
if (item.__type === 0 /* DATA */) {
|
|
96
|
+
yield item.data;
|
|
97
|
+
} else {
|
|
98
|
+
throw item.err;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
yield await new Promise((resolve, reject) => {
|
|
103
|
+
iteratorState.set(iteratorKey, {
|
|
104
|
+
__hasPolled: true,
|
|
105
|
+
onData: resolve,
|
|
106
|
+
onError: reject
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
} catch (e) {
|
|
111
|
+
if (e === (EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken())) {
|
|
112
|
+
return;
|
|
113
|
+
} else {
|
|
114
|
+
throw e;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} finally {
|
|
119
|
+
iteratorState.delete(iteratorKey);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/data-publisher.ts
|
|
126
|
+
function getDataPublisherFromEventEmitter(eventEmitter) {
|
|
127
|
+
return {
|
|
128
|
+
on(channelName, subscriber, options) {
|
|
129
|
+
function innerListener(ev) {
|
|
130
|
+
if (ev instanceof CustomEvent) {
|
|
131
|
+
const data = ev.detail;
|
|
132
|
+
subscriber(data);
|
|
133
|
+
} else {
|
|
134
|
+
subscriber();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
eventEmitter.addEventListener(channelName, innerListener, options);
|
|
138
|
+
return () => {
|
|
139
|
+
eventEmitter.removeEventListener(channelName, innerListener);
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/demultiplex.ts
|
|
146
|
+
function demultiplexDataPublisher(publisher, sourceChannelName, messageTransformer) {
|
|
147
|
+
let innerPublisherState;
|
|
148
|
+
const eventTarget = new EventTarget();
|
|
149
|
+
const demultiplexedDataPublisher = getDataPublisherFromEventEmitter(eventTarget);
|
|
150
|
+
return {
|
|
151
|
+
...demultiplexedDataPublisher,
|
|
152
|
+
on(channelName, subscriber, options) {
|
|
153
|
+
if (!innerPublisherState) {
|
|
154
|
+
const innerPublisherUnsubscribe = publisher.on(sourceChannelName, (sourceMessage) => {
|
|
155
|
+
const transformResult = messageTransformer(sourceMessage);
|
|
156
|
+
if (!transformResult) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const [destinationChannelName, message] = transformResult;
|
|
160
|
+
eventTarget.dispatchEvent(
|
|
161
|
+
new CustomEvent(destinationChannelName, {
|
|
162
|
+
detail: message
|
|
163
|
+
})
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
innerPublisherState = {
|
|
167
|
+
dispose: innerPublisherUnsubscribe,
|
|
168
|
+
numSubscribers: 0
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
innerPublisherState.numSubscribers++;
|
|
172
|
+
const unsubscribe = demultiplexedDataPublisher.on(channelName, subscriber, options);
|
|
173
|
+
let isActive = true;
|
|
174
|
+
function handleUnsubscribe() {
|
|
175
|
+
if (!isActive) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
isActive = false;
|
|
179
|
+
options?.signal.removeEventListener("abort", handleUnsubscribe);
|
|
180
|
+
innerPublisherState.numSubscribers--;
|
|
181
|
+
if (innerPublisherState.numSubscribers === 0) {
|
|
182
|
+
innerPublisherState.dispose();
|
|
183
|
+
innerPublisherState = void 0;
|
|
184
|
+
}
|
|
185
|
+
unsubscribe();
|
|
186
|
+
}
|
|
187
|
+
options?.signal.addEventListener("abort", handleUnsubscribe);
|
|
188
|
+
return handleUnsubscribe;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
exports.createAsyncIterableFromDataPublisher = createAsyncIterableFromDataPublisher;
|
|
194
|
+
exports.demultiplexDataPublisher = demultiplexDataPublisher;
|
|
195
|
+
exports.getDataPublisherFromEventEmitter = getDataPublisherFromEventEmitter;
|
|
196
|
+
//# sourceMappingURL=index.browser.cjs.map
|
|
197
|
+
//# sourceMappingURL=index.browser.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/async-iterable.ts","../src/data-publisher.ts","../src/demultiplex.ts"],"names":["SolanaError","SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING","SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE"],"mappings":"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","file":"index.browser.cjs","sourcesContent":["import {\n SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE,\n SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING,\n SolanaError,\n} from '@solana/errors';\n\nimport { DataPublisher } from './data-publisher';\n\ntype Config = Readonly<{\n abortSignal: AbortSignal;\n dataChannelName: string;\n // FIXME: It would be nice to be able to constrain the type of `dataPublisher` to one that\n // definitely supports the `dataChannelName` and `errorChannelName` channels, and\n // furthermore publishes `TData` on the `dataChannelName` channel. This is more difficult\n // than it should be: https://tsplay.dev/NlZelW\n dataPublisher: DataPublisher;\n errorChannelName: string;\n}>;\n\nconst enum PublishType {\n DATA,\n ERROR,\n}\n\ntype IteratorKey = symbol;\ntype IteratorState<TData> =\n | {\n __hasPolled: false;\n publishQueue: (\n | {\n __type: PublishType.DATA;\n data: TData;\n }\n | {\n __type: PublishType.ERROR;\n err: unknown;\n }\n )[];\n }\n | {\n __hasPolled: true;\n onData: (data: TData) => void;\n onError: Parameters<ConstructorParameters<typeof Promise>[0]>[1];\n };\n\nlet EXPLICIT_ABORT_TOKEN: symbol;\nfunction createExplicitAbortToken() {\n // This function is an annoying workaround to prevent `process.env.NODE_ENV` from appearing at\n // the top level of this module and thwarting an optimizing compiler's attempt to tree-shake.\n return Symbol(\n process.env.NODE_ENV !== \"production\"\n ? \"This symbol is thrown from a socket's iterator when the connection is explicitly \" +\n 'aborted by the user'\n : undefined,\n );\n}\n\nconst UNINITIALIZED = Symbol();\n\nexport function createAsyncIterableFromDataPublisher<TData>({\n abortSignal,\n dataChannelName,\n dataPublisher,\n errorChannelName,\n}: Config): AsyncIterable<TData> {\n const iteratorState: Map<IteratorKey, IteratorState<TData>> = new Map();\n function publishErrorToAllIterators(reason: unknown) {\n for (const [iteratorKey, state] of iteratorState.entries()) {\n if (state.__hasPolled) {\n iteratorState.delete(iteratorKey);\n state.onError(reason);\n } else {\n state.publishQueue.push({\n __type: PublishType.ERROR,\n err: reason,\n });\n }\n }\n }\n const abortController = new AbortController();\n abortSignal.addEventListener('abort', () => {\n abortController.abort();\n publishErrorToAllIterators((EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken()));\n });\n const options = { signal: abortController.signal } as const;\n let firstError: unknown | typeof UNINITIALIZED = UNINITIALIZED;\n dataPublisher.on(\n errorChannelName,\n err => {\n if (firstError === UNINITIALIZED) {\n firstError = err;\n abortController.abort();\n publishErrorToAllIterators(err);\n }\n },\n options,\n );\n dataPublisher.on(\n dataChannelName,\n data => {\n iteratorState.forEach((state, iteratorKey) => {\n if (state.__hasPolled) {\n const { onData } = state;\n iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] });\n onData(data as TData);\n } else {\n state.publishQueue.push({\n __type: PublishType.DATA,\n data: data as TData,\n });\n }\n });\n },\n options,\n );\n return {\n async *[Symbol.asyncIterator]() {\n if (abortSignal.aborted) {\n return;\n }\n if (firstError !== UNINITIALIZED) {\n throw firstError;\n }\n const iteratorKey = Symbol();\n iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] });\n try {\n while (true) {\n const state = iteratorState.get(iteratorKey);\n if (!state) {\n // There should always be state by now.\n throw new SolanaError(SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING);\n }\n if (state.__hasPolled) {\n // You should never be able to poll twice in a row.\n throw new SolanaError(\n SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE,\n );\n }\n const publishQueue = state.publishQueue;\n try {\n if (publishQueue.length) {\n state.publishQueue = [];\n for (const item of publishQueue) {\n if (item.__type === PublishType.DATA) {\n yield item.data;\n } else {\n throw item.err;\n }\n }\n } else {\n yield await new Promise<TData>((resolve, reject) => {\n iteratorState.set(iteratorKey, {\n __hasPolled: true,\n onData: resolve,\n onError: reject,\n });\n });\n }\n } catch (e) {\n if (e === (EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken())) {\n return;\n } else {\n throw e;\n }\n }\n }\n } finally {\n iteratorState.delete(iteratorKey);\n }\n },\n };\n}\n","import { TypedEventEmitter, TypedEventTarget } from './event-emitter';\n\ntype UnsubscribeFn = () => void;\n\nexport interface DataPublisher<TDataByChannelName extends Record<string, unknown> = Record<string, unknown>> {\n on<const TChannelName extends keyof TDataByChannelName>(\n channelName: TChannelName,\n subscriber: (data: TDataByChannelName[TChannelName]) => void,\n options?: { signal: AbortSignal },\n ): UnsubscribeFn;\n}\n\nexport function getDataPublisherFromEventEmitter<TEventMap extends Record<string, Event>>(\n eventEmitter: TypedEventEmitter<TEventMap> | TypedEventTarget<TEventMap>,\n): DataPublisher<{\n [TEventType in keyof TEventMap]: TEventMap[TEventType] extends CustomEvent ? TEventMap[TEventType]['detail'] : null;\n}> {\n return {\n on(channelName, subscriber, options) {\n function innerListener(ev: Event) {\n if (ev instanceof CustomEvent) {\n const data = (ev as CustomEvent<TEventMap[typeof channelName]>).detail;\n (subscriber as unknown as (data: TEventMap[typeof channelName]) => void)(data);\n } else {\n (subscriber as () => void)();\n }\n }\n eventEmitter.addEventListener(channelName, innerListener, options);\n return () => {\n eventEmitter.removeEventListener(channelName, innerListener);\n };\n },\n };\n}\n","import { DataPublisher, getDataPublisherFromEventEmitter } from './data-publisher';\n\nexport function demultiplexDataPublisher<\n TDataPublisher extends DataPublisher,\n const TChannelName extends Parameters<TDataPublisher['on']>[0],\n>(\n publisher: TDataPublisher,\n sourceChannelName: TChannelName,\n messageTransformer: (\n // FIXME: Deriving the type of the message from `TDataPublisher` and `TChannelName` would\n // help callers to constrain their transform functions.\n message: unknown,\n ) => [destinationChannelName: string, message: unknown] | void,\n): DataPublisher {\n let innerPublisherState:\n | {\n readonly dispose: () => void;\n numSubscribers: number;\n }\n | undefined;\n const eventTarget = new EventTarget();\n const demultiplexedDataPublisher = getDataPublisherFromEventEmitter(eventTarget);\n return {\n ...demultiplexedDataPublisher,\n on(channelName, subscriber, options) {\n if (!innerPublisherState) {\n const innerPublisherUnsubscribe = publisher.on(sourceChannelName, sourceMessage => {\n const transformResult = messageTransformer(sourceMessage);\n if (!transformResult) {\n return;\n }\n const [destinationChannelName, message] = transformResult;\n eventTarget.dispatchEvent(\n new CustomEvent(destinationChannelName, {\n detail: message,\n }),\n );\n });\n innerPublisherState = {\n dispose: innerPublisherUnsubscribe,\n numSubscribers: 0,\n };\n }\n innerPublisherState.numSubscribers++;\n const unsubscribe = demultiplexedDataPublisher.on(channelName, subscriber, options);\n let isActive = true;\n function handleUnsubscribe() {\n if (!isActive) {\n return;\n }\n isActive = false;\n options?.signal.removeEventListener('abort', handleUnsubscribe);\n innerPublisherState!.numSubscribers--;\n if (innerPublisherState!.numSubscribers === 0) {\n innerPublisherState!.dispose();\n innerPublisherState = undefined;\n }\n unsubscribe();\n }\n options?.signal.addEventListener('abort', handleUnsubscribe);\n return handleUnsubscribe;\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { SolanaError, SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING, SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE } from '@solana/errors';
|
|
2
|
+
|
|
3
|
+
// src/async-iterable.ts
|
|
4
|
+
var EXPLICIT_ABORT_TOKEN;
|
|
5
|
+
function createExplicitAbortToken() {
|
|
6
|
+
return Symbol(
|
|
7
|
+
process.env.NODE_ENV !== "production" ? "This symbol is thrown from a socket's iterator when the connection is explicitly aborted by the user" : void 0
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
var UNINITIALIZED = Symbol();
|
|
11
|
+
function createAsyncIterableFromDataPublisher({
|
|
12
|
+
abortSignal,
|
|
13
|
+
dataChannelName,
|
|
14
|
+
dataPublisher,
|
|
15
|
+
errorChannelName
|
|
16
|
+
}) {
|
|
17
|
+
const iteratorState = /* @__PURE__ */ new Map();
|
|
18
|
+
function publishErrorToAllIterators(reason) {
|
|
19
|
+
for (const [iteratorKey, state] of iteratorState.entries()) {
|
|
20
|
+
if (state.__hasPolled) {
|
|
21
|
+
iteratorState.delete(iteratorKey);
|
|
22
|
+
state.onError(reason);
|
|
23
|
+
} else {
|
|
24
|
+
state.publishQueue.push({
|
|
25
|
+
__type: 1 /* ERROR */,
|
|
26
|
+
err: reason
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const abortController = new AbortController();
|
|
32
|
+
abortSignal.addEventListener("abort", () => {
|
|
33
|
+
abortController.abort();
|
|
34
|
+
publishErrorToAllIterators(EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken());
|
|
35
|
+
});
|
|
36
|
+
const options = { signal: abortController.signal };
|
|
37
|
+
let firstError = UNINITIALIZED;
|
|
38
|
+
dataPublisher.on(
|
|
39
|
+
errorChannelName,
|
|
40
|
+
(err) => {
|
|
41
|
+
if (firstError === UNINITIALIZED) {
|
|
42
|
+
firstError = err;
|
|
43
|
+
abortController.abort();
|
|
44
|
+
publishErrorToAllIterators(err);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
options
|
|
48
|
+
);
|
|
49
|
+
dataPublisher.on(
|
|
50
|
+
dataChannelName,
|
|
51
|
+
(data) => {
|
|
52
|
+
iteratorState.forEach((state, iteratorKey) => {
|
|
53
|
+
if (state.__hasPolled) {
|
|
54
|
+
const { onData } = state;
|
|
55
|
+
iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] });
|
|
56
|
+
onData(data);
|
|
57
|
+
} else {
|
|
58
|
+
state.publishQueue.push({
|
|
59
|
+
__type: 0 /* DATA */,
|
|
60
|
+
data
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
options
|
|
66
|
+
);
|
|
67
|
+
return {
|
|
68
|
+
async *[Symbol.asyncIterator]() {
|
|
69
|
+
if (abortSignal.aborted) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (firstError !== UNINITIALIZED) {
|
|
73
|
+
throw firstError;
|
|
74
|
+
}
|
|
75
|
+
const iteratorKey = Symbol();
|
|
76
|
+
iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] });
|
|
77
|
+
try {
|
|
78
|
+
while (true) {
|
|
79
|
+
const state = iteratorState.get(iteratorKey);
|
|
80
|
+
if (!state) {
|
|
81
|
+
throw new SolanaError(SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING);
|
|
82
|
+
}
|
|
83
|
+
if (state.__hasPolled) {
|
|
84
|
+
throw new SolanaError(
|
|
85
|
+
SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const publishQueue = state.publishQueue;
|
|
89
|
+
try {
|
|
90
|
+
if (publishQueue.length) {
|
|
91
|
+
state.publishQueue = [];
|
|
92
|
+
for (const item of publishQueue) {
|
|
93
|
+
if (item.__type === 0 /* DATA */) {
|
|
94
|
+
yield item.data;
|
|
95
|
+
} else {
|
|
96
|
+
throw item.err;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
yield await new Promise((resolve, reject) => {
|
|
101
|
+
iteratorState.set(iteratorKey, {
|
|
102
|
+
__hasPolled: true,
|
|
103
|
+
onData: resolve,
|
|
104
|
+
onError: reject
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
} catch (e) {
|
|
109
|
+
if (e === (EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken())) {
|
|
110
|
+
return;
|
|
111
|
+
} else {
|
|
112
|
+
throw e;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} finally {
|
|
117
|
+
iteratorState.delete(iteratorKey);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/data-publisher.ts
|
|
124
|
+
function getDataPublisherFromEventEmitter(eventEmitter) {
|
|
125
|
+
return {
|
|
126
|
+
on(channelName, subscriber, options) {
|
|
127
|
+
function innerListener(ev) {
|
|
128
|
+
if (ev instanceof CustomEvent) {
|
|
129
|
+
const data = ev.detail;
|
|
130
|
+
subscriber(data);
|
|
131
|
+
} else {
|
|
132
|
+
subscriber();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
eventEmitter.addEventListener(channelName, innerListener, options);
|
|
136
|
+
return () => {
|
|
137
|
+
eventEmitter.removeEventListener(channelName, innerListener);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/demultiplex.ts
|
|
144
|
+
function demultiplexDataPublisher(publisher, sourceChannelName, messageTransformer) {
|
|
145
|
+
let innerPublisherState;
|
|
146
|
+
const eventTarget = new EventTarget();
|
|
147
|
+
const demultiplexedDataPublisher = getDataPublisherFromEventEmitter(eventTarget);
|
|
148
|
+
return {
|
|
149
|
+
...demultiplexedDataPublisher,
|
|
150
|
+
on(channelName, subscriber, options) {
|
|
151
|
+
if (!innerPublisherState) {
|
|
152
|
+
const innerPublisherUnsubscribe = publisher.on(sourceChannelName, (sourceMessage) => {
|
|
153
|
+
const transformResult = messageTransformer(sourceMessage);
|
|
154
|
+
if (!transformResult) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const [destinationChannelName, message] = transformResult;
|
|
158
|
+
eventTarget.dispatchEvent(
|
|
159
|
+
new CustomEvent(destinationChannelName, {
|
|
160
|
+
detail: message
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
innerPublisherState = {
|
|
165
|
+
dispose: innerPublisherUnsubscribe,
|
|
166
|
+
numSubscribers: 0
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
innerPublisherState.numSubscribers++;
|
|
170
|
+
const unsubscribe = demultiplexedDataPublisher.on(channelName, subscriber, options);
|
|
171
|
+
let isActive = true;
|
|
172
|
+
function handleUnsubscribe() {
|
|
173
|
+
if (!isActive) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
isActive = false;
|
|
177
|
+
options?.signal.removeEventListener("abort", handleUnsubscribe);
|
|
178
|
+
innerPublisherState.numSubscribers--;
|
|
179
|
+
if (innerPublisherState.numSubscribers === 0) {
|
|
180
|
+
innerPublisherState.dispose();
|
|
181
|
+
innerPublisherState = void 0;
|
|
182
|
+
}
|
|
183
|
+
unsubscribe();
|
|
184
|
+
}
|
|
185
|
+
options?.signal.addEventListener("abort", handleUnsubscribe);
|
|
186
|
+
return handleUnsubscribe;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export { createAsyncIterableFromDataPublisher, demultiplexDataPublisher, getDataPublisherFromEventEmitter };
|
|
192
|
+
//# sourceMappingURL=index.browser.mjs.map
|
|
193
|
+
//# sourceMappingURL=index.browser.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/async-iterable.ts","../src/data-publisher.ts","../src/demultiplex.ts"],"names":[],"mappings":"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","file":"index.browser.mjs","sourcesContent":["import {\n SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE,\n SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING,\n SolanaError,\n} from '@solana/errors';\n\nimport { DataPublisher } from './data-publisher';\n\ntype Config = Readonly<{\n abortSignal: AbortSignal;\n dataChannelName: string;\n // FIXME: It would be nice to be able to constrain the type of `dataPublisher` to one that\n // definitely supports the `dataChannelName` and `errorChannelName` channels, and\n // furthermore publishes `TData` on the `dataChannelName` channel. This is more difficult\n // than it should be: https://tsplay.dev/NlZelW\n dataPublisher: DataPublisher;\n errorChannelName: string;\n}>;\n\nconst enum PublishType {\n DATA,\n ERROR,\n}\n\ntype IteratorKey = symbol;\ntype IteratorState<TData> =\n | {\n __hasPolled: false;\n publishQueue: (\n | {\n __type: PublishType.DATA;\n data: TData;\n }\n | {\n __type: PublishType.ERROR;\n err: unknown;\n }\n )[];\n }\n | {\n __hasPolled: true;\n onData: (data: TData) => void;\n onError: Parameters<ConstructorParameters<typeof Promise>[0]>[1];\n };\n\nlet EXPLICIT_ABORT_TOKEN: symbol;\nfunction createExplicitAbortToken() {\n // This function is an annoying workaround to prevent `process.env.NODE_ENV` from appearing at\n // the top level of this module and thwarting an optimizing compiler's attempt to tree-shake.\n return Symbol(\n process.env.NODE_ENV !== \"production\"\n ? \"This symbol is thrown from a socket's iterator when the connection is explicitly \" +\n 'aborted by the user'\n : undefined,\n );\n}\n\nconst UNINITIALIZED = Symbol();\n\nexport function createAsyncIterableFromDataPublisher<TData>({\n abortSignal,\n dataChannelName,\n dataPublisher,\n errorChannelName,\n}: Config): AsyncIterable<TData> {\n const iteratorState: Map<IteratorKey, IteratorState<TData>> = new Map();\n function publishErrorToAllIterators(reason: unknown) {\n for (const [iteratorKey, state] of iteratorState.entries()) {\n if (state.__hasPolled) {\n iteratorState.delete(iteratorKey);\n state.onError(reason);\n } else {\n state.publishQueue.push({\n __type: PublishType.ERROR,\n err: reason,\n });\n }\n }\n }\n const abortController = new AbortController();\n abortSignal.addEventListener('abort', () => {\n abortController.abort();\n publishErrorToAllIterators((EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken()));\n });\n const options = { signal: abortController.signal } as const;\n let firstError: unknown | typeof UNINITIALIZED = UNINITIALIZED;\n dataPublisher.on(\n errorChannelName,\n err => {\n if (firstError === UNINITIALIZED) {\n firstError = err;\n abortController.abort();\n publishErrorToAllIterators(err);\n }\n },\n options,\n );\n dataPublisher.on(\n dataChannelName,\n data => {\n iteratorState.forEach((state, iteratorKey) => {\n if (state.__hasPolled) {\n const { onData } = state;\n iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] });\n onData(data as TData);\n } else {\n state.publishQueue.push({\n __type: PublishType.DATA,\n data: data as TData,\n });\n }\n });\n },\n options,\n );\n return {\n async *[Symbol.asyncIterator]() {\n if (abortSignal.aborted) {\n return;\n }\n if (firstError !== UNINITIALIZED) {\n throw firstError;\n }\n const iteratorKey = Symbol();\n iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] });\n try {\n while (true) {\n const state = iteratorState.get(iteratorKey);\n if (!state) {\n // There should always be state by now.\n throw new SolanaError(SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING);\n }\n if (state.__hasPolled) {\n // You should never be able to poll twice in a row.\n throw new SolanaError(\n SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE,\n );\n }\n const publishQueue = state.publishQueue;\n try {\n if (publishQueue.length) {\n state.publishQueue = [];\n for (const item of publishQueue) {\n if (item.__type === PublishType.DATA) {\n yield item.data;\n } else {\n throw item.err;\n }\n }\n } else {\n yield await new Promise<TData>((resolve, reject) => {\n iteratorState.set(iteratorKey, {\n __hasPolled: true,\n onData: resolve,\n onError: reject,\n });\n });\n }\n } catch (e) {\n if (e === (EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken())) {\n return;\n } else {\n throw e;\n }\n }\n }\n } finally {\n iteratorState.delete(iteratorKey);\n }\n },\n };\n}\n","import { TypedEventEmitter, TypedEventTarget } from './event-emitter';\n\ntype UnsubscribeFn = () => void;\n\nexport interface DataPublisher<TDataByChannelName extends Record<string, unknown> = Record<string, unknown>> {\n on<const TChannelName extends keyof TDataByChannelName>(\n channelName: TChannelName,\n subscriber: (data: TDataByChannelName[TChannelName]) => void,\n options?: { signal: AbortSignal },\n ): UnsubscribeFn;\n}\n\nexport function getDataPublisherFromEventEmitter<TEventMap extends Record<string, Event>>(\n eventEmitter: TypedEventEmitter<TEventMap> | TypedEventTarget<TEventMap>,\n): DataPublisher<{\n [TEventType in keyof TEventMap]: TEventMap[TEventType] extends CustomEvent ? TEventMap[TEventType]['detail'] : null;\n}> {\n return {\n on(channelName, subscriber, options) {\n function innerListener(ev: Event) {\n if (ev instanceof CustomEvent) {\n const data = (ev as CustomEvent<TEventMap[typeof channelName]>).detail;\n (subscriber as unknown as (data: TEventMap[typeof channelName]) => void)(data);\n } else {\n (subscriber as () => void)();\n }\n }\n eventEmitter.addEventListener(channelName, innerListener, options);\n return () => {\n eventEmitter.removeEventListener(channelName, innerListener);\n };\n },\n };\n}\n","import { DataPublisher, getDataPublisherFromEventEmitter } from './data-publisher';\n\nexport function demultiplexDataPublisher<\n TDataPublisher extends DataPublisher,\n const TChannelName extends Parameters<TDataPublisher['on']>[0],\n>(\n publisher: TDataPublisher,\n sourceChannelName: TChannelName,\n messageTransformer: (\n // FIXME: Deriving the type of the message from `TDataPublisher` and `TChannelName` would\n // help callers to constrain their transform functions.\n message: unknown,\n ) => [destinationChannelName: string, message: unknown] | void,\n): DataPublisher {\n let innerPublisherState:\n | {\n readonly dispose: () => void;\n numSubscribers: number;\n }\n | undefined;\n const eventTarget = new EventTarget();\n const demultiplexedDataPublisher = getDataPublisherFromEventEmitter(eventTarget);\n return {\n ...demultiplexedDataPublisher,\n on(channelName, subscriber, options) {\n if (!innerPublisherState) {\n const innerPublisherUnsubscribe = publisher.on(sourceChannelName, sourceMessage => {\n const transformResult = messageTransformer(sourceMessage);\n if (!transformResult) {\n return;\n }\n const [destinationChannelName, message] = transformResult;\n eventTarget.dispatchEvent(\n new CustomEvent(destinationChannelName, {\n detail: message,\n }),\n );\n });\n innerPublisherState = {\n dispose: innerPublisherUnsubscribe,\n numSubscribers: 0,\n };\n }\n innerPublisherState.numSubscribers++;\n const unsubscribe = demultiplexedDataPublisher.on(channelName, subscriber, options);\n let isActive = true;\n function handleUnsubscribe() {\n if (!isActive) {\n return;\n }\n isActive = false;\n options?.signal.removeEventListener('abort', handleUnsubscribe);\n innerPublisherState!.numSubscribers--;\n if (innerPublisherState!.numSubscribers === 0) {\n innerPublisherState!.dispose();\n innerPublisherState = undefined;\n }\n unsubscribe();\n }\n options?.signal.addEventListener('abort', handleUnsubscribe);\n return handleUnsubscribe;\n },\n };\n}\n"]}
|