@adonisjs/transmit-client 0.2.2 → 1.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.
- package/LICENSE.md +9 -9
- package/README.md +113 -13
- package/build/index.d.ts +24 -22
- package/build/index.js +72 -63
- package/package.json +40 -22
- package/src/subscription.ts +35 -4
- package/src/transmit.ts +13 -7
package/LICENSE.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# The MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2023, Romain Lanz, AdonisJS Core Team, contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
-
|
|
7
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
-
|
|
9
|
-
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
1
|
+
# The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, Romain Lanz, AdonisJS Core Team, contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[![
|
|
10
|
+
[![typescript-image]][typescript-url]
|
|
11
|
+
[![gh-workflow-image]][gh-workflow-url]
|
|
12
|
+
[![npm-image]][npm-url]
|
|
13
|
+
[![npm-download-image]][npm-download-url]
|
|
14
|
+
[![license-image]][license-url]
|
|
11
15
|
|
|
12
16
|
</div>
|
|
13
17
|
|
|
@@ -38,6 +42,8 @@ AdonisJS Transmit Client is a client for the native Server-Sent-Event (SSE) modu
|
|
|
38
42
|
- [Creating a subscription](#creating-a-subscription)
|
|
39
43
|
- [Unsubscribing](#unsubscribing)
|
|
40
44
|
- [Subscription Request](#subscription-request)
|
|
45
|
+
- [Authenticated event stream](#authenticated-event-stream)
|
|
46
|
+
- [Custom UID Generator](#custom-uid-generator)
|
|
41
47
|
- [Reconnecting](#reconnecting)
|
|
42
48
|
- [Events](#events)
|
|
43
49
|
|
|
@@ -130,6 +136,93 @@ const transmit = new Transmit({
|
|
|
130
136
|
})
|
|
131
137
|
```
|
|
132
138
|
|
|
139
|
+
### Authenticated event stream
|
|
140
|
+
|
|
141
|
+
The `__transmit/events` stream is opened using `EventSource`, which cannot send custom headers. That means `beforeSubscribe`/`beforeUnsubscribe` only affect the subscribe/unsubscribe HTTP calls. If you rely on header-based auth, protect `__transmit/subscribe` and `__transmit/unsubscribe`, or provide a custom `eventSourceFactory` that can send headers.
|
|
142
|
+
|
|
143
|
+
Example using `@microsoft/fetch-event-source`:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
|
147
|
+
|
|
148
|
+
function createFetchEventSource(
|
|
149
|
+
url: string | URL,
|
|
150
|
+
options: { withCredentials: boolean },
|
|
151
|
+
headers: Record<string, string>
|
|
152
|
+
) {
|
|
153
|
+
const controller = new AbortController()
|
|
154
|
+
const listeners = new Map<string, Set<(event: MessageEvent) => void>>()
|
|
155
|
+
|
|
156
|
+
const dispatch = (type: string, data?: string) => {
|
|
157
|
+
const event = new MessageEvent(type, { data })
|
|
158
|
+
listeners.get(type)?.forEach((listener) => listener(event))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fetchEventSource(url.toString(), {
|
|
162
|
+
headers,
|
|
163
|
+
credentials: options.withCredentials ? 'include' : 'omit',
|
|
164
|
+
signal: controller.signal,
|
|
165
|
+
onopen: () => dispatch('open'),
|
|
166
|
+
onmessage: (message) => dispatch(message.event ?? 'message', message.data),
|
|
167
|
+
onerror: () => {
|
|
168
|
+
dispatch('error')
|
|
169
|
+
},
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
addEventListener(type: string, listener: (event: MessageEvent) => void) {
|
|
174
|
+
if (!listeners.has(type)) {
|
|
175
|
+
listeners.set(type, new Set())
|
|
176
|
+
}
|
|
177
|
+
listeners.get(type)!.add(listener)
|
|
178
|
+
},
|
|
179
|
+
removeEventListener(type: string, listener: (event: MessageEvent) => void) {
|
|
180
|
+
listeners.get(type)?.delete(listener)
|
|
181
|
+
},
|
|
182
|
+
close() {
|
|
183
|
+
controller.abort()
|
|
184
|
+
},
|
|
185
|
+
} as EventSource
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const transmit = new Transmit({
|
|
189
|
+
baseUrl: 'http://localhost:3333',
|
|
190
|
+
eventSourceFactory: (url, options) => {
|
|
191
|
+
return createFetchEventSource(url, options, {
|
|
192
|
+
Authorization: `Bearer ${token}`,
|
|
193
|
+
})
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Note: this adapter is minimal and only wires `open`, `error`, and `message` (or custom event names). If you rely on other `EventSource` features like `readyState` or `onopen`, you may want to expand it.
|
|
199
|
+
|
|
200
|
+
### Custom UID Generator
|
|
201
|
+
|
|
202
|
+
By default, Transmit uses `crypto.randomUUID()` to generate unique client identifiers. This method only works in [secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) (HTTPS). If you need to use Transmit over HTTP (e.g., local network deployments), you can provide a custom `uidGenerator` function.
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
const transmit = new Transmit({
|
|
206
|
+
baseUrl: 'http://localhost:3333',
|
|
207
|
+
uidGenerator: () => {
|
|
208
|
+
return Array.from({ length: 16 }, () =>
|
|
209
|
+
Math.floor(Math.random() * 256).toString(16).padStart(2, '0')
|
|
210
|
+
).join('')
|
|
211
|
+
},
|
|
212
|
+
})
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Or using a library like `uuid`:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import { v4 as uuid } from 'uuid'
|
|
219
|
+
|
|
220
|
+
const transmit = new Transmit({
|
|
221
|
+
baseUrl: 'http://localhost:3333',
|
|
222
|
+
uidGenerator: () => uuid(),
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
133
226
|
### Reconnecting
|
|
134
227
|
|
|
135
228
|
The transmit client will automatically reconnect to the server when the connection is lost. You can change the number of retries and hook into the reconnect lifecycle as follows:
|
|
@@ -149,9 +242,9 @@ const transmit = new Transmit({
|
|
|
149
242
|
|
|
150
243
|
# Events
|
|
151
244
|
|
|
152
|
-
The`Transmit` class uses the [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) class to emits multiple events.
|
|
245
|
+
The `Transmit` class uses the [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) class to emits multiple events.
|
|
153
246
|
|
|
154
|
-
|
|
247
|
+
```ts
|
|
155
248
|
transmit.on('connected', () => {
|
|
156
249
|
console.log('connected')
|
|
157
250
|
})
|
|
@@ -165,17 +258,24 @@ transmit.on('reconnecting', () => {
|
|
|
165
258
|
})
|
|
166
259
|
```
|
|
167
260
|
|
|
168
|
-
|
|
169
|
-
[gh-workflow-url]: https://github.com/adonisjs/transmit-client/actions/workflows/test.yml "Github action"
|
|
261
|
+
That means you can also remove an event listener previously registered, by passing the event listener function itself.
|
|
170
262
|
|
|
171
|
-
|
|
172
|
-
|
|
263
|
+
```ts
|
|
264
|
+
const onConnected = () => {
|
|
265
|
+
console.log('connected')
|
|
266
|
+
}
|
|
173
267
|
|
|
174
|
-
|
|
175
|
-
|
|
268
|
+
transmit.on('connected', onConnected)
|
|
269
|
+
transmit.off('connected', onConnected)
|
|
270
|
+
```
|
|
176
271
|
|
|
272
|
+
[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/transmit-client/checks.yml?branch=develop&style=for-the-badge
|
|
273
|
+
[gh-workflow-url]: https://github.com/adonisjs/transmit-client/actions/workflows/checks.yml
|
|
274
|
+
[npm-image]: https://img.shields.io/npm/v/@adonisjs/transmit-client.svg?style=for-the-badge&logo=npm
|
|
275
|
+
[npm-url]: https://www.npmjs.com/package/@adonisjs/transmit-client
|
|
276
|
+
[npm-download-image]: https://img.shields.io/npm/dm/@adonisjs/transmit-client?style=for-the-badge
|
|
277
|
+
[npm-download-url]: https://www.npmjs.com/package/@adonisjs/transmit-client
|
|
278
|
+
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
|
|
279
|
+
[typescript-url]: https://www.typescriptlang.org
|
|
177
280
|
[license-image]: https://img.shields.io/npm/l/@adonisjs/transmit-client?color=blueviolet&style=for-the-badge
|
|
178
|
-
[license-url]: LICENSE.md
|
|
179
|
-
|
|
180
|
-
[synk-image]: https://img.shields.io/snyk/vulnerabilities/github/adonisjs/transmit-client?label=Synk%20Vulnerabilities&style=for-the-badge
|
|
181
|
-
[synk-url]: https://snyk.io/test/github/adonisjs/transmit-client?targetFile=package.json "synk"
|
|
281
|
+
[license-url]: LICENSE.md
|
package/build/index.d.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
createRequest(path: string, body: Record<string, unknown>): Request;
|
|
10
|
-
}
|
|
1
|
+
declare const TransmitStatus: {
|
|
2
|
+
readonly Initializing: "initializing";
|
|
3
|
+
readonly Connecting: "connecting";
|
|
4
|
+
readonly Connected: "connected";
|
|
5
|
+
readonly Disconnected: "disconnected";
|
|
6
|
+
readonly Reconnecting: "reconnecting";
|
|
7
|
+
};
|
|
8
|
+
type TransmitStatus = (typeof TransmitStatus)[keyof typeof TransmitStatus];
|
|
11
9
|
|
|
12
10
|
declare const HookEvent: {
|
|
13
11
|
readonly BeforeSubscribe: "beforeSubscribe";
|
|
@@ -32,20 +30,23 @@ declare class Hook {
|
|
|
32
30
|
onUnsubscription(channel: string): this;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
interface HttpClientOptions {
|
|
34
|
+
baseUrl: string;
|
|
35
|
+
uid: string;
|
|
36
|
+
}
|
|
37
|
+
declare class HttpClient {
|
|
38
|
+
#private;
|
|
39
|
+
constructor(options: HttpClientOptions);
|
|
40
|
+
send(request: Request): Promise<Response>;
|
|
41
|
+
createRequest(path: string, body: Record<string, unknown>): Request;
|
|
42
|
+
}
|
|
43
43
|
|
|
44
44
|
interface SubscriptionOptions {
|
|
45
45
|
channel: string;
|
|
46
46
|
httpClient: HttpClient;
|
|
47
47
|
getEventSourceStatus: () => TransmitStatus;
|
|
48
48
|
hooks?: Hook;
|
|
49
|
+
onDelete?: () => void;
|
|
49
50
|
}
|
|
50
51
|
declare class Subscription {
|
|
51
52
|
#private;
|
|
@@ -66,8 +67,8 @@ declare class Subscription {
|
|
|
66
67
|
* Run all registered handlers for the subscription.
|
|
67
68
|
*/
|
|
68
69
|
$runHandler(message: unknown): void;
|
|
69
|
-
create(): Promise<
|
|
70
|
-
forceCreate(): Promise<
|
|
70
|
+
create(): Promise<void>;
|
|
71
|
+
forceCreate(): Promise<void>;
|
|
71
72
|
delete(): Promise<void>;
|
|
72
73
|
onMessage<T>(handler: (message: T) => void): () => void;
|
|
73
74
|
onMessageOnce<T>(handler: (message: T) => void): void;
|
|
@@ -81,8 +82,8 @@ interface TransmitOptions {
|
|
|
81
82
|
}) => EventSource;
|
|
82
83
|
eventTargetFactory?: () => EventTarget | null;
|
|
83
84
|
httpClientFactory?: (baseUrl: string, uid: string) => HttpClient;
|
|
84
|
-
beforeSubscribe?: (request:
|
|
85
|
-
beforeUnsubscribe?: (request:
|
|
85
|
+
beforeSubscribe?: (request: Request) => void;
|
|
86
|
+
beforeUnsubscribe?: (request: Request) => void;
|
|
86
87
|
maxReconnectAttempts?: number;
|
|
87
88
|
onReconnectAttempt?: (attempt: number) => void;
|
|
88
89
|
onReconnectFailed?: () => void;
|
|
@@ -99,6 +100,7 @@ declare class Transmit {
|
|
|
99
100
|
constructor(options: TransmitOptions);
|
|
100
101
|
subscription(channel: string): Subscription;
|
|
101
102
|
on(event: Exclude<TransmitStatus, 'connecting'>, callback: (event: CustomEvent) => void): void;
|
|
103
|
+
off(event: Exclude<TransmitStatus, 'connecting'>, callback: (event: CustomEvent) => void): void;
|
|
102
104
|
close(): void;
|
|
103
105
|
}
|
|
104
106
|
|
package/build/index.js
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
throw TypeError("Cannot " + msg);
|
|
4
|
-
};
|
|
5
|
-
var __privateGet = (obj, member, getter) => {
|
|
6
|
-
__accessCheck(obj, member, "read from private field");
|
|
7
|
-
return getter ? getter.call(obj) : member.get(obj);
|
|
8
|
-
};
|
|
9
|
-
var __privateAdd = (obj, member, value) => {
|
|
10
|
-
if (member.has(obj))
|
|
11
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
12
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
13
|
-
};
|
|
14
|
-
var __privateSet = (obj, member, value, setter) => {
|
|
15
|
-
__accessCheck(obj, member, "write to private field");
|
|
16
|
-
setter ? setter.call(obj, value) : member.set(obj, value);
|
|
17
|
-
return value;
|
|
1
|
+
var __typeError = (msg) => {
|
|
2
|
+
throw TypeError(msg);
|
|
18
3
|
};
|
|
4
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
5
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
6
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
7
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
8
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
19
9
|
var __privateWrapper = (obj, member, setter, getter) => ({
|
|
20
10
|
set _(value) {
|
|
21
11
|
__privateSet(obj, member, value, setter);
|
|
@@ -24,10 +14,6 @@ var __privateWrapper = (obj, member, setter, getter) => ({
|
|
|
24
14
|
return __privateGet(obj, member, getter);
|
|
25
15
|
}
|
|
26
16
|
});
|
|
27
|
-
var __privateMethod = (obj, member, method) => {
|
|
28
|
-
__accessCheck(obj, member, "access private method");
|
|
29
|
-
return method;
|
|
30
|
-
};
|
|
31
17
|
|
|
32
18
|
// src/subscription_status.ts
|
|
33
19
|
var SubscriptionStatus = {
|
|
@@ -46,29 +32,37 @@ var TransmitStatus = {
|
|
|
46
32
|
};
|
|
47
33
|
|
|
48
34
|
// src/subscription.ts
|
|
49
|
-
var _httpClient, _hooks, _channel, _getEventSourceStatus, _handlers, _status;
|
|
35
|
+
var _httpClient, _hooks, _onDelete, _channel, _getEventSourceStatus, _handlers, _createPending, _status;
|
|
50
36
|
var Subscription = class {
|
|
51
37
|
constructor(options) {
|
|
52
38
|
/**
|
|
53
39
|
* HTTP client instance.
|
|
54
40
|
*/
|
|
55
|
-
__privateAdd(this, _httpClient
|
|
41
|
+
__privateAdd(this, _httpClient);
|
|
56
42
|
/**
|
|
57
43
|
* Hook instance.
|
|
58
44
|
*/
|
|
59
|
-
__privateAdd(this, _hooks
|
|
45
|
+
__privateAdd(this, _hooks);
|
|
46
|
+
/**
|
|
47
|
+
* Callback to call when the subscription is deleted.
|
|
48
|
+
*/
|
|
49
|
+
__privateAdd(this, _onDelete);
|
|
60
50
|
/**
|
|
61
51
|
* Channel name.
|
|
62
52
|
*/
|
|
63
|
-
__privateAdd(this, _channel
|
|
53
|
+
__privateAdd(this, _channel);
|
|
64
54
|
/**
|
|
65
55
|
* Event source status getter.
|
|
66
56
|
*/
|
|
67
|
-
__privateAdd(this, _getEventSourceStatus
|
|
57
|
+
__privateAdd(this, _getEventSourceStatus);
|
|
68
58
|
/**
|
|
69
59
|
* Registered message handlers.
|
|
70
60
|
*/
|
|
71
61
|
__privateAdd(this, _handlers, /* @__PURE__ */ new Set());
|
|
62
|
+
/**
|
|
63
|
+
* Pending create retry promise to avoid stacking timeouts.
|
|
64
|
+
*/
|
|
65
|
+
__privateAdd(this, _createPending, null);
|
|
72
66
|
/**
|
|
73
67
|
* Current status of the subscription.
|
|
74
68
|
*/
|
|
@@ -76,6 +70,7 @@ var Subscription = class {
|
|
|
76
70
|
__privateSet(this, _channel, options.channel);
|
|
77
71
|
__privateSet(this, _httpClient, options.httpClient);
|
|
78
72
|
__privateSet(this, _hooks, options.hooks);
|
|
73
|
+
__privateSet(this, _onDelete, options.onDelete);
|
|
79
74
|
__privateSet(this, _getEventSourceStatus, options.getEventSourceStatus);
|
|
80
75
|
}
|
|
81
76
|
/**
|
|
@@ -101,23 +96,36 @@ var Subscription = class {
|
|
|
101
96
|
*/
|
|
102
97
|
$runHandler(message) {
|
|
103
98
|
for (const handler of __privateGet(this, _handlers)) {
|
|
104
|
-
|
|
99
|
+
try {
|
|
100
|
+
handler(message);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(error);
|
|
103
|
+
}
|
|
105
104
|
}
|
|
106
105
|
}
|
|
107
106
|
async create() {
|
|
108
107
|
if (this.isCreated) {
|
|
109
108
|
return;
|
|
110
109
|
}
|
|
110
|
+
if (__privateGet(this, _getEventSourceStatus).call(this) !== TransmitStatus.Connected && __privateGet(this, _createPending)) {
|
|
111
|
+
return __privateGet(this, _createPending);
|
|
112
|
+
}
|
|
111
113
|
return this.forceCreate();
|
|
112
114
|
}
|
|
113
115
|
async forceCreate() {
|
|
114
116
|
if (__privateGet(this, _getEventSourceStatus).call(this) !== TransmitStatus.Connected) {
|
|
115
|
-
|
|
117
|
+
if (__privateGet(this, _createPending)) {
|
|
118
|
+
return __privateGet(this, _createPending);
|
|
119
|
+
}
|
|
120
|
+
__privateSet(this, _createPending, new Promise((resolve) => {
|
|
116
121
|
setTimeout(() => {
|
|
122
|
+
__privateSet(this, _createPending, null);
|
|
117
123
|
resolve(this.create());
|
|
118
124
|
}, 100);
|
|
119
|
-
});
|
|
125
|
+
}));
|
|
126
|
+
return __privateGet(this, _createPending);
|
|
120
127
|
}
|
|
128
|
+
__privateSet(this, _createPending, null);
|
|
121
129
|
const request = __privateGet(this, _httpClient).createRequest("/__transmit/subscribe", {
|
|
122
130
|
channel: __privateGet(this, _channel)
|
|
123
131
|
});
|
|
@@ -135,6 +143,7 @@ var Subscription = class {
|
|
|
135
143
|
}
|
|
136
144
|
}
|
|
137
145
|
async delete() {
|
|
146
|
+
var _a;
|
|
138
147
|
if (this.isDeleted || !this.isCreated) {
|
|
139
148
|
return;
|
|
140
149
|
}
|
|
@@ -150,6 +159,7 @@ var Subscription = class {
|
|
|
150
159
|
}
|
|
151
160
|
__privateSet(this, _status, SubscriptionStatus.Deleted);
|
|
152
161
|
__privateGet(this, _hooks)?.onUnsubscription(__privateGet(this, _channel));
|
|
162
|
+
(_a = __privateGet(this, _onDelete)) == null ? void 0 : _a.call(this);
|
|
153
163
|
} catch (error) {
|
|
154
164
|
}
|
|
155
165
|
}
|
|
@@ -168,17 +178,19 @@ var Subscription = class {
|
|
|
168
178
|
};
|
|
169
179
|
_httpClient = new WeakMap();
|
|
170
180
|
_hooks = new WeakMap();
|
|
181
|
+
_onDelete = new WeakMap();
|
|
171
182
|
_channel = new WeakMap();
|
|
172
183
|
_getEventSourceStatus = new WeakMap();
|
|
173
184
|
_handlers = new WeakMap();
|
|
185
|
+
_createPending = new WeakMap();
|
|
174
186
|
_status = new WeakMap();
|
|
175
187
|
|
|
176
188
|
// src/http_client.ts
|
|
177
|
-
var _options,
|
|
189
|
+
var _options, _HttpClient_instances, retrieveXsrfToken_fn;
|
|
178
190
|
var HttpClient = class {
|
|
179
191
|
constructor(options) {
|
|
180
|
-
__privateAdd(this,
|
|
181
|
-
__privateAdd(this, _options
|
|
192
|
+
__privateAdd(this, _HttpClient_instances);
|
|
193
|
+
__privateAdd(this, _options);
|
|
182
194
|
__privateSet(this, _options, options);
|
|
183
195
|
}
|
|
184
196
|
send(request) {
|
|
@@ -189,7 +201,7 @@ var HttpClient = class {
|
|
|
189
201
|
method: "POST",
|
|
190
202
|
headers: {
|
|
191
203
|
"Content-Type": "application/json",
|
|
192
|
-
"X-XSRF-TOKEN": __privateMethod(this,
|
|
204
|
+
"X-XSRF-TOKEN": __privateMethod(this, _HttpClient_instances, retrieveXsrfToken_fn).call(this) ?? ""
|
|
193
205
|
},
|
|
194
206
|
body: JSON.stringify({ uid: __privateGet(this, _options).uid, ...body }),
|
|
195
207
|
credentials: "include"
|
|
@@ -197,10 +209,9 @@ var HttpClient = class {
|
|
|
197
209
|
}
|
|
198
210
|
};
|
|
199
211
|
_options = new WeakMap();
|
|
200
|
-
|
|
212
|
+
_HttpClient_instances = new WeakSet();
|
|
201
213
|
retrieveXsrfToken_fn = function() {
|
|
202
|
-
if (typeof document === "undefined")
|
|
203
|
-
return null;
|
|
214
|
+
if (typeof document === "undefined") return null;
|
|
204
215
|
const match = document.cookie.match(new RegExp("(^|;\\s*)(XSRF-TOKEN)=([^;]*)"));
|
|
205
216
|
return match ? decodeURIComponent(match[3]) : null;
|
|
206
217
|
};
|
|
@@ -261,21 +272,18 @@ var Hook = class {
|
|
|
261
272
|
_handlers2 = new WeakMap();
|
|
262
273
|
|
|
263
274
|
// src/transmit.ts
|
|
264
|
-
var _uid, _options2, _subscriptions, _httpClient2, _hooks2, _status2, _eventSource, _eventTarget, _reconnectAttempts,
|
|
275
|
+
var _uid, _options2, _subscriptions, _httpClient2, _hooks2, _status2, _eventSource, _eventTarget, _reconnectAttempts, _Transmit_instances, changeStatus_fn, connect_fn, onMessage_fn, onError_fn;
|
|
265
276
|
var Transmit = class {
|
|
266
277
|
constructor(options) {
|
|
267
|
-
__privateAdd(this,
|
|
268
|
-
__privateAdd(this, _connect);
|
|
269
|
-
__privateAdd(this, _onMessage);
|
|
270
|
-
__privateAdd(this, _onError);
|
|
278
|
+
__privateAdd(this, _Transmit_instances);
|
|
271
279
|
/**
|
|
272
280
|
* Unique identifier for this client.
|
|
273
281
|
*/
|
|
274
|
-
__privateAdd(this, _uid
|
|
282
|
+
__privateAdd(this, _uid);
|
|
275
283
|
/**
|
|
276
284
|
* Options for this client.
|
|
277
285
|
*/
|
|
278
|
-
__privateAdd(this, _options2
|
|
286
|
+
__privateAdd(this, _options2);
|
|
279
287
|
/**
|
|
280
288
|
* Registered subscriptions.
|
|
281
289
|
*/
|
|
@@ -283,11 +291,11 @@ var Transmit = class {
|
|
|
283
291
|
/**
|
|
284
292
|
* HTTP client instance.
|
|
285
293
|
*/
|
|
286
|
-
__privateAdd(this, _httpClient2
|
|
294
|
+
__privateAdd(this, _httpClient2);
|
|
287
295
|
/**
|
|
288
296
|
* Hook instance.
|
|
289
297
|
*/
|
|
290
|
-
__privateAdd(this, _hooks2
|
|
298
|
+
__privateAdd(this, _hooks2);
|
|
291
299
|
/**
|
|
292
300
|
* Current status of the client.
|
|
293
301
|
*/
|
|
@@ -295,11 +303,11 @@ var Transmit = class {
|
|
|
295
303
|
/**
|
|
296
304
|
* EventSource instance.
|
|
297
305
|
*/
|
|
298
|
-
__privateAdd(this, _eventSource
|
|
306
|
+
__privateAdd(this, _eventSource);
|
|
299
307
|
/**
|
|
300
308
|
* EventTarget instance.
|
|
301
309
|
*/
|
|
302
|
-
__privateAdd(this, _eventTarget
|
|
310
|
+
__privateAdd(this, _eventTarget);
|
|
303
311
|
/**
|
|
304
312
|
* Number of reconnect attempts.
|
|
305
313
|
*/
|
|
@@ -345,7 +353,7 @@ var Transmit = class {
|
|
|
345
353
|
__privateGet(this, _hooks2).register(HookEvent.OnUnsubscription, options.onUnsubscription);
|
|
346
354
|
}
|
|
347
355
|
__privateSet(this, _options2, options);
|
|
348
|
-
__privateMethod(this,
|
|
356
|
+
__privateMethod(this, _Transmit_instances, connect_fn).call(this);
|
|
349
357
|
}
|
|
350
358
|
/**
|
|
351
359
|
* Returns the unique identifier of the client.
|
|
@@ -354,21 +362,25 @@ var Transmit = class {
|
|
|
354
362
|
return __privateGet(this, _uid);
|
|
355
363
|
}
|
|
356
364
|
subscription(channel) {
|
|
365
|
+
if (__privateGet(this, _subscriptions).has(channel)) {
|
|
366
|
+
return __privateGet(this, _subscriptions).get(channel);
|
|
367
|
+
}
|
|
357
368
|
const subscription = new Subscription({
|
|
358
369
|
channel,
|
|
359
370
|
httpClient: __privateGet(this, _httpClient2),
|
|
360
371
|
hooks: __privateGet(this, _hooks2),
|
|
361
|
-
getEventSourceStatus: () => __privateGet(this, _status2)
|
|
372
|
+
getEventSourceStatus: () => __privateGet(this, _status2),
|
|
373
|
+
onDelete: () => __privateGet(this, _subscriptions).delete(channel)
|
|
362
374
|
});
|
|
363
|
-
if (__privateGet(this, _subscriptions).has(channel)) {
|
|
364
|
-
return __privateGet(this, _subscriptions).get(channel);
|
|
365
|
-
}
|
|
366
375
|
__privateGet(this, _subscriptions).set(channel, subscription);
|
|
367
376
|
return subscription;
|
|
368
377
|
}
|
|
369
378
|
on(event, callback) {
|
|
370
379
|
__privateGet(this, _eventTarget)?.addEventListener(event, callback);
|
|
371
380
|
}
|
|
381
|
+
off(event, callback) {
|
|
382
|
+
__privateGet(this, _eventTarget)?.removeEventListener(event, callback);
|
|
383
|
+
}
|
|
372
384
|
close() {
|
|
373
385
|
__privateGet(this, _eventSource)?.close();
|
|
374
386
|
}
|
|
@@ -382,23 +394,22 @@ _status2 = new WeakMap();
|
|
|
382
394
|
_eventSource = new WeakMap();
|
|
383
395
|
_eventTarget = new WeakMap();
|
|
384
396
|
_reconnectAttempts = new WeakMap();
|
|
385
|
-
|
|
397
|
+
_Transmit_instances = new WeakSet();
|
|
386
398
|
changeStatus_fn = function(status) {
|
|
387
399
|
__privateSet(this, _status2, status);
|
|
388
400
|
__privateGet(this, _eventTarget)?.dispatchEvent(new CustomEvent(status));
|
|
389
401
|
};
|
|
390
|
-
_connect = new WeakSet();
|
|
391
402
|
connect_fn = function() {
|
|
392
|
-
__privateMethod(this,
|
|
403
|
+
__privateMethod(this, _Transmit_instances, changeStatus_fn).call(this, TransmitStatus.Connecting);
|
|
393
404
|
const url = new URL(`${__privateGet(this, _options2).baseUrl}/__transmit/events`);
|
|
394
405
|
url.searchParams.append("uid", __privateGet(this, _uid));
|
|
395
406
|
__privateSet(this, _eventSource, __privateGet(this, _options2).eventSourceFactory(url, {
|
|
396
407
|
withCredentials: true
|
|
397
408
|
}));
|
|
398
|
-
__privateGet(this, _eventSource).addEventListener("message", __privateMethod(this,
|
|
399
|
-
__privateGet(this, _eventSource).addEventListener("error", __privateMethod(this,
|
|
409
|
+
__privateGet(this, _eventSource).addEventListener("message", __privateMethod(this, _Transmit_instances, onMessage_fn).bind(this));
|
|
410
|
+
__privateGet(this, _eventSource).addEventListener("error", __privateMethod(this, _Transmit_instances, onError_fn).bind(this));
|
|
400
411
|
__privateGet(this, _eventSource).addEventListener("open", () => {
|
|
401
|
-
__privateMethod(this,
|
|
412
|
+
__privateMethod(this, _Transmit_instances, changeStatus_fn).call(this, TransmitStatus.Connected);
|
|
402
413
|
__privateSet(this, _reconnectAttempts, 0);
|
|
403
414
|
for (const subscription of __privateGet(this, _subscriptions).values()) {
|
|
404
415
|
if (subscription.isCreated) {
|
|
@@ -407,7 +418,6 @@ connect_fn = function() {
|
|
|
407
418
|
}
|
|
408
419
|
});
|
|
409
420
|
};
|
|
410
|
-
_onMessage = new WeakSet();
|
|
411
421
|
onMessage_fn = function(event) {
|
|
412
422
|
const data = JSON.parse(event.data);
|
|
413
423
|
const subscription = __privateGet(this, _subscriptions).get(data.channel);
|
|
@@ -417,15 +427,14 @@ onMessage_fn = function(event) {
|
|
|
417
427
|
try {
|
|
418
428
|
subscription.$runHandler(data.payload);
|
|
419
429
|
} catch (error) {
|
|
420
|
-
console.
|
|
430
|
+
console.error(error);
|
|
421
431
|
}
|
|
422
432
|
};
|
|
423
|
-
_onError = new WeakSet();
|
|
424
433
|
onError_fn = function() {
|
|
425
434
|
if (__privateGet(this, _status2) !== TransmitStatus.Reconnecting) {
|
|
426
|
-
__privateMethod(this,
|
|
435
|
+
__privateMethod(this, _Transmit_instances, changeStatus_fn).call(this, TransmitStatus.Disconnected);
|
|
427
436
|
}
|
|
428
|
-
__privateMethod(this,
|
|
437
|
+
__privateMethod(this, _Transmit_instances, changeStatus_fn).call(this, TransmitStatus.Reconnecting);
|
|
429
438
|
__privateGet(this, _hooks2).onReconnectAttempt(__privateGet(this, _reconnectAttempts) + 1);
|
|
430
439
|
if (__privateGet(this, _options2).maxReconnectAttempts && __privateGet(this, _reconnectAttempts) >= __privateGet(this, _options2).maxReconnectAttempts) {
|
|
431
440
|
__privateGet(this, _eventSource).close();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonisjs/transmit-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A client for the native Server-Sent-Event module of AdonisJS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"client",
|
|
@@ -29,20 +29,21 @@
|
|
|
29
29
|
"test": "c8 node --loader ts-node/esm --enable-source-maps bin/test.ts"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@adonisjs/eslint-config": "^
|
|
33
|
-
"@adonisjs/prettier-config": "^1.
|
|
34
|
-
"@adonisjs/tsconfig": "^1.
|
|
35
|
-
"@japa/assert": "^2.0
|
|
36
|
-
"@japa/runner": "^3.0
|
|
37
|
-
"@
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
32
|
+
"@adonisjs/eslint-config": "^3.0.0-next.9",
|
|
33
|
+
"@adonisjs/prettier-config": "^1.4.5",
|
|
34
|
+
"@adonisjs/tsconfig": "^1.4.1",
|
|
35
|
+
"@japa/assert": "^4.2.0",
|
|
36
|
+
"@japa/runner": "^5.3.0",
|
|
37
|
+
"@release-it/conventional-changelog": "^10.0.4",
|
|
38
|
+
"@swc/core": "^1.15.8",
|
|
39
|
+
"c8": "^10.1.3",
|
|
40
|
+
"del-cli": "^7.0.0",
|
|
41
|
+
"eslint": "^9.39.2",
|
|
42
|
+
"prettier": "^3.8.0",
|
|
43
|
+
"release-it": "^19.2.3",
|
|
43
44
|
"ts-node": "^10.9.2",
|
|
44
|
-
"tsup": "^8.
|
|
45
|
-
"typescript": "^5.
|
|
45
|
+
"tsup": "^8.5.1",
|
|
46
|
+
"typescript": "^5.9.3"
|
|
46
47
|
},
|
|
47
48
|
"files": [
|
|
48
49
|
"src",
|
|
@@ -51,13 +52,18 @@
|
|
|
51
52
|
"engines": {
|
|
52
53
|
"node": ">=18.16.0"
|
|
53
54
|
},
|
|
54
|
-
"eslintConfig": {
|
|
55
|
-
"extends": "@adonisjs/eslint-config/package"
|
|
56
|
-
},
|
|
57
55
|
"prettier": "@adonisjs/prettier-config",
|
|
58
56
|
"publishConfig": {
|
|
59
|
-
"
|
|
60
|
-
"
|
|
57
|
+
"provenance": true,
|
|
58
|
+
"access": "public"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/adonisjs/transmit-client#readme",
|
|
61
|
+
"repository": {
|
|
62
|
+
"type": "git",
|
|
63
|
+
"url": "git+https://github.com/adonisjs/transmit-client.git"
|
|
64
|
+
},
|
|
65
|
+
"bugs": {
|
|
66
|
+
"url": "https://github.com/adonisjs/transmit-client/issues"
|
|
61
67
|
},
|
|
62
68
|
"tsup": {
|
|
63
69
|
"dts": true,
|
|
@@ -72,14 +78,26 @@
|
|
|
72
78
|
},
|
|
73
79
|
"release-it": {
|
|
74
80
|
"git": {
|
|
81
|
+
"requireCleanWorkingDir": true,
|
|
82
|
+
"requireUpstream": true,
|
|
75
83
|
"commitMessage": "chore(release): ${version}",
|
|
76
84
|
"tagAnnotation": "v${version}",
|
|
85
|
+
"push": true,
|
|
77
86
|
"tagName": "v${version}"
|
|
78
87
|
},
|
|
79
88
|
"github": {
|
|
80
|
-
"release": true
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
"release": true
|
|
90
|
+
},
|
|
91
|
+
"npm": {
|
|
92
|
+
"publish": true,
|
|
93
|
+
"skipChecks": true
|
|
94
|
+
},
|
|
95
|
+
"plugins": {
|
|
96
|
+
"@release-it/conventional-changelog": {
|
|
97
|
+
"preset": {
|
|
98
|
+
"name": "angular"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
83
101
|
}
|
|
84
102
|
}
|
|
85
103
|
}
|
package/src/subscription.ts
CHANGED
|
@@ -8,15 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { SubscriptionStatus } from './subscription_status.js'
|
|
11
|
-
import { HttpClient } from './http_client.js'
|
|
12
|
-
import { Hook } from './hook.js'
|
|
13
11
|
import { TransmitStatus } from './transmit_status.js'
|
|
12
|
+
import type { Hook } from './hook.js'
|
|
13
|
+
import type { HttpClient } from './http_client.js'
|
|
14
14
|
|
|
15
15
|
interface SubscriptionOptions {
|
|
16
16
|
channel: string
|
|
17
17
|
httpClient: HttpClient
|
|
18
18
|
getEventSourceStatus: () => TransmitStatus
|
|
19
19
|
hooks?: Hook
|
|
20
|
+
onDelete?: () => void
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export class Subscription {
|
|
@@ -30,6 +31,11 @@ export class Subscription {
|
|
|
30
31
|
*/
|
|
31
32
|
#hooks: Hook | undefined
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Callback to call when the subscription is deleted.
|
|
36
|
+
*/
|
|
37
|
+
readonly #onDelete: (() => void) | undefined
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* Channel name.
|
|
35
41
|
*/
|
|
@@ -45,6 +51,11 @@ export class Subscription {
|
|
|
45
51
|
*/
|
|
46
52
|
#handlers = new Set<(message: any) => void>()
|
|
47
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Pending create retry promise to avoid stacking timeouts.
|
|
56
|
+
*/
|
|
57
|
+
#createPending: Promise<void> | null = null
|
|
58
|
+
|
|
48
59
|
/**
|
|
49
60
|
* Current status of the subscription.
|
|
50
61
|
*/
|
|
@@ -75,6 +86,7 @@ export class Subscription {
|
|
|
75
86
|
this.#channel = options.channel
|
|
76
87
|
this.#httpClient = options.httpClient
|
|
77
88
|
this.#hooks = options.hooks
|
|
89
|
+
this.#onDelete = options.onDelete
|
|
78
90
|
this.#getEventSourceStatus = options.getEventSourceStatus
|
|
79
91
|
}
|
|
80
92
|
|
|
@@ -83,7 +95,12 @@ export class Subscription {
|
|
|
83
95
|
*/
|
|
84
96
|
$runHandler(message: unknown) {
|
|
85
97
|
for (const handler of this.#handlers) {
|
|
86
|
-
|
|
98
|
+
try {
|
|
99
|
+
handler(message)
|
|
100
|
+
} catch (error) {
|
|
101
|
+
// TODO: Rescue
|
|
102
|
+
console.error(error)
|
|
103
|
+
}
|
|
87
104
|
}
|
|
88
105
|
}
|
|
89
106
|
|
|
@@ -92,18 +109,31 @@ export class Subscription {
|
|
|
92
109
|
return
|
|
93
110
|
}
|
|
94
111
|
|
|
112
|
+
if (this.#getEventSourceStatus() !== TransmitStatus.Connected && this.#createPending) {
|
|
113
|
+
return this.#createPending
|
|
114
|
+
}
|
|
115
|
+
|
|
95
116
|
return this.forceCreate()
|
|
96
117
|
}
|
|
97
118
|
|
|
98
119
|
async forceCreate() {
|
|
99
120
|
if (this.#getEventSourceStatus() !== TransmitStatus.Connected) {
|
|
100
|
-
|
|
121
|
+
if (this.#createPending) {
|
|
122
|
+
return this.#createPending
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.#createPending = new Promise((resolve) => {
|
|
101
126
|
setTimeout(() => {
|
|
127
|
+
this.#createPending = null
|
|
102
128
|
resolve(this.create())
|
|
103
129
|
}, 100)
|
|
104
130
|
})
|
|
131
|
+
|
|
132
|
+
return this.#createPending
|
|
105
133
|
}
|
|
106
134
|
|
|
135
|
+
this.#createPending = null
|
|
136
|
+
|
|
107
137
|
const request = this.#httpClient.createRequest('/__transmit/subscribe', {
|
|
108
138
|
channel: this.#channel,
|
|
109
139
|
})
|
|
@@ -149,6 +179,7 @@ export class Subscription {
|
|
|
149
179
|
|
|
150
180
|
this.#status = SubscriptionStatus.Deleted
|
|
151
181
|
this.#hooks?.onUnsubscription(this.#channel)
|
|
182
|
+
this.#onDelete?.()
|
|
152
183
|
} catch (error) {}
|
|
153
184
|
}
|
|
154
185
|
|
package/src/transmit.ts
CHANGED
|
@@ -19,8 +19,8 @@ interface TransmitOptions {
|
|
|
19
19
|
eventSourceFactory?: (url: string | URL, options: { withCredentials: boolean }) => EventSource
|
|
20
20
|
eventTargetFactory?: () => EventTarget | null
|
|
21
21
|
httpClientFactory?: (baseUrl: string, uid: string) => HttpClient
|
|
22
|
-
beforeSubscribe?: (request:
|
|
23
|
-
beforeUnsubscribe?: (request:
|
|
22
|
+
beforeSubscribe?: (request: Request) => void
|
|
23
|
+
beforeUnsubscribe?: (request: Request) => void
|
|
24
24
|
maxReconnectAttempts?: number
|
|
25
25
|
onReconnectAttempt?: (attempt: number) => void
|
|
26
26
|
onReconnectFailed?: () => void
|
|
@@ -181,7 +181,7 @@ export class Transmit {
|
|
|
181
181
|
subscription.$runHandler(data.payload)
|
|
182
182
|
} catch (error) {
|
|
183
183
|
// TODO: Rescue
|
|
184
|
-
console.
|
|
184
|
+
console.error(error)
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
@@ -209,17 +209,18 @@ export class Transmit {
|
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
subscription(channel: string) {
|
|
212
|
+
if (this.#subscriptions.has(channel)) {
|
|
213
|
+
return this.#subscriptions.get(channel)!
|
|
214
|
+
}
|
|
215
|
+
|
|
212
216
|
const subscription = new Subscription({
|
|
213
217
|
channel,
|
|
214
218
|
httpClient: this.#httpClient,
|
|
215
219
|
hooks: this.#hooks,
|
|
216
220
|
getEventSourceStatus: () => this.#status,
|
|
221
|
+
onDelete: () => this.#subscriptions.delete(channel),
|
|
217
222
|
})
|
|
218
223
|
|
|
219
|
-
if (this.#subscriptions.has(channel)) {
|
|
220
|
-
return this.#subscriptions.get(channel)!
|
|
221
|
-
}
|
|
222
|
-
|
|
223
224
|
this.#subscriptions.set(channel, subscription)
|
|
224
225
|
|
|
225
226
|
return subscription
|
|
@@ -230,6 +231,11 @@ export class Transmit {
|
|
|
230
231
|
this.#eventTarget?.addEventListener(event, callback)
|
|
231
232
|
}
|
|
232
233
|
|
|
234
|
+
off(event: Exclude<TransmitStatus, 'connecting'>, callback: (event: CustomEvent) => void) {
|
|
235
|
+
// @ts-ignore
|
|
236
|
+
this.#eventTarget?.removeEventListener(event, callback)
|
|
237
|
+
}
|
|
238
|
+
|
|
233
239
|
close() {
|
|
234
240
|
this.#eventSource?.close()
|
|
235
241
|
}
|