@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.
Files changed (56) hide show
  1. package/dist/client.d.ts +64 -0
  2. package/dist/client.js +97 -0
  3. package/dist/client.js.map +1 -0
  4. package/dist/clients/runtime.d.ts +6 -12
  5. package/dist/clients/runtime.js +58 -57
  6. package/dist/clients/runtime.js.map +1 -1
  7. package/dist/clients/static.d.ts +4 -9
  8. package/dist/clients/static.js +20 -20
  9. package/dist/clients/static.js.map +1 -1
  10. package/dist/core.d.ts +33 -83
  11. package/dist/core.js +305 -690
  12. package/dist/core.js.map +1 -1
  13. package/dist/events.d.ts +0 -1
  14. package/dist/events.js +74 -11
  15. package/dist/events.js.map +1 -1
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.js +4 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/layers/ping.d.ts +6 -0
  20. package/dist/layers/ping.js +65 -0
  21. package/dist/layers/ping.js.map +1 -0
  22. package/dist/layers/rpc.d.ts +19 -0
  23. package/dist/layers/rpc.js +521 -0
  24. package/dist/layers/rpc.js.map +1 -0
  25. package/dist/layers/streams.d.ts +20 -0
  26. package/dist/layers/streams.js +194 -0
  27. package/dist/layers/streams.js.map +1 -0
  28. package/dist/plugins/browser.js +28 -9
  29. package/dist/plugins/browser.js.map +1 -1
  30. package/dist/plugins/heartbeat.js +10 -10
  31. package/dist/plugins/heartbeat.js.map +1 -1
  32. package/dist/plugins/index.d.ts +1 -1
  33. package/dist/plugins/index.js +0 -1
  34. package/dist/plugins/index.js.map +1 -1
  35. package/dist/plugins/reconnect.js +11 -94
  36. package/dist/plugins/reconnect.js.map +1 -1
  37. package/dist/plugins/types.d.ts +27 -11
  38. package/dist/transport.d.ts +49 -31
  39. package/dist/types.d.ts +21 -5
  40. package/package.json +10 -10
  41. package/src/client.ts +216 -0
  42. package/src/clients/runtime.ts +93 -79
  43. package/src/clients/static.ts +46 -38
  44. package/src/core.ts +394 -901
  45. package/src/events.ts +113 -14
  46. package/src/index.ts +4 -0
  47. package/src/layers/ping.ts +99 -0
  48. package/src/layers/rpc.ts +725 -0
  49. package/src/layers/streams.ts +277 -0
  50. package/src/plugins/browser.ts +39 -9
  51. package/src/plugins/heartbeat.ts +10 -10
  52. package/src/plugins/index.ts +8 -1
  53. package/src/plugins/reconnect.ts +12 -119
  54. package/src/plugins/types.ts +30 -13
  55. package/src/transport.ts +75 -46
  56. package/src/types.ts +33 -8
