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/README.zh-TW.md
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
> **English:** [README.md](./README.md)
|
|
2
|
+
|
|
3
|
+
# react-ws-context
|
|
4
|
+
|
|
5
|
+
React 用的 WebSocket **連線層**套件。將連線生命週期、可訂閱狀態與訊息事件分離,避免 status 或高頻訊息更新拖垮整棵元件樹。
|
|
6
|
+
|
|
7
|
+
> **維護者:** [GaiaYang](https://github.com/GaiaYang)
|
|
8
|
+
> **原始碼:** [github.com/GaiaYang/react-ws](https://github.com/GaiaYang/react-ws)(monorepo 內路徑 `packages/react-ws`)
|
|
9
|
+
|
|
10
|
+
## 特性
|
|
11
|
+
|
|
12
|
+
- **零 runtime 依賴** — 僅需 `react >= 18`(peer dependency)
|
|
13
|
+
- **設定凍結** — `url`、`reconnectMs` 等在 `createWsContext` 時固定;執行期以 `connect` / `disconnect` 控制
|
|
14
|
+
- **渲染隔離** — 連線層 state(健康/佇列/重連)走外部 store;訊息走 event emitter,不進 React Context
|
|
15
|
+
- **可選探活** — 週期性 ping/pong 偵測,逾時主動關閉 socket 以觸發重連
|
|
16
|
+
- **可選 outbound 佇列** — 未 OPEN 時暫存待送訊息,連線成功後 flush
|
|
17
|
+
|
|
18
|
+
## 環境需求
|
|
19
|
+
|
|
20
|
+
| 項目 | 版本 |
|
|
21
|
+
| -------- | ------------------------------------------------- |
|
|
22
|
+
| React | >= 18(依賴 `useSyncExternalStore`) |
|
|
23
|
+
| 執行環境 | 瀏覽器 Client Component(需原生 `WebSocket` API) |
|
|
24
|
+
|
|
25
|
+
套件入口標有 `"use client"`。呼叫 `createWsContext` 的模組,以及使用其 hooks 的元件,皆須位於 Client 邊界內。
|
|
26
|
+
|
|
27
|
+
## 安裝
|
|
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
|
+
## 快速開始
|
|
36
|
+
|
|
37
|
+
**1. 建立連線 context(通常放在獨立模組,只執行一次)**
|
|
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. 在應用中使用**
|
|
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("收到訊息", data);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<button
|
|
72
|
+
disabled={status !== "open"}
|
|
73
|
+
onClick={() => sendJson({ type: "ping" })}
|
|
74
|
+
>
|
|
75
|
+
送出({status})
|
|
76
|
+
</button>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 核心概念
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
createWsContext(options)
|
|
85
|
+
│
|
|
86
|
+
├── WsProvider 管理 WebSocket 實例、重連、探活、outbound 佇列
|
|
87
|
+
├── useWsActions() 連線操作(send / connect / disconnect),不觸發重繪
|
|
88
|
+
├── useWsStore() 訂閱連線層 state:健康/佇列/重連(useSyncExternalStore)
|
|
89
|
+
└── useWsEvents() 訂閱 open / message / error / close 事件
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- **同一應用可多次呼叫 `createWsContext`**,每次產生一組互不共用的 Provider 與 hooks(例如同時連業務 WS 與通知 WS)。
|
|
93
|
+
- **`WsState` 只放連線層、低頻欄位** — 連線健康(如 `status`)、outbound 佇列(如未來 `pendingCount`)、重連(如未來 `reconnectAttempt`)。**不放**訊息 payload 或業務資料。
|
|
94
|
+
- **訊息與錯誤事件** — 請用 `useWsEvents`;訊息歷史請自行寫入 state、cache 或外部 store。
|
|
95
|
+
- **連線錯誤不反映在 `WsStatus`** — 請用 `useWsEvents("error", …)` 處理;原生 `error` 事件後通常緊接 `close`。
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## API 參考
|
|
100
|
+
|
|
101
|
+
### `createWsContext(options)`
|
|
102
|
+
|
|
103
|
+
建立一組綁定同一連線設定的 `WsProvider` 與 hooks。
|
|
104
|
+
|
|
105
|
+
#### 參數:`CreateWsContextOptions`
|
|
106
|
+
|
|
107
|
+
| 欄位 | 型別 | 預設 | 說明 |
|
|
108
|
+
| ------------------ | ----------------------------------------- | -------- | ---------------------------------------------- |
|
|
109
|
+
| `url` | `string` | (必填) | WebSocket 連線網址 |
|
|
110
|
+
| `protocols` | `string \| string[]` | — | 傳入 `new WebSocket(url, protocols)` 的子協定 |
|
|
111
|
+
| `autoConnect` | `boolean` | `true` | `WsProvider` mount 後是否自動呼叫 `connect()` |
|
|
112
|
+
| `reconnectMs` | `number` | `0` | 非主動斷線後的重連間隔(毫秒);`0` 表示不重連 |
|
|
113
|
+
| `outgoingQueueMax` | `number` | `0` | 未 OPEN 時 outbound 佇列上限;`0` 關閉佇列 |
|
|
114
|
+
| `parse` | `(data: MessageEvent["data"]) => unknown` | 見下方 | 將原始 `MessageEvent.data` 轉成業務資料 |
|
|
115
|
+
| `liveness` | `LivenessOptions` | — | 探活設定;省略則不啟用 |
|
|
116
|
+
|
|
117
|
+
**預設 `parse` 行為:**
|
|
118
|
+
|
|
119
|
+
- `data` 為字串 → 嘗試 `JSON.parse`,失敗則原樣回傳
|
|
120
|
+
- 非字串 → 原樣回傳
|
|
121
|
+
|
|
122
|
+
#### 回傳值
|
|
123
|
+
|
|
124
|
+
| 名稱 | 型別 | 說明 |
|
|
125
|
+
| -------------- | ------------------------------------ | ------------------------------------ |
|
|
126
|
+
| `WsProvider` | `React.FC<{ children }>` | 包住需要此連線的子樹 |
|
|
127
|
+
| `useWsActions` | `() => WsContextValue` | 連線操作 API |
|
|
128
|
+
| `useWsStore` | `() => WsState` 或 `(selector) => T` | 訂閱連線層 state(健康/佇列/重連) |
|
|
129
|
+
| `useWsEvents` | `(type, handler) => void` | 訂閱 WebSocket 事件 |
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### `WsProvider`
|
|
134
|
+
|
|
135
|
+
負責建立、維護與銷毀原生 `WebSocket` 實例。
|
|
136
|
+
|
|
137
|
+
| 行為 | 說明 |
|
|
138
|
+
| --------------------------- | --------------------------------------------------------------------------- |
|
|
139
|
+
| mount + `autoConnect: true` | 自動 `connect()` |
|
|
140
|
+
| unmount | 主動關閉連線、停止探活、清空 outbound 佇列,並 emit `close` |
|
|
141
|
+
| 重連 | 非主動斷線且 `reconnectMs > 0` 時,以固定間隔重試(無 exponential backoff) |
|
|
142
|
+
| 重連前 | 若已有舊 socket,先關閉並 emit `close`(reason: `"reconnect"`) |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### `useWsActions(): WsContextValue`
|
|
147
|
+
|
|
148
|
+
必須在對應的 `WsProvider` 內使用。回傳值以 `useMemo` 穩定引用,**不會**因 store 或訊息更新而重繪元件。
|
|
149
|
+
|
|
150
|
+
| 方法 | 簽名 | 說明 |
|
|
151
|
+
| ------------ | ---------------------------- | ------------------------------------------------------------------------------------------ |
|
|
152
|
+
| `send` | `(data) => boolean` | 傳送原始資料(`string`、`ArrayBuffer`、`Blob` 等)。已 OPEN 則立即送出;否則視佇列設定入隊 |
|
|
153
|
+
| `sendJson` | `(data: unknown) => boolean` | `JSON.stringify` 後呼叫 `send` |
|
|
154
|
+
| `connect` | `() => void` | 建立連線;若已有連線會先關閉舊 socket |
|
|
155
|
+
| `disconnect` | `() => void` | 主動斷線,**不**觸發自動重連;清空 outbound 佇列 |
|
|
156
|
+
| `getStatus` | `() => WsStatus` | 讀取當下 status;不訂閱、不觸發渲染 |
|
|
157
|
+
|
|
158
|
+
**`send` / `sendJson` 回傳值:**
|
|
159
|
+
|
|
160
|
+
- `true` — 已送出,或已成功入隊
|
|
161
|
+
- `false` — 未 OPEN 且佇列已滿(`outgoingQueueMax > 0` 且達上限),或佇列關閉(`outgoingQueueMax === 0`)
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### `useWsStore()`
|
|
166
|
+
|
|
167
|
+
必須在對應的 `WsProvider` 內使用。底層以 `useSyncExternalStore` 訂閱外部 store。
|
|
168
|
+
|
|
169
|
+
`WsState` 定位:**連線健康/outbound 佇列/重連** 等低頻、連線生命週期資訊。高頻訊息請用 `useWsEvents("message", …)`,不要寫進 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
|
+
// 未來可能擴充(皆為低頻、連線層):
|
|
182
|
+
// reconnectAttempt?: number;
|
|
183
|
+
// pendingCount?: number;
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
| 適合放進 store | 不適合 |
|
|
188
|
+
| ------------------------------------------------------------ | ------------------------------------- |
|
|
189
|
+
| `status`、重連次數、待送佇列長度、探活/stall 等連線健康摘要 | `lastMessage`、訊息歷史、業務 payload |
|
|
190
|
+
|
|
191
|
+
#### `WsStatus`
|
|
192
|
+
|
|
193
|
+
| 值 | 意義 |
|
|
194
|
+
| ------------ | -------- |
|
|
195
|
+
| `idle` | 尚未連線 |
|
|
196
|
+
| `connecting` | 連線中 |
|
|
197
|
+
| `open` | 已連線 |
|
|
198
|
+
| `closed` | 已斷線 |
|
|
199
|
+
|
|
200
|
+
**建議:** 以 selector 只訂閱需要的欄位;state 擴充後可避免不必要的重繪。
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
const status = useWsStore((s) => s.status);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
### `useWsEvents(type, handler)`
|
|
209
|
+
|
|
210
|
+
必須在對應的 `WsProvider` 內使用。在 `useEffect` 內註冊,unmount 時自動取消訂閱。
|
|
211
|
+
|
|
212
|
+
| `type` | handler 簽名 | 說明 |
|
|
213
|
+
| ----------- | ---------------------------------------------- | -------------------------------- |
|
|
214
|
+
| `"message"` | `(data: unknown, event: MessageEvent) => void` | `data` 為經 `parse` 處理後的結果 |
|
|
215
|
+
| `"open"` | `(event: Event) => void` | 連線建立 |
|
|
216
|
+
| `"error"` | `(event: Event) => void` | 連線錯誤 |
|
|
217
|
+
| `"close"` | `(event: CloseEvent) => void` | 連線關閉 |
|
|
218
|
+
|
|
219
|
+
**行為細節:**
|
|
220
|
+
|
|
221
|
+
- `handler` 以 ref 保存最新引用,callback 重建**不會**導致重新訂閱
|
|
222
|
+
- `type` 變更**會**重新訂閱
|
|
223
|
+
- 需監聽多種事件時,分別呼叫多次 `useWsEvents`
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### 探活:`LivenessOptions`
|
|
228
|
+
|
|
229
|
+
透過 `createWsContext({ liveness: { … } })` 啟用。連線 OPEN 後開始週期性送 ping;若在 `timeoutMs` 內未收到符合條件的 pong,主動 `close()` socket(進而觸發重連流程)。
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
interface LivenessOptions {
|
|
233
|
+
intervalMs: number; // ping 間隔(毫秒)
|
|
234
|
+
timeoutMs: number; // 等待 pong 逾時(毫秒)
|
|
235
|
+
ping: unknown | (() => unknown); // ping payload;函式則每次動態產生
|
|
236
|
+
isPong: (data: unknown) => boolean; // 判定傳入 data 是否為 pong
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**範例:**
|
|
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
|
+
探活期間,`onmessage` 收到的每一筆資料都會先經 `isPong` 判定;若為 pong 則重置逾時計時,並照常 emit `"message"` 事件。
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
### Outbound 佇列
|
|
263
|
+
|
|
264
|
+
當 `outgoingQueueMax > 0` 時:
|
|
265
|
+
|
|
266
|
+
| 時機 | 行為 |
|
|
267
|
+
| ------------------------ | ------------------------------ |
|
|
268
|
+
| `send` 且 socket 未 OPEN | 訊息入隊(FIFO) |
|
|
269
|
+
| 佇列已滿 | 回傳 `false`,**不**丟棄舊訊息 |
|
|
270
|
+
| socket OPEN | 依序 flush 全部佇列 |
|
|
271
|
+
| `disconnect()` | 清空佇列 |
|
|
272
|
+
| `WsProvider` unmount | 清空佇列 |
|
|
273
|
+
| 自動重連等待期間 | **保留**佇列 |
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
### 匯出型別
|
|
278
|
+
|
|
279
|
+
自 `react-ws-context` 主入口匯出:
|
|
280
|
+
|
|
281
|
+
| 型別 | 說明 |
|
|
282
|
+
| ------------------------ | -------------------------------------------------- |
|
|
283
|
+
| `CreateWsContextOptions` | `createWsContext` 的選項 |
|
|
284
|
+
| `WsContextValue` | `useWsActions()` 回傳型別 |
|
|
285
|
+
| `WsEvents` | 事件名稱與 handler 的型別對應 |
|
|
286
|
+
| `WsStatus` | 連線生命週期狀態聯集(`WsState` 的一環) |
|
|
287
|
+
| `WsState` | 可訂閱 store 的 state 形狀(連線健康/佇列/重連) |
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## 子模組:`react-ws-context/stall`
|
|
292
|
+
|
|
293
|
+
可選的停滯(stall)控制訊息 helper,供 Demo 或需與 mock server 對接的場景使用。**不**自動整合進 `createWsContext`,需自行在 handler 內解析。
|
|
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
|
+
| 匯出 | 說明 |
|
|
308
|
+
| ---------------------------- | --------------------------------------------------------------- |
|
|
309
|
+
| `STALL_MESSAGE_TYPE` | 客戶端控制訊息 type 常數(`"STALL"`) |
|
|
310
|
+
| `STALL_ACK_TYPE` | 伺服器確認 type 常數(`"STALL_ACK"`) |
|
|
311
|
+
| `createStallMessage(action)` | 建立可 `sendJson` 的控制訊息 |
|
|
312
|
+
| `parseStallMessage(data)` | 從 `useWsEvents("message")` 的 `data` 解析;格式不符回傳 `null` |
|
|
313
|
+
| `StallAction` | `"stall" \| "release"` |
|
|
314
|
+
| `StallMessage` | `{ type: "STALL"; action: StallAction }` |
|
|
315
|
+
| `StallAck` | `{ type: "STALL_ACK"; action: StallAction; active: boolean }` |
|
|
316
|
+
|
|
317
|
+
**範例:**
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
const { sendJson } = useWsActions();
|
|
321
|
+
|
|
322
|
+
useWsEvents("message", (data) => {
|
|
323
|
+
const stall = parseStallMessage(data);
|
|
324
|
+
if (stall) console.log("stall 控制", stall.action);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
sendJson(createStallMessage("stall"));
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## 設計取捨與限制
|
|
333
|
+
|
|
334
|
+
| 項目 | 說明 |
|
|
335
|
+
| -------------- | ------------------------------------------------------------------------------------------ |
|
|
336
|
+
| 設定不可變 | `url`、`reconnectMs` 等建立後固定;需換 URL 請另建 context 或手動 `disconnect` + `connect` |
|
|
337
|
+
| 重連策略 | 固定間隔,無 exponential backoff、無最大重試次數 |
|
|
338
|
+
| SSR | 不在 server 建立 `WebSocket`;`connect()` 在 `window` 不存在時為 no-op |
|
|
339
|
+
| 錯誤狀態 | 不設 `"error"` status;請監聽 `useWsEvents("error")` |
|
|
340
|
+
| `WsState` 範圍 | 只含連線健康/佇列/重連;訊息與業務資料不走 store |
|
|
341
|
+
| 訊息與渲染 | 只呼叫 `useWsActions` 的元件不會因 store 或 message 重繪 |
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## 授權
|
|
346
|
+
|
|
347
|
+
本套件以 [MIT License](./LICENSE) 釋出。Copyright (c) 2026 [GaiaYang](https://github.com/GaiaYang)。
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 借鑑與致謝
|
|
352
|
+
|
|
353
|
+
本套件**未**將下列專案列為 npm 依賴;為達成零 runtime 依賴,僅內嵌本套件所需的精簡子集。各原始碼檔案頂部亦附有出處備註。
|
|
354
|
+
|
|
355
|
+
### [zustand](https://github.com/pmndrs/zustand)
|
|
356
|
+
|
|
357
|
+
- **作者/維護:** [pmndrs](https://github.com/pmndrs)(Poimandres)
|
|
358
|
+
- **授權:** [MIT](https://github.com/pmndrs/zustand/blob/main/LICENSE)
|
|
359
|
+
- **借鑑範圍:**
|
|
360
|
+
- 外部 store(`getState` / `setState` / `subscribe` / `getInitialState`)— 靈感與行為對齊 [`vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts),非完整搬移(無 middleware、replace、initializer factory)
|
|
361
|
+
- React 訂閱 hook — 靈感來自 [`react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) 的 `useStore`(selector 必填、無 `useDebugValue`)
|
|
362
|
+
- **對應原始碼:** `src/ws-context/store.ts`、`src/ws-context/use-store.ts`
|
|
363
|
+
|
|
364
|
+
### [nanoevents](https://github.com/ai/nanoevents)
|
|
365
|
+
|
|
366
|
+
- **作者:** [Andrey Sitnik](https://github.com/ai)(`ai`)
|
|
367
|
+
- **授權:** [MIT](https://github.com/ai/nanoevents/blob/main/LICENSE)
|
|
368
|
+
- **借鑑範圍:**
|
|
369
|
+
- Typed event emitter — 執行期邏輯幾乎對齊 [`createNanoEvents`](https://github.com/ai/nanoevents/blob/main/index.js);型別為本套件收斂版
|
|
370
|
+
- `useEmitter` 為本套件自行新增(React `useState` 包裝)
|
|
371
|
+
- **對應原始碼:** `src/ws-context/emitter.ts`
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { PropsWithChildren } from "react";
|
|
2
|
+
//#region src/ws-context/ws-store.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* 連線生命週期狀態(`WsState` 的一環)。
|
|
5
|
+
*
|
|
6
|
+
* - `idle` — 尚未連線
|
|
7
|
+
* - `connecting` — 連線中
|
|
8
|
+
* - `open` — 已連線
|
|
9
|
+
* - `closed` — 已斷線
|
|
10
|
+
*
|
|
11
|
+
* 錯誤用 `useWsEvents("error")`;不另設 error status。
|
|
12
|
+
*/
|
|
13
|
+
type WsStatus = "idle" | "connecting" | "open" | "closed";
|
|
14
|
+
/**
|
|
15
|
+
* 可訂閱的連線層 state(低頻更新)。
|
|
16
|
+
*
|
|
17
|
+
* 只放:**連線健康**、**outbound 佇列**、**重連** 等連線生命週期資訊。
|
|
18
|
+
* 不放:訊息 payload、訊息歷史、業務資料(請用 `useWsEvents` 或自行管理 state)。
|
|
19
|
+
*
|
|
20
|
+
* 未來可能擴充例如 `reconnectAttempt`、`pendingCount`;新增欄位時請維持低頻、可 selector 訂閱。
|
|
21
|
+
*/
|
|
22
|
+
type WsState = {
|
|
23
|
+
/** 連線生命週期狀態 */
|
|
24
|
+
status: WsStatus;
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/ws-context/liveness/types.d.ts
|
|
28
|
+
/** 探活選項 */
|
|
29
|
+
interface LivenessOptions {
|
|
30
|
+
/** 探活間隔(ms) */
|
|
31
|
+
intervalMs: number;
|
|
32
|
+
/** 探活超時(ms) */
|
|
33
|
+
timeoutMs: number;
|
|
34
|
+
/** 探活訊息 */
|
|
35
|
+
ping: unknown | (() => unknown);
|
|
36
|
+
/** 探活回應判定 */
|
|
37
|
+
isPong: (data: unknown) => boolean;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/ws-context/types.d.ts
|
|
41
|
+
/** `createWsContext` 連線設定;建立時固定 */
|
|
42
|
+
interface CreateWsContextOptions {
|
|
43
|
+
/** 連線 URL */
|
|
44
|
+
url: string;
|
|
45
|
+
/** 連線協定 */
|
|
46
|
+
protocols?: string | string[];
|
|
47
|
+
/**
|
|
48
|
+
* 是否自動重新連線。
|
|
49
|
+
*
|
|
50
|
+
* @default true
|
|
51
|
+
*/
|
|
52
|
+
autoConnect?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* 自動重連間隔(ms);`0` 不重連。
|
|
55
|
+
*
|
|
56
|
+
* @default 0
|
|
57
|
+
*/
|
|
58
|
+
reconnectMs?: number;
|
|
59
|
+
/**
|
|
60
|
+
* 未連線時發送訊息的佇列上限;`0` 關閉。
|
|
61
|
+
*
|
|
62
|
+
* @default 0
|
|
63
|
+
*/
|
|
64
|
+
outgoingQueueMax?: number;
|
|
65
|
+
/**
|
|
66
|
+
* 資料解析函數。
|
|
67
|
+
*
|
|
68
|
+
* 預設處理方式:如果資料為字串,嘗試 `JSON.parse`,否則原樣返回。
|
|
69
|
+
*/
|
|
70
|
+
parse?: (data: MessageEvent["data"]) => unknown;
|
|
71
|
+
/** 探活 */
|
|
72
|
+
liveness?: LivenessOptions;
|
|
73
|
+
}
|
|
74
|
+
interface WsEvents {
|
|
75
|
+
/**
|
|
76
|
+
* 收到訊息
|
|
77
|
+
*
|
|
78
|
+
* @param data 訊息內容,已經過 `parse` 處理
|
|
79
|
+
* @param event 原始事件物件
|
|
80
|
+
*/
|
|
81
|
+
message: (data: unknown, event: MessageEvent) => void;
|
|
82
|
+
/** 連線建立 */
|
|
83
|
+
open: (event: Event) => void;
|
|
84
|
+
/** 發生錯誤 */
|
|
85
|
+
error: (event: Event) => void;
|
|
86
|
+
/** 連線斷開 */
|
|
87
|
+
close: (event: CloseEvent) => void;
|
|
88
|
+
}
|
|
89
|
+
/** 連線操作 API;可訂閱的連線層 state(健康/佇列/重連)請用 `useWsStore` */
|
|
90
|
+
interface WsContextValue {
|
|
91
|
+
/**
|
|
92
|
+
* 傳送訊息
|
|
93
|
+
*
|
|
94
|
+
* @param data 訊息內容
|
|
95
|
+
* @returns 是否已送出或已入隊
|
|
96
|
+
*/
|
|
97
|
+
send: (data: Parameters<WebSocket["send"]>[0]) => boolean;
|
|
98
|
+
/**
|
|
99
|
+
* 傳送 JSON 資料
|
|
100
|
+
*
|
|
101
|
+
* @param data 資料
|
|
102
|
+
* @returns 是否已送出或已入隊
|
|
103
|
+
*/
|
|
104
|
+
sendJson: (data: unknown) => boolean;
|
|
105
|
+
/** 建立連線 */
|
|
106
|
+
connect: () => void;
|
|
107
|
+
/** 斷開連線 */
|
|
108
|
+
disconnect: () => void;
|
|
109
|
+
/** 讀取當下 `status`;不訂閱 store、不觸發渲染 */
|
|
110
|
+
getStatus: () => WsStatus;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/ws-context/index.d.ts
|
|
114
|
+
/**
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* export const { WsProvider, useWsActions, useWsStore, useWsEvents } =
|
|
118
|
+
* createWsContext({ url: "ws://localhost:8080" });
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
declare function createWsContext(options: CreateWsContextOptions): {
|
|
122
|
+
WsProvider: ({ children }: PropsWithChildren) => import("react").JSX.Element;
|
|
123
|
+
useWsActions: () => WsContextValue;
|
|
124
|
+
useWsStore: {
|
|
125
|
+
(): WsState;
|
|
126
|
+
<T>(selector: (state: WsState) => T): T;
|
|
127
|
+
};
|
|
128
|
+
useWsEvents: <E extends keyof WsEvents>(type: E, handler: WsEvents[E]) => void;
|
|
129
|
+
};
|
|
130
|
+
//#endregion
|
|
131
|
+
export { type CreateWsContextOptions, type WsContextValue, type WsEvents, type WsState, type WsStatus, createWsContext };
|
|
132
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/ws-context/ws-store.ts","../src/ws-context/liveness/types.ts","../src/ws-context/types.ts","../src/ws-context/index.tsx"],"mappings":";;;;;;;;;;;;KAcY;;;;;;;;;KAUA;;EAEV,QAAQ;;;;;UCzBO;;EAEf;;EAEA;;EAEA;;EAEA,SAAS;;;;;UCLM;;EAEf;;EAEA;;;;;;EAMA;;;;;;EAMA;;;;;;EAMA;;;;;;EAMA,SAAS,MAAM;;EAEf,WAAW;;UAGI;;;;;;;EAOf,UAAU,eAAe,OAAO;;EAEhC,OAAO,OAAO;;EAEd,QAAQ,OAAO;;EAEf,QAAQ,OAAO;;;UAIA;;;;;;;EAOf,OAAO,MAAM,WAAW;;;;;;;EAOxB,WAAW;;EAEX;;EAEA;;EAEA,iBAAiB;;;;;;;;;;;iBC/BH,gBAAgB,SAAS;EAkBL,eAAA,YAAA,sCAAiB,IAAA"}
|