@nmtjs/client 0.15.2 → 0.16.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +64 -0
- package/dist/client.js +97 -0
- package/dist/client.js.map +1 -0
- package/dist/clients/runtime.d.ts +6 -12
- package/dist/clients/runtime.js +58 -57
- package/dist/clients/runtime.js.map +1 -1
- package/dist/clients/static.d.ts +4 -9
- package/dist/clients/static.js +20 -20
- package/dist/clients/static.js.map +1 -1
- package/dist/core.d.ts +33 -83
- package/dist/core.js +305 -690
- package/dist/core.js.map +1 -1
- package/dist/events.d.ts +0 -1
- package/dist/events.js +74 -11
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/layers/ping.d.ts +6 -0
- package/dist/layers/ping.js +65 -0
- package/dist/layers/ping.js.map +1 -0
- package/dist/layers/rpc.d.ts +19 -0
- package/dist/layers/rpc.js +521 -0
- package/dist/layers/rpc.js.map +1 -0
- package/dist/layers/streams.d.ts +20 -0
- package/dist/layers/streams.js +194 -0
- package/dist/layers/streams.js.map +1 -0
- package/dist/plugins/browser.js +28 -9
- package/dist/plugins/browser.js.map +1 -1
- package/dist/plugins/heartbeat.js +10 -10
- package/dist/plugins/heartbeat.js.map +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.js +0 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/reconnect.js +11 -94
- package/dist/plugins/reconnect.js.map +1 -1
- package/dist/plugins/types.d.ts +27 -11
- package/dist/transport.d.ts +49 -31
- package/dist/types.d.ts +21 -5
- package/package.json +10 -10
- package/src/client.ts +216 -0
- package/src/clients/runtime.ts +93 -79
- package/src/clients/static.ts +46 -38
- package/src/core.ts +394 -901
- package/src/events.ts +113 -14
- package/src/index.ts +4 -0
- package/src/layers/ping.ts +99 -0
- package/src/layers/rpc.ts +725 -0
- package/src/layers/streams.ts +277 -0
- package/src/plugins/browser.ts +39 -9
- package/src/plugins/heartbeat.ts +10 -10
- package/src/plugins/index.ts +8 -1
- package/src/plugins/reconnect.ts +12 -119
- package/src/plugins/types.ts +30 -13
- package/src/transport.ts +75 -46
- package/src/types.ts +33 -8
package/src/events.ts
CHANGED
|
@@ -2,6 +2,16 @@ import type { Callback } from '@nmtjs/common'
|
|
|
2
2
|
|
|
3
3
|
export type EventMap = { [K: string]: any[] }
|
|
4
4
|
|
|
5
|
+
type ListenerRegistration = {
|
|
6
|
+
abortHandler?: () => void
|
|
7
|
+
capture: boolean
|
|
8
|
+
disposed: boolean
|
|
9
|
+
event: string
|
|
10
|
+
listener: Callback
|
|
11
|
+
signal?: AbortSignal
|
|
12
|
+
wrapper: EventListener
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
// TODO: add errors and promise rejections handling
|
|
6
16
|
/**
|
|
7
17
|
* Thin node-like event emitter wrapper around EventTarget
|
|
@@ -13,25 +23,101 @@ export class EventEmitter<
|
|
|
13
23
|
string
|
|
14
24
|
>,
|
|
15
25
|
> {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
#target = new EventTarget()
|
|
27
|
+
#listeners = new Map<string, Map<Callback, Set<ListenerRegistration>>>()
|
|
28
|
+
|
|
29
|
+
#addRegistration(registration: ListenerRegistration) {
|
|
30
|
+
const events =
|
|
31
|
+
this.#listeners.get(registration.event) ??
|
|
32
|
+
new Map<Callback, Set<ListenerRegistration>>()
|
|
33
|
+
|
|
34
|
+
if (!this.#listeners.has(registration.event)) {
|
|
35
|
+
this.#listeners.set(registration.event, events)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const registrations =
|
|
39
|
+
events.get(registration.listener) ?? new Set<ListenerRegistration>()
|
|
40
|
+
|
|
41
|
+
if (!events.has(registration.listener)) {
|
|
42
|
+
events.set(registration.listener, registrations)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
registrations.add(registration)
|
|
21
46
|
}
|
|
22
47
|
|
|
23
|
-
#
|
|
24
|
-
|
|
48
|
+
#removeRegistration(registration: ListenerRegistration) {
|
|
49
|
+
if (registration.disposed) return
|
|
50
|
+
|
|
51
|
+
registration.disposed = true
|
|
52
|
+
this.#target.removeEventListener(
|
|
53
|
+
registration.event,
|
|
54
|
+
registration.wrapper,
|
|
55
|
+
registration.capture,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if (registration.signal && registration.abortHandler) {
|
|
59
|
+
registration.signal.removeEventListener(
|
|
60
|
+
'abort',
|
|
61
|
+
registration.abortHandler,
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const events = this.#listeners.get(registration.event)
|
|
66
|
+
const registrations = events?.get(registration.listener)
|
|
67
|
+
|
|
68
|
+
registrations?.delete(registration)
|
|
69
|
+
|
|
70
|
+
if (registrations?.size === 0) {
|
|
71
|
+
events?.delete(registration.listener)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (events?.size === 0) {
|
|
75
|
+
this.#listeners.delete(registration.event)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
25
78
|
|
|
26
79
|
on<E extends EventName>(
|
|
27
80
|
event: E | (Object & string),
|
|
28
81
|
listener: (...args: Events[E]) => void,
|
|
29
82
|
options?: AddEventListenerOptions,
|
|
30
83
|
) {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
84
|
+
const cleanup = () => {
|
|
85
|
+
this.#removeRegistration(registration)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const registration: ListenerRegistration = {
|
|
89
|
+
capture: !!options?.capture,
|
|
90
|
+
disposed: false,
|
|
91
|
+
event,
|
|
92
|
+
listener,
|
|
93
|
+
wrapper: (rawEvent) => {
|
|
94
|
+
try {
|
|
95
|
+
listener(...(rawEvent as CustomEvent<Events[E]>).detail)
|
|
96
|
+
} finally {
|
|
97
|
+
if (options?.once) {
|
|
98
|
+
cleanup()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (options?.signal) {
|
|
105
|
+
if (options.signal.aborted) {
|
|
106
|
+
return cleanup
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
registration.signal = options.signal
|
|
110
|
+
registration.abortHandler = cleanup
|
|
111
|
+
options.signal.addEventListener('abort', cleanup, { once: true })
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.#addRegistration(registration)
|
|
115
|
+
this.#target.addEventListener(registration.event, registration.wrapper, {
|
|
116
|
+
capture: options?.capture,
|
|
117
|
+
passive: options?.passive,
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
return cleanup
|
|
35
121
|
}
|
|
36
122
|
|
|
37
123
|
once<E extends EventName>(
|
|
@@ -43,8 +129,15 @@ export class EventEmitter<
|
|
|
43
129
|
}
|
|
44
130
|
|
|
45
131
|
off(event: EventName | (Object & string), listener: Callback) {
|
|
46
|
-
const
|
|
47
|
-
|
|
132
|
+
const registration = this.#listeners
|
|
133
|
+
.get(event)
|
|
134
|
+
?.get(listener)
|
|
135
|
+
?.values()
|
|
136
|
+
.next().value
|
|
137
|
+
|
|
138
|
+
if (registration) {
|
|
139
|
+
this.#removeRegistration(registration)
|
|
140
|
+
}
|
|
48
141
|
}
|
|
49
142
|
|
|
50
143
|
emit<E extends EventName | (Object & string)>(
|
|
@@ -65,6 +158,12 @@ export const once = <
|
|
|
65
158
|
signal?: AbortSignal,
|
|
66
159
|
) => {
|
|
67
160
|
return new Promise<EventMap[EventName]>((resolve) => {
|
|
68
|
-
ee.once(
|
|
161
|
+
ee.once(
|
|
162
|
+
event,
|
|
163
|
+
((...args: EventMap[EventName]) => {
|
|
164
|
+
resolve(args)
|
|
165
|
+
}) as (...args: any[]) => void,
|
|
166
|
+
{ signal },
|
|
167
|
+
)
|
|
69
168
|
})
|
|
70
169
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
export * from './client.ts'
|
|
2
|
+
export * from './clients/runtime.ts'
|
|
3
|
+
export * from './clients/static.ts'
|
|
1
4
|
export * from './core.ts'
|
|
2
5
|
export * from './events.ts'
|
|
3
6
|
export * from './plugins/index.ts'
|
|
7
|
+
export * from './streams.ts'
|
|
4
8
|
export * from './transformers.ts'
|
|
5
9
|
export * from './transport.ts'
|
|
6
10
|
export * from './types.ts'
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Future } from '@nmtjs/common'
|
|
2
|
+
import { createFuture, MAX_UINT32, withTimeout } from '@nmtjs/common'
|
|
3
|
+
import {
|
|
4
|
+
ClientMessageType,
|
|
5
|
+
ConnectionType,
|
|
6
|
+
ServerMessageType,
|
|
7
|
+
} from '@nmtjs/protocol'
|
|
8
|
+
|
|
9
|
+
import type { ClientCore } from '../core.ts'
|
|
10
|
+
|
|
11
|
+
export interface PingLayerApi {
|
|
12
|
+
ping(timeout: number, signal?: AbortSignal): Promise<void>
|
|
13
|
+
stopAll(reason?: unknown): void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const createPingLayer = (core: ClientCore): PingLayerApi => {
|
|
17
|
+
let pingNonce = 0
|
|
18
|
+
const pendingPings = new Map<number, Future<void>>()
|
|
19
|
+
|
|
20
|
+
const nextPingNonce = () => {
|
|
21
|
+
if (pingNonce >= MAX_UINT32) {
|
|
22
|
+
pingNonce = 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return pingNonce++
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const stopAll = (reason?: unknown) => {
|
|
29
|
+
if (!pendingPings.size) return
|
|
30
|
+
|
|
31
|
+
const error = new Error('Heartbeat stopped', { cause: reason })
|
|
32
|
+
for (const pending of pendingPings.values()) {
|
|
33
|
+
pending.reject(error)
|
|
34
|
+
}
|
|
35
|
+
pendingPings.clear()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
core.on('message', (message: any) => {
|
|
39
|
+
switch (message.type) {
|
|
40
|
+
case ServerMessageType.Pong: {
|
|
41
|
+
const pending = pendingPings.get(message.nonce)
|
|
42
|
+
if (!pending) return
|
|
43
|
+
|
|
44
|
+
pendingPings.delete(message.nonce)
|
|
45
|
+
pending.resolve()
|
|
46
|
+
core.emit('pong', message.nonce)
|
|
47
|
+
break
|
|
48
|
+
}
|
|
49
|
+
case ServerMessageType.Ping: {
|
|
50
|
+
if (!core.messageContext) return
|
|
51
|
+
|
|
52
|
+
const buffer = core.protocol.encodeMessage(
|
|
53
|
+
core.messageContext,
|
|
54
|
+
ClientMessageType.Pong,
|
|
55
|
+
{ nonce: message.nonce },
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
core.send(buffer).catch(() => {})
|
|
59
|
+
break
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
core.on('disconnected', (reason) => {
|
|
65
|
+
stopAll(reason)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
ping(timeout: number, signal?: AbortSignal) {
|
|
70
|
+
if (
|
|
71
|
+
core.transportType !== ConnectionType.Bidirectional ||
|
|
72
|
+
core.state !== 'connected' ||
|
|
73
|
+
!core.messageContext
|
|
74
|
+
) {
|
|
75
|
+
return Promise.reject(new Error('Client is not connected'))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const nonce = nextPingNonce()
|
|
79
|
+
const future = createFuture<void>()
|
|
80
|
+
pendingPings.set(nonce, future)
|
|
81
|
+
|
|
82
|
+
const buffer = core.protocol.encodeMessage(
|
|
83
|
+
core.messageContext,
|
|
84
|
+
ClientMessageType.Ping,
|
|
85
|
+
{ nonce },
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return core
|
|
89
|
+
.send(buffer, signal)
|
|
90
|
+
.then(() =>
|
|
91
|
+
withTimeout(future.promise, timeout, new Error('Heartbeat timeout')),
|
|
92
|
+
)
|
|
93
|
+
.finally(() => {
|
|
94
|
+
pendingPings.delete(nonce)
|
|
95
|
+
})
|
|
96
|
+
},
|
|
97
|
+
stopAll,
|
|
98
|
+
}
|
|
99
|
+
}
|