@@ -0,0 +1,277 @@
1
+ import type { ProtocolBlob, ProtocolBlobMetadata } from '@nmtjs/protocol'
2
+ import type {
3
+ ProtocolClientBlobStream,
4
+ ProtocolServerBlobConsumer,
5
+ } from '@nmtjs/protocol/client'
6
+ import { MAX_UINT32, noopFn } from '@nmtjs/common'
7
+ import { ClientMessageType, kBlobKey, ServerMessageType } from '@nmtjs/protocol'
8
+ import { ProtocolServerBlobStream } from '@nmtjs/protocol/client'
9
+
10
+ import type { ClientCore } from '../core.ts'
11
+ import { ClientStreams, ServerStreams } from '../streams.ts'
12
+
13
+ const DEFAULT_PULL_SIZE = 65535
14
+
15
+ const toReasonString = (reason: unknown) => {
16
+ if (typeof reason === 'string') return reason
17
+ if (reason === undefined || reason === null) return undefined
18
+ return String(reason)
19
+ }
20
+
21
+ export interface StreamLayerApi {
22
+ readonly clientStreams: ClientStreams
23
+ readonly serverStreams: ServerStreams
24
+ getStreamId: () => number
25
+ addClientStream: (blob: ProtocolBlob) => ProtocolClientBlobStream
26
+ createServerBlobStream: (
27
+ streamId: number,
28
+ metadata: ProtocolBlobMetadata,
29
+ ) => ProtocolServerBlobConsumer
30
+ addServerBlobStream: (metadata: ProtocolBlobMetadata) => {
31
+ streamId: number
32
+ stream: ProtocolServerBlobStream
33
+ }
34
+ }
35
+
36
+ export const createServerBlobConsumer = (
37
+ metadata: ProtocolBlobMetadata,
38
+ subscribe: (options?: { signal?: AbortSignal }) => ProtocolServerBlobStream,
39
+ ): ProtocolServerBlobConsumer => {
40
+ const consumer = ((options?: { signal?: AbortSignal }) =>
41
+ subscribe(options)) as ProtocolServerBlobConsumer
42
+
43
+ Object.defineProperties(consumer, {
44
+ metadata: {
45
+ configurable: false,
46
+ enumerable: true,
47
+ writable: false,
48
+ value: metadata,
49
+ },
50
+ [kBlobKey]: {
51
+ configurable: false,
52
+ enumerable: false,
53
+ writable: false,
54
+ value: true,
55
+ },
56
+ })
57
+
58
+ return consumer
59
+ }
60
+
61
+ export const createStreamLayer = (core: ClientCore): StreamLayerApi => {
62
+ const clientStreams = new ClientStreams()
63
+ const serverStreams = new ServerStreams()
64
+
65
+ let streamId = 0
66
+
67
+ const getStreamId = () => {
68
+ if (streamId >= MAX_UINT32) {
69
+ streamId = 0
70
+ }
71
+
72
+ return streamId++
73
+ }
74
+
75
+ const addClientStream = (blob: ProtocolBlob) => {
76
+ const id = getStreamId()
77
+ return clientStreams.add(blob.source, id, blob.metadata)
78
+ }
79
+
80
+ const createServerBlobStream = (
81
+ id: number,
82
+ metadata: ProtocolBlobMetadata,
83
+ ) => {
84
+ const stream = new ProtocolServerBlobStream(metadata, {
85
+ pull: () => {
86
+ if (!core.messageContext) return
87
+
88
+ core.emitStreamEvent({
89
+ direction: 'outgoing',
90
+ streamType: 'server_blob',
91
+ action: 'pull',
92
+ streamId: id,
93
+ byteLength: DEFAULT_PULL_SIZE,
94
+ })
95
+
96
+ const buffer = core.protocol.encodeMessage(
97
+ core.messageContext,
98
+ ClientMessageType.ServerStreamPull,
99
+ { streamId: id, size: DEFAULT_PULL_SIZE },
100
+ )
101
+
102
+ core.send(buffer).catch(noopFn)
103
+ },
104
+ close: () => {
105
+ serverStreams.remove(id)
106
+ },
107
+ readableStrategy: { highWaterMark: 0 },
108
+ })
109
+
110
+ serverStreams.add(id, stream)
111
+
112
+ return createServerBlobConsumer(metadata, ({ signal } = {}) => {
113
+ if (signal) {
114
+ signal.addEventListener(
115
+ 'abort',
116
+ () => {
117
+ if (!core.messageContext) return
118
+
119
+ core.emitStreamEvent({
120
+ direction: 'outgoing',
121
+ streamType: 'server_blob',
122
+ action: 'abort',
123
+ streamId: id,
124
+ reason: toReasonString(signal.reason),
125
+ })
126
+
127
+ const buffer = core.protocol.encodeMessage(
128
+ core.messageContext,
129
+ ClientMessageType.ServerStreamAbort,
130
+ { streamId: id, reason: toReasonString(signal.reason) },
131
+ )
132
+
133
+ core.send(buffer).catch(noopFn)
134
+ void serverStreams.abort(id).catch(noopFn)
135
+ },
136
+ { once: true },
137
+ )
138
+ }
139
+
140
+ return stream
141
+ })
142
+ }
143
+
144
+ const addServerBlobStream = (metadata: ProtocolBlobMetadata) => {
145
+ const id = getStreamId()
146
+ const stream = new ProtocolServerBlobStream(metadata)
147
+ serverStreams.add(id, stream)
148
+ return { streamId: id, stream }
149
+ }
150
+
151
+ core.on('message', (message: any) => {
152
+ switch (message.type) {
153
+ case ServerMessageType.ServerStreamPush:
154
+ core.emitStreamEvent({
155
+ direction: 'incoming',
156
+ streamType: 'server_blob',
157
+ action: 'push',
158
+ streamId: message.streamId,
159
+ byteLength: message.chunk.byteLength,
160
+ })
161
+ void serverStreams.push(message.streamId, message.chunk)
162
+ break
163
+ case ServerMessageType.ServerStreamEnd:
164
+ core.emitStreamEvent({
165
+ direction: 'incoming',
166
+ streamType: 'server_blob',
167
+ action: 'end',
168
+ streamId: message.streamId,
169
+ })
170
+ void serverStreams.end(message.streamId)
171
+ break
172
+ case ServerMessageType.ServerStreamAbort:
173
+ core.emitStreamEvent({
174
+ direction: 'incoming',
175
+ streamType: 'server_blob',
176
+ action: 'abort',
177
+ streamId: message.streamId,
178
+ reason: message.reason,
179
+ })
180
+ void serverStreams.abort(message.streamId)
181
+ break
182
+ case ServerMessageType.ClientStreamPull:
183
+ core.emitStreamEvent({
184
+ direction: 'incoming',
185
+ streamType: 'client_blob',
186
+ action: 'pull',
187
+ streamId: message.streamId,
188
+ byteLength: message.size,
189
+ })
190
+
191
+ void clientStreams.pull(message.streamId, message.size).then(
192
+ (chunk) => {
193
+ if (!core.messageContext) return
194
+
195
+ if (chunk) {
196
+ core.emitStreamEvent({
197
+ direction: 'outgoing',
198
+ streamType: 'client_blob',
199
+ action: 'push',
200
+ streamId: message.streamId,
201
+ byteLength: chunk.byteLength,
202
+ })
203
+
204
+ const buffer = core.protocol.encodeMessage(
205
+ core.messageContext,
206
+ ClientMessageType.ClientStreamPush,
207
+ { streamId: message.streamId, chunk },
208
+ )
209
+
210
+ core.send(buffer).catch(noopFn)
211
+ return
212
+ }
213
+
214
+ core.emitStreamEvent({
215
+ direction: 'outgoing',
216
+ streamType: 'client_blob',
217
+ action: 'end',
218
+ streamId: message.streamId,
219
+ })
220
+
221
+ const buffer = core.protocol.encodeMessage(
222
+ core.messageContext,
223
+ ClientMessageType.ClientStreamEnd,
224
+ { streamId: message.streamId },
225
+ )
226
+
227
+ core.send(buffer).catch(noopFn)
228
+ void clientStreams.end(message.streamId).catch(noopFn)
229
+ },
230
+ () => {
231
+ if (!core.messageContext) return
232
+
233
+ core.emitStreamEvent({
234
+ direction: 'outgoing',
235
+ streamType: 'client_blob',
236
+ action: 'abort',
237
+ streamId: message.streamId,
238
+ })
239
+
240
+ const buffer = core.protocol.encodeMessage(
241
+ core.messageContext,
242
+ ClientMessageType.ClientStreamAbort,
243
+ { streamId: message.streamId },
244
+ )
245
+
246
+ core.send(buffer).catch(noopFn)
247
+ clientStreams.remove(message.streamId)
248
+ },
249
+ )
250
+ break
251
+ case ServerMessageType.ClientStreamAbort:
252
+ core.emitStreamEvent({
253
+ direction: 'incoming',
254
+ streamType: 'client_blob',
255
+ action: 'abort',
256
+ streamId: message.streamId,
257
+ reason: message.reason,
258
+ })
259
+ void clientStreams.abort(message.streamId, message.reason).catch(noopFn)
260
+ break
261
+ }
262
+ })
263
+
264
+ core.on('disconnected', (reason) => {
265
+ void clientStreams.clear(reason).catch(noopFn)
266
+ void serverStreams.clear(reason).catch(noopFn)
267
+ })
268
+
269
+ return {
270
+ clientStreams,
271
+ serverStreams,
272
+ getStreamId,
273
+ addClientStream,
274
+ createServerBlobStream,
275
+ addServerBlobStream,
276
+ }
277
+ }
@@ -1,32 +1,60 @@
1
1
  import type { ClientPlugin } from './types.ts'
