react-ws-context 0.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/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +356 -0
- package/README.zh-TW.md +371 -0
- package/dist/index.d.mts +132 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +403 -0
- package/dist/index.mjs.map +1 -0
- package/dist/stall/index.d.mts +40 -0
- package/dist/stall/index.d.mts.map +1 -0
- package/dist/stall/index.mjs +32 -0
- package/dist/stall/index.mjs.map +1 -0
- package/package.json +79 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this package are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-08-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `createWsContext(options)` factory — returns `WsProvider`, `useWsActions`, `useWsStore`, and `useWsEvents` per connection
|
|
15
|
+
- **Render isolation:** actions (no re-renders), connection-layer store (`useSyncExternalStore`), and message events kept separate
|
|
16
|
+
- Connection lifecycle: `connect`, `disconnect`, `autoConnect`, fixed-interval reconnect (`reconnectMs`)
|
|
17
|
+
- Outbound message queue while socket is not `OPEN` (`outgoingQueueMax`)
|
|
18
|
+
- Optional liveness / heartbeat (`LivenessOptions`: ping interval, timeout, custom `isPong`)
|
|
19
|
+
- Configurable `parse` for incoming messages (default: `JSON.parse` for strings)
|
|
20
|
+
- `WsStatus`: `idle` | `connecting` | `open` | `closed`
|
|
21
|
+
- `react-ws-context/stall` subpath — optional stall-control message helpers for demos and mock servers
|
|
22
|
+
- Zero runtime dependencies (`react >= 18` peer only)
|
|
23
|
+
- Smoke tests for context, liveness, and stall helpers
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GaiaYang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# react-ws-context
|
|
2
|
+
|
|
3
|
+
> **繁體中文:** [README.zh-TW.md](./README.zh-TW.md)
|
|
4
|
+
|
|
5
|
+
A React **WebSocket connection layer**. It separates connection lifecycle, subscribable state, and message events so status updates or high-frequency messages do not re-render your entire component tree.
|
|
6
|
+
|
|
7
|
+
> **Maintainer:** [GaiaYang](https://github.com/GaiaYang)
|
|
8
|
+
> **Source:** [github.com/GaiaYang/react-ws](https://github.com/GaiaYang/react-ws) (package path: `packages/react-ws`)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Zero runtime dependencies** — only `react >= 18` as a peer dependency
|
|
13
|
+
- **Frozen config** — `url`, `reconnectMs`, etc. are fixed at `createWsContext`; use `connect` / `disconnect` at runtime
|
|
14
|
+
- **Render isolation** — connection-layer state (health / queue / reconnect) lives in an external store; messages go through an event emitter, not React Context
|
|
15
|
+
- **Optional liveness** — periodic ping / pong; closes the socket on timeout to trigger reconnect
|
|
16
|
+
- **Optional outbound queue** — buffers messages while not OPEN, flushes on connect
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
| Item | Version |
|
|
21
|
+
| ----------- | ------------------------------------------------- |
|
|
22
|
+
| React | >= 18 (`useSyncExternalStore`) |
|
|
23
|
+
| Environment | Browser Client Component (native `WebSocket` API) |
|
|
24
|
+
|
|
25
|
+
The package entry is marked `"use client"`. Modules that call `createWsContext` and components that use its hooks must live inside a Client boundary.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm add react-ws-context react
|
|
31
|
+
# npm install react-ws-context react
|
|
32
|
+
# yarn add react-ws-context react
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
**1. Create a connection context (usually once, in its own module)**
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
"use client";
|
|
41
|
+
|
|
42
|
+
import { createWsContext } from "react-ws-context";
|
|
43
|
+
|
|
44
|
+
export const { WsProvider, useWsActions, useWsStore, useWsEvents } =
|
|
45
|
+
createWsContext({
|
|
46
|
+
url: "ws://localhost:8080",
|
|
47
|
+
reconnectMs: 2000,
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**2. Use it in your app**
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
"use client";
|
|
55
|
+
|
|
56
|
+
import { WsProvider, useWsActions, useWsStore, useWsEvents } from "./ws";
|
|
57
|
+
|
|
58
|
+
export function App({ children }: { children: React.ReactNode }) {
|
|
59
|
+
return <WsProvider>{children}</WsProvider>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function Chat() {
|
|
63
|
+
const { sendJson } = useWsActions();
|
|
64
|
+
const status = useWsStore((s) => s.status);
|
|
65
|
+
|
|
66
|
+
useWsEvents("message", (data) => {
|
|
67
|
+
console.log("message", data);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<button
|
|
72
|
+
disabled={status !== "open"}
|
|
73
|
+
onClick={() => sendJson({ type: "ping" })}
|
|
74
|
+
>
|
|
75
|
+
Send ({status})
|
|
76
|
+
</button>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Core concepts
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
createWsContext(options)
|
|
85
|
+
│
|
|
86
|
+
├── WsProvider WebSocket instance, reconnect, liveness, outbound queue
|
|
87
|
+
├── useWsActions() send / connect / disconnect — no re-renders
|
|
88
|
+
├── useWsStore() connection-layer state: health / queue / reconnect
|
|
89
|
+
└── useWsEvents() open / message / error / close
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- **Call `createWsContext` multiple times** for independent connections (e.g. app WS + notification WS).
|
|
93
|
+
- **`WsState` holds low-frequency connection data only** — health (`status`), outbound queue (e.g. future `pendingCount`), reconnect (e.g. future `reconnectAttempt`). **Not** message payloads or app data.
|
|
94
|
+
- **Messages and errors** — use `useWsEvents`; keep message history in your own state, cache, or store.
|
|
95
|
+
- **Connection errors are not a `WsStatus`** — use `useWsEvents("error")`; native `error` is usually followed by `close`.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## API reference
|
|
100
|
+
|
|
101
|
+
### `createWsContext(options)`
|
|
102
|
+
|
|
103
|
+
Creates a `WsProvider` and hooks bound to the same connection config.
|
|
104
|
+
|
|
105
|
+
#### `CreateWsContextOptions`
|
|
106
|
+
|
|
107
|
+
| Field | Type | Default | Description |
|
|
108
|
+
| ------------------ | ----------------------------------------- | ---------- | ---------------------------------------------------------------------- |
|
|
109
|
+
| `url` | `string` | (required) | WebSocket URL |
|
|
110
|
+
| `protocols` | `string \| string[]` | — | Passed to `new WebSocket(url, protocols)` |
|
|
111
|
+
| `autoConnect` | `boolean` | `true` | Call `connect()` after `WsProvider` mounts |
|
|
112
|
+
| `reconnectMs` | `number` | `0` | Reconnect delay (ms) after unintentional close; `0` disables reconnect |
|
|
113
|
+
| `outgoingQueueMax` | `number` | `0` | Max outbound queue size while not OPEN; `0` disables the queue |
|
|
114
|
+
| `parse` | `(data: MessageEvent["data"]) => unknown` | see below | Transform raw `MessageEvent.data` |
|
|
115
|
+
| `liveness` | `LivenessOptions` | — | Liveness / heartbeat config; omit to disable |
|
|
116
|
+
|
|
117
|
+
**Default `parse`:**
|
|
118
|
+
|
|
119
|
+
- string → try `JSON.parse`, return raw string on failure
|
|
120
|
+
- otherwise → return as-is
|
|
121
|
+
|
|
122
|
+
#### Returns
|
|
123
|
+
|
|
124
|
+
| Name | Type | Description |
|
|
125
|
+
| -------------- | ------------------------------------ | -------------------------------------------- |
|
|
126
|
+
| `WsProvider` | `React.FC<{ children }>` | Wraps the subtree that needs this connection |
|
|
127
|
+
| `useWsActions` | `() => WsContextValue` | Connection actions |
|
|
128
|
+
| `useWsStore` | `() => WsState` or `(selector) => T` | Subscribe to connection-layer state |
|
|
129
|
+
| `useWsEvents` | `(type, handler) => void` | Subscribe to WebSocket events |
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### `WsProvider`
|
|
134
|
+
|
|
135
|
+
Creates, owns, and tears down the native `WebSocket`.
|
|
136
|
+
|
|
137
|
+
| Behavior | Description |
|
|
138
|
+
| --------------------------- | -------------------------------------------------------------------------------------------- |
|
|
139
|
+
| mount + `autoConnect: true` | Calls `connect()` |
|
|
140
|
+
| unmount | Closes connection, stops liveness, clears outbound queue, emits `close` |
|
|
141
|
+
| reconnect | Fixed interval when `reconnectMs > 0` and close was not intentional (no exponential backoff) |
|
|
142
|
+
| before reconnect | Closes existing socket and emits `close` (reason: `"reconnect"`) |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### `useWsActions(): WsContextValue`
|
|
147
|
+
|
|
148
|
+
Must be used inside the matching `WsProvider`. Return value is memoized and **does not** re-render on store or message updates.
|
|
149
|
+
|
|
150
|
+
| Method | Signature | Description |
|
|
151
|
+
| ------------ | ---------------------------- | ---------------------------------------------------------------------------- |
|
|
152
|
+
| `send` | `(data) => boolean` | Send raw data. Sends immediately when OPEN; otherwise enqueues if configured |
|
|
153
|
+
| `sendJson` | `(data: unknown) => boolean` | `JSON.stringify` then `send` |
|
|
154
|
+
| `connect` | `() => void` | Open connection; closes any existing socket first |
|
|
155
|
+
| `disconnect` | `() => void` | Intentional close; no auto-reconnect; clears outbound queue |
|
|
156
|
+
| `getStatus` | `() => WsStatus` | Read current status; no subscription, no re-render |
|
|
157
|
+
|
|
158
|
+
**`send` / `sendJson` return value:**
|
|
159
|
+
|
|
160
|
+
- `true` — sent or enqueued
|
|
161
|
+
- `false` — not OPEN and queue full (`outgoingQueueMax > 0`), or queue disabled (`outgoingQueueMax === 0`)
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### `useWsStore()`
|
|
166
|
+
|
|
167
|
+
Must be used inside the matching `WsProvider`. Uses `useSyncExternalStore` under the hood.
|
|
168
|
+
|
|
169
|
+
`WsState` is for **connection health / outbound queue / reconnect** — low-frequency lifecycle data. For high-frequency messages, use `useWsEvents("message", …)`, not the store.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
useWsStore(): WsState
|
|
173
|
+
useWsStore<T>(selector: (state: WsState) => T): T
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### `WsState`
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
interface WsState {
|
|
180
|
+
status: WsStatus;
|
|
181
|
+
// Possible future fields (all low-frequency, connection-layer):
|
|
182
|
+
// reconnectAttempt?: number;
|
|
183
|
+
// pendingCount?: number;
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
| Belongs in store | Does not belong |
|
|
188
|
+
| ------------------------------------------------------------------------- | -------------------------------------------- |
|
|
189
|
+
| `status`, reconnect count, pending queue size, liveness / stall summaries | `lastMessage`, message history, app payloads |
|
|
190
|
+
|
|
191
|
+
#### `WsStatus`
|
|
192
|
+
|
|
193
|
+
| Value | Meaning |
|
|
194
|
+
| ------------ | ------------- |
|
|
195
|
+
| `idle` | Not connected |
|
|
196
|
+
| `connecting` | Connecting |
|
|
197
|
+
| `open` | Connected |
|
|
198
|
+
| `closed` | Disconnected |
|
|
199
|
+
|
|
200
|
+
**Tip:** use a selector to subscribe to only the fields you need.
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
const status = useWsStore((s) => s.status);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
### `useWsEvents(type, handler)`
|
|
209
|
+
|
|
210
|
+
Must be used inside the matching `WsProvider`. Registers in `useEffect` and unsubscribes on unmount.
|
|
211
|
+
|
|
212
|
+
| `type` | Handler | Description |
|
|
213
|
+
| ----------- | ---------------------------------------------- | ---------------------------- |
|
|
214
|
+
| `"message"` | `(data: unknown, event: MessageEvent) => void` | `data` is the parsed payload |
|
|
215
|
+
| `"open"` | `(event: Event) => void` | Connection open |
|
|
216
|
+
| `"error"` | `(event: Event) => void` | Connection error |
|
|
217
|
+
| `"close"` | `(event: CloseEvent) => void` | Connection closed |
|
|
218
|
+
|
|
219
|
+
**Details:**
|
|
220
|
+
|
|
221
|
+
- Handler is kept in a ref — changing the callback does **not** re-subscribe
|
|
222
|
+
- Changing `type` **does** re-subscribe
|
|
223
|
+
- For multiple events, call `useWsEvents` multiple times
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### Liveness: `LivenessOptions`
|
|
228
|
+
|
|
229
|
+
Enable via `createWsContext({ liveness: { … } })`. After OPEN, sends periodic pings; if no matching pong within `timeoutMs`, closes the socket (which can trigger reconnect).
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
interface LivenessOptions {
|
|
233
|
+
intervalMs: number;
|
|
234
|
+
timeoutMs: number;
|
|
235
|
+
ping: unknown | (() => unknown);
|
|
236
|
+
isPong: (data: unknown) => boolean;
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Example:**
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
createWsContext({
|
|
244
|
+
url: "ws://localhost:8080",
|
|
245
|
+
reconnectMs: 3000,
|
|
246
|
+
liveness: {
|
|
247
|
+
intervalMs: 30_000,
|
|
248
|
+
timeoutMs: 10_000,
|
|
249
|
+
ping: { type: "ping" },
|
|
250
|
+
isPong: (data) =>
|
|
251
|
+
typeof data === "object" &&
|
|
252
|
+
data != null &&
|
|
253
|
+
(data as { type?: string }).type === "pong",
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Every incoming message is checked with `isPong`; a pong resets the timeout timer and still emits `"message"`.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
### Outbound queue
|
|
263
|
+
|
|
264
|
+
When `outgoingQueueMax > 0`:
|
|
265
|
+
|
|
266
|
+
| When | Behavior |
|
|
267
|
+
| -------------------------- | ------------------------------------------------- |
|
|
268
|
+
| `send` while not OPEN | Enqueue (FIFO) |
|
|
269
|
+
| Queue full | Returns `false`; does **not** drop older messages |
|
|
270
|
+
| Socket OPEN | Flush entire queue in order |
|
|
271
|
+
| `disconnect()` | Clear queue |
|
|
272
|
+
| `WsProvider` unmount | Clear queue |
|
|
273
|
+
| Waiting for auto-reconnect | **Keep** queue |
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
### Exported types
|
|
278
|
+
|
|
279
|
+
From the main `react-ws-context` entry:
|
|
280
|
+
|
|
281
|
+
| Type | Description |
|
|
282
|
+
| ------------------------ | ----------------------------------------------------- |
|
|
283
|
+
| `CreateWsContextOptions` | Options for `createWsContext` |
|
|
284
|
+
| `WsContextValue` | Return type of `useWsActions()` |
|
|
285
|
+
| `WsEvents` | Event name → handler map |
|
|
286
|
+
| `WsStatus` | Connection lifecycle status |
|
|
287
|
+
| `WsState` | Subscribable store shape (health / queue / reconnect) |
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Submodule: `react-ws-context/stall`
|
|
292
|
+
|
|
293
|
+
Optional stall-control message helpers for demos or mock-server integration. **Not** wired into `createWsContext` — parse in your own handlers.
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import {
|
|
297
|
+
STALL_MESSAGE_TYPE,
|
|
298
|
+
STALL_ACK_TYPE,
|
|
299
|
+
createStallMessage,
|
|
300
|
+
parseStallMessage,
|
|
301
|
+
type StallAction,
|
|
302
|
+
type StallMessage,
|
|
303
|
+
type StallAck,
|
|
304
|
+
} from "react-ws-context/stall";
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
| Export | Description |
|
|
308
|
+
| ---------------------------- | ------------------------------------------------------------- |
|
|
309
|
+
| `STALL_MESSAGE_TYPE` | Client control message type (`"STALL"`) |
|
|
310
|
+
| `STALL_ACK_TYPE` | Server ack type (`"STALL_ACK"`) |
|
|
311
|
+
| `createStallMessage(action)` | Build a message for `sendJson` |
|
|
312
|
+
| `parseStallMessage(data)` | Parse from `useWsEvents("message")` data; `null` if invalid |
|
|
313
|
+
| `StallAction` | `"stall" \| "release"` |
|
|
314
|
+
| `StallMessage` | `{ type: "STALL"; action: StallAction }` |
|
|
315
|
+
| `StallAck` | `{ type: "STALL_ACK"; action: StallAction; active: boolean }` |
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Design trade-offs
|
|
320
|
+
|
|
321
|
+
| Topic | Notes |
|
|
322
|
+
| ---------------- | ------------------------------------------------------------------------------ |
|
|
323
|
+
| Immutable config | `url`, `reconnectMs`, etc. are fixed at create time |
|
|
324
|
+
| Reconnect | Fixed interval only; no exponential backoff or max retries yet |
|
|
325
|
+
| SSR | No `WebSocket` on the server; `connect()` is a no-op without `window` |
|
|
326
|
+
| Error status | No `"error"` in `WsStatus`; use `useWsEvents("error")` |
|
|
327
|
+
| `WsState` scope | Health / queue / reconnect only — not messages or app data |
|
|
328
|
+
| Rendering | Components that only call `useWsActions` do not re-render on store or messages |
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## License
|
|
333
|
+
|
|
334
|
+
[MIT License](./LICENSE). Copyright (c) 2026 [GaiaYang](https://github.com/GaiaYang).
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Acknowledgments
|
|
339
|
+
|
|
340
|
+
This package does **not** list zustand or nanoevents as npm dependencies. It inlines minimal subsets for zero runtime deps. Source files include attribution headers.
|
|
341
|
+
|
|
342
|
+
### [zustand](https://github.com/pmndrs/zustand)
|
|
343
|
+
|
|
344
|
+
- **Maintainer:** [pmndrs](https://github.com/pmndrs) (Poimandres)
|
|
345
|
+
- **License:** [MIT](https://github.com/pmndrs/zustand/blob/main/LICENSE)
|
|
346
|
+
- **Adapted from:**
|
|
347
|
+
- External store API — aligned with [`vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts) (subset only)
|
|
348
|
+
- React subscription — inspired by [`react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) `useStore`
|
|
349
|
+
- **Files:** `src/ws-context/store.ts`, `src/ws-context/use-store.ts`
|
|
350
|
+
|
|
351
|
+
### [nanoevents](https://github.com/ai/nanoevents)
|
|
352
|
+
|
|
353
|
+
- **Author:** [Andrey Sitnik](https://github.com/ai) (`ai`)
|
|
354
|
+
- **License:** [MIT](https://github.com/ai/nanoevents/blob/main/LICENSE)
|
|
355
|
+
- **Adapted from:** [`createNanoEvents`](https://github.com/ai/nanoevents/blob/main/index.js); `useEmitter` added by this package
|
|
356
|
+
- **File:** `src/ws-context/emitter.ts`
|