2
2
 
3
+ const syncPauseReasons = (
4
+ setPauseReason: (reason: string, active: boolean) => void,
5
+ ) => {
6
+ if (globalThis.window && 'navigator' in globalThis.window) {
7
+ setPauseReason('offline', globalThis.window.navigator?.onLine === false)
8
+ }
9
+
10
+ if (globalThis.document) {
11
+ setPauseReason(
12
+ 'tab_hidden',
13
+ globalThis.document.visibilityState === 'hidden',
14
+ )
15
+ }
16
+ }
17
+
3
18
  export const browserConnectivityPlugin = (): ClientPlugin => {
4
- return (client) => {
19
+ return ({ core }) => {
5
20
  const cleanup: Array<() => void> = []
6
21
 
7
- const maybeConnect = () => {
8
- if (client.state === 'disconnected' && !client.isDisposed()) {
9
- client.connect().catch(() => void 0)
22
+ const triggerReconnect = () => {
23
+ if (!core.isDisposed()) {
24
+ core.triggerReconnect()
10
25
  }
11
26
  }
12
27
 
13
28
  return {
14
29
  name: 'browser-connectivity',
15
30
  onInit: () => {
31
+ syncPauseReasons(core.setReconnectPauseReason.bind(core))
32
+
16
33
  if (globalThis.window) {
17
- const onPageShow = () => maybeConnect()
34
+ const onPageShow = () => triggerReconnect()
18
35
  globalThis.window.addEventListener('pageshow', onPageShow)
19
36
  cleanup.push(() =>
20
37
  globalThis.window?.removeEventListener('pageshow', onPageShow),
21
38
  )
22
39
 
23
- const onOnline = () => maybeConnect()
40
+ const onOnline = () => {
41
+ core.setReconnectPauseReason('offline', false)
42
+ triggerReconnect()
43
+ }
24
44
  globalThis.window.addEventListener('online', onOnline)
25
45
  cleanup.push(() =>
26
46
  globalThis.window?.removeEventListener('online', onOnline),
27
47
  )
28
48
 
29
- const onFocus = () => maybeConnect()
49
+ const onOffline = () => {
50
+ core.setReconnectPauseReason('offline', true)
51
+ }
52
+ globalThis.window.addEventListener('offline', onOffline)
53
+ cleanup.push(() =>
54
+ globalThis.window?.removeEventListener('offline', onOffline),
55
+ )
56
+
57
+ const onFocus = () => triggerReconnect()
30
58
  globalThis.window.addEventListener('focus', onFocus)
31
59
  cleanup.push(() =>
32
60
  globalThis.window?.removeEventListener('focus', onFocus),
@@ -35,8 +63,10 @@ export const browserConnectivityPlugin = (): ClientPlugin => {
35
63
 
36
64
  if (globalThis.document) {
37
65
  const onVisibilityChange = () => {
38
- if (globalThis.document?.visibilityState === 'visible') {
39
- maybeConnect()
66
+ const hidden = globalThis.document?.visibilityState === 'hidden'
67
+ core.setReconnectPauseReason('tab_hidden', hidden)
68
+ if (!hidden) {
69
+ triggerReconnect()
40
70
  }
41
71
  }
42
72
 
@@ -40,7 +40,7 @@ export interface HeartbeatPluginOptions {
40
40
  export const heartbeatPlugin = (
41
41
  options: HeartbeatPluginOptions = {},
42
42
  ): ClientPlugin => {
43
- return (client) => {
43
+ return ({ core, ping }) => {
44
44
  const interval = options.interval ?? DEFAULT_HEARTBEAT_INTERVAL
45
45
  const timeout = options.timeout ?? DEFAULT_HEARTBEAT_TIMEOUT
46
46
 
@@ -55,7 +55,7 @@ export const heartbeatPlugin = (
55
55
 
56
56
  const startHeartbeat = () => {
57
57
  if (heartbeatTask) return
58
- if (client.transportType !== ConnectionType.Bidirectional) return
58
+ if (core.transportType !== ConnectionType.Bidirectional) return
59
59
 
60
60
  heartbeatAbortController = new AbortController()
61
61
  const signal = heartbeatAbortController.signal
@@ -63,8 +63,8 @@ export const heartbeatPlugin = (
63
63
  heartbeatTask = (async () => {
64
64
  while (
65
65
  !signal.aborted &&
66
- !client.isDisposed() &&
67
- client.state === 'connected'
66
+ !core.isDisposed() &&
67
+ core.state === 'connected'
68
68
  ) {
69
69
  if (isPaused()) {
70
70
  await sleep(1000, signal)
@@ -75,21 +75,21 @@ export const heartbeatPlugin = (
75
75
 
76
76
  if (
77
77
  signal.aborted ||
78
- client.isDisposed() ||
79
- client.state !== 'connected'
78
+ core.isDisposed() ||
79
+ core.state !== 'connected'
80
80
  ) {
81
81
  continue
82
82
  }
83
83
 
84
84
  try {
85
- await client.ping(timeout, signal)
85
+ await ping.ping(timeout, signal)
86
86
  } catch {
87
87
  if (
88
88
  !signal.aborted &&
89
- !client.isDisposed() &&
90
- client.state === 'connected'
89
+ !core.isDisposed() &&
90
+ core.state === 'connected'
91
91
  ) {
92
- await client
92
+ await core
93
93
  .requestReconnect('heartbeat_timeout')
94
94
  .catch(() => void 0)
95
95
  }
@@ -1,5 +1,12 @@
1
+ export type {
2
+ ClientPlugin,
3
+ ClientPluginContext,
4
+ ClientPluginEvent,
5
+ ClientPluginInstance,
6
+ ReconnectConfig,
7
+ StreamEvent,
8
+ } from './types.ts'
1
9
  export * from './browser.ts'
2
10
  export * from './heartbeat.ts'
3
11
  export * from './logging.ts'
4
12
  export * from './reconnect.ts'
5
- export * from './types.ts'
@@ -1,44 +1,4 @@
1
- import { ConnectionType } from '@nmtjs/protocol'
2
-
3
- import type { ClientDisconnectReason, ClientPlugin } from './types.ts'
4
-
5
- const DEFAULT_RECONNECT_TIMEOUT = 1000
6
- const DEFAULT_MAX_RECONNECT_TIMEOUT = 60000
7
-
8
- const sleep = (ms: number, signal?: AbortSignal) => {
9
- return new Promise<void>((resolve) => {
10
- if (signal?.aborted) return resolve()
11
- const timer = setTimeout(resolve, ms)
12
- if (signal) {
13
- signal.addEventListener(
14
- 'abort',
15
- () => {
16
- clearTimeout(timer)
17
- resolve()
18
- },
19
- { once: true },
20
- )
21
- }
22
- })
23
- }
24
-
25
- const computeReconnectDelay = (ms: number) => {
26
- if (globalThis.window) {
27
- const jitter = Math.floor(ms * 0.2 * Math.random())
28
- return ms + jitter
29
- }
30
- return ms
31
- }
32
-
33
- const isReconnectPaused = () => {
34
- if (globalThis.window && 'navigator' in globalThis.window) {
35
- if (globalThis.window.navigator?.onLine === false) return true
36
- }
37
- if (globalThis.document) {
38
- if (globalThis.document.visibilityState === 'hidden') return true
39
- }
40
- return false
41
- }
1
+ import type { ClientPlugin } from './types.ts'
42
2
 
43
3
  export interface ReconnectPluginOptions {
44
4
  initialTimeout?: number
@@ -48,83 +8,16 @@ export interface ReconnectPluginOptions {
48
8
  export const reconnectPlugin = (
49
9
  options: ReconnectPluginOptions = {},
50
10
  ): ClientPlugin => {
51
- return (client) => {
52
- let reconnecting: Promise<void> | null = null
53
- let reconnectAbortController: AbortController | null = null
54
- let reconnectTimeout = options.initialTimeout ?? DEFAULT_RECONNECT_TIMEOUT
55
-
56
- const cancelReconnect = () => {
57
- reconnectAbortController?.abort()
58
- reconnectAbortController = null
59
- reconnecting = null
60
- }
61
-
62
- const ensureReconnectLoop = () => {
63
- if (reconnecting) return
64
-
65
- reconnectAbortController = new AbortController()
66
- const signal = reconnectAbortController.signal
67
-
68
- reconnecting = (async () => {
69
- while (
70
- !signal.aborted &&
71
- !client.isDisposed() &&
72
- client.state === 'disconnected' &&
73
- client.lastDisconnectReason !== 'client'
74
- ) {
75
- if (isReconnectPaused()) {
76
- await sleep(1000, signal)
77
- continue
78
- }
79
-
80
- const delay = computeReconnectDelay(reconnectTimeout)
81
- await sleep(delay, signal)
82
-
83
- if (
84
- signal.aborted ||
85
- client.isDisposed() ||
86
- client.state !== 'disconnected' ||
87
- client.lastDisconnectReason === 'client'
88
- ) {
89
- break
90
- }
91
-
92
- const previousTimeout = reconnectTimeout
93
- await client.connect().catch(() => void 0)
94
-
95
- if (client.state === 'disconnected') {
96
- reconnectTimeout = Math.min(
97
- previousTimeout * 2,
98
- options.maxTimeout ?? DEFAULT_MAX_RECONNECT_TIMEOUT,
99
- )
100
- }
101
- }
102
- })().finally(() => {
103
- reconnecting = null
104
- reconnectAbortController = null
11
+ return ({ core }) => ({
12
+ name: 'reconnect',
13
+ onInit: () => {
14
+ core.configureReconnect({
15
+ initialTimeout: options.initialTimeout,
16
+ maxTimeout: options.maxTimeout,
105
17
  })
106
- }
107
-
108
- const onDisconnect = (reason: ClientDisconnectReason) => {
109
- if (
110
- client.transportType !== ConnectionType.Bidirectional ||
111
- reason === 'client' ||
112
- client.isDisposed()
113
- ) {
114
- cancelReconnect()
115
- return
116
- }
117
- ensureReconnectLoop()
118
- }
119
-
120
- return {
121
- name: 'reconnect',
122
- onConnect: () => {
123
- reconnectTimeout = options.initialTimeout ?? DEFAULT_RECONNECT_TIMEOUT
124
- cancelReconnect()
125
- },
126
- onDisconnect,
127
- dispose: cancelReconnect,
128
- }
129
- }
18
+ },
19
+ dispose: () => {
20
+ core.configureReconnect(null)
21
+ },
22
+ })
130
23
  }
@@ -1,8 +1,30 @@
1
- import type { BaseClient } from '../core.ts'
1
+ import type { ClientCore, ConnectionState } from '../core.ts'
2
+ import type { PingLayerApi } from '../layers/ping.ts'
2
3
 
3
4
  export type ClientDisconnectReason = 'client' | 'server' | (string & {})
4
5
 
6
+ export interface ReconnectConfig {
7
+ initialTimeout?: number
8
+ maxTimeout?: number
9
+ }
10
+
11
+ export type StreamEvent = {
12
+ direction: 'incoming' | 'outgoing'
13
+ streamType: 'rpc' | 'client_blob' | 'server_blob'
14
+ action: 'response' | 'pull' | 'push' | 'end' | 'abort'
15
+ callId?: number
16
+ streamId?: number
17
+ byteLength?: number
18
+ reason?: string
19
+ }
20
+
5
21
  export type ClientPluginEvent =
22
+ | {
23
+ kind: 'state_changed'
24
+ timestamp: number
25
+ state: ConnectionState
26
+ previous: ConnectionState
27
+ }
6
28
  | {
7
29
  kind: 'connected'
8
30
  timestamp: number
@@ -38,17 +60,7 @@ export type ClientPluginEvent =
38
60
  procedure: string
39
61
  error: unknown
40
62
  }
41
- | {
42
- kind: 'stream_event'
43
- timestamp: number
44
- direction: 'incoming' | 'outgoing'
45
- streamType: 'rpc' | 'client_blob' | 'server_blob'
46
- action: 'response' | 'pull' | 'push' | 'end' | 'abort'
47
- callId?: number
48
- streamId?: number
49
- byteLength?: number
50
- reason?: string
51
- }
63
+ | ({ kind: 'stream_event'; timestamp: number } & StreamEvent)
52
64
 
53
65
  /**
54
66
  * Client plugin lifecycle contract.
@@ -67,6 +79,11 @@ export interface ClientPluginInstance {
67
79
  dispose?(): void
68
80
  }
69
81
 
82
+ export interface ClientPluginContext {
83
+ core: ClientCore
84
+ ping: PingLayerApi
85
+ }
86
+
70
87
  export type ClientPlugin = (
71
- client: BaseClient<any, any, any, any, any>,
88
+ context: ClientPluginContext,
72
89
  ) => ClientPluginInstance