react-ws-context 0.3.0 → 0.4.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/README.zh-TW.md CHANGED
@@ -1,11 +1,11 @@
1
- > **English:** [README.md](./README.md)
2
-
3
1
  # react-ws-context
4
2
 
5
3
  [![npm version](https://img.shields.io/npm/v/react-ws-context.svg)](https://www.npmjs.com/package/react-ws-context)
6
4
  [![npm downloads](https://img.shields.io/npm/dm/react-ws-context.svg)](https://www.npmjs.com/package/react-ws-context)
7
5
 
8
- React 用的 WebSocket **連線層**套件。將連線生命週期、可訂閱狀態與訊息事件分離,避免 status 或高頻訊息更新拖垮整棵元件樹。
6
+ > **English:** [README.md](./README.md)
7
+
8
+ React 用的 **WebSocket 連線層**套件。將連線生命週期、可訂閱狀態與訊息事件分離,避免連線 status 或高頻訊息更新拖垮整棵元件樹。
9
9
 
10
10
  > **維護者:** [GaiaYang](https://github.com/GaiaYang)
11
11
  > **原始碼:** [github.com/GaiaYang/react-ws](https://github.com/GaiaYang/react-ws)(monorepo 內路徑 `packages/react-ws`)
@@ -14,7 +14,7 @@ React 用的 WebSocket **連線層**套件。將連線生命週期、可訂閱
14
14
 
15
15
  - **零 runtime 依賴** — 僅需 `react >= 18`(peer dependency)
16
16
  - **設定凍結** — `url`、`reconnectMs` 等在 `createWsContext` 時固定;執行期以 `connect` / `disconnect` 控制
17
- - **渲染隔離** — 連線層 state(健康/佇列/重連)走外部 store;訊息走 event emitter,不進 React Context
17
+ - **渲染隔離** — 連線層 state(健康/佇列/重連)走外部 store;訊息經 event emitter 傳遞,以 `useWsEvents` 訂閱(不寫入 React state)
18
18
  - **可選探活** — 週期性 ping/pong 偵測,逾時主動關閉 socket 以觸發重連
19
19
  - **可選 outbound 佇列** — 未 OPEN 時暫存待送訊息,連線成功後 flush
20
20
 
@@ -87,12 +87,13 @@ function Chat() {
87
87
  createWsContext(options)
88
88
 
89
89
  ├── WsProvider 管理 WebSocket 實例、重連、探活、outbound 佇列
90
- ├── useWsActions() 連線操作(send / connect / disconnect),不觸發重繪
91
- ├── useWsStore() 訂閱連線層 state:健康/佇列/重連(useSyncExternalStore)
92
- └── useWsEvents() 訂閱 open / message / error / close 事件
90
+ ├── useWsActions() 連線操作(send / connect / disconnect / getStatus),不觸發重繪
91
+ ├── useWsStore() 訂閱連線層 state:健康/佇列/重連
92
+ └── useWsEvents() 訂閱 open / message / error / close 事件
93
93
  ```
94
94
 
95
95
  - **同一應用可多次呼叫 `createWsContext`**,每次產生一組互不共用的 Provider 與 hooks(例如同時連業務 WS 與通知 WS)。
96
+ - **Provider 內部** — 各 `WsProvider` 持有獨立的 store 與 event emitter(非公開 API);actions 在 `WsProvider` 內以 `useMemo` 組裝後注入 Context。
96
97
  - **`WsState` 只放連線層、低頻欄位** — 連線健康(`status`、`phase`)、outbound 佇列(如未來 `pendingCount`)、重連(`reconnectAttempt`)。**不放**訊息 payload 或業務資料。
97
98
  - **訊息與錯誤事件** — 請用 `useWsEvents`;訊息歷史請自行寫入 state、cache 或外部 store。
98
99
  - **連線錯誤不反映在 `WsStatus`** — 請用 `useWsEvents("error", …)` 處理;原生 `error` 事件後通常緊接 `close`。
@@ -107,16 +108,16 @@ createWsContext(options)
107
108
 
108
109
  #### 參數:`CreateWsContextOptions`
109
110
 
110
- | 欄位 | 型別 | 預設 | 說明 |
111
- | ------------------ | ----------------------------------------- | -------- | --------------------------------------------------------------------------------------------- |
112
- | `url` | `string` | (必填) | WebSocket 連線網址 |
113
- | `protocols` | `string \| string[]` | — | 傳入 `new WebSocket(url, protocols)` 的子協定 |
114
- | `autoConnect` | `boolean` | `true` | `WsProvider` mount 後是否自動呼叫 `connect()` |
115
- | `reconnectMs` | `number` | `0` | 非主動斷線後的重連間隔(毫秒);`0` 表示不重連 |
116
- | `reconnectMax` | `number` | `0` | 非主動斷線後最多自動重連幾次(不含首次連線);`0` 不限制。`reconnectAttempt` 於成功 `open`、手動 `connect()` `disconnect()` 歸零 |
117
- | `outgoingQueueMax` | `number` | `0` | 未 OPEN 時 outbound 佇列上限;`0` 關閉佇列 |
118
- | `parse` | `(data: MessageEvent["data"]) => unknown` | 見下方 | 將原始 `MessageEvent.data` 轉成業務資料 |
119
- | `liveness` | `LivenessOptions` | — | 探活設定;省略則不啟用 |
111
+ | 欄位 | 型別 | 預設 | 說明 |
112
+ | ------------------ | ----------------------------------------- | -------- | ---------------------------------------------------------------- |
113
+ | `url` | `string` | (必填) | WebSocket 連線網址 |
114
+ | `protocols` | `string \| string[]` | — | 傳入 `new WebSocket(url, protocols)` 的子協定 |
115
+ | `autoConnect` | `boolean` | `true` | WsProvider 載入時是否自動連線 |
116
+ | `reconnectMs` | `number` | `0` | 非主動斷線後的重連間隔(毫秒);`0` 表示不重連 |
117
+ | `reconnectMax` | `number` | `0` | 非主動斷線後最多自動重連幾次;`0` 不限制(需 `reconnectMs > 0`) |
118
+ | `outgoingQueueMax` | `number` | `0` | 未 OPEN 時 outbound 佇列上限;`0` 關閉佇列 |
119
+ | `parse` | `(data: MessageEvent["data"]) => unknown` | 見下方 | 將原始 `MessageEvent.data` 轉成業務資料 |
120
+ | `liveness` | `LivenessOptions` | — | 探活設定;省略則不啟用 |
120
121
 
121
122
  **預設 `parse` 行為:**
122
123
 
@@ -138,12 +139,13 @@ createWsContext(options)
138
139
 
139
140
  負責建立、維護與銷毀原生 `WebSocket` 實例。
140
141
 
141
- | 行為 | 說明 |
142
- | --------------------------- | ---------------------------------------------------------------------------------------------------------------- |
143
- | mount + `autoConnect: true` | 自動 `connect()` |
144
- | unmount | 主動關閉連線、停止探活、清空 outbound 佇列,並 emit `close` |
145
- | 重連 | 非主動斷線且 `reconnectMs > 0` 時,以固定間隔重試(無 exponential backoff);`reconnectMax > 0` 時超過次數即停止 |
146
- | 重連前 | 若已有舊 socket,先關閉並 emit `close`(reason: `"reconnect"`) |
142
+ | 行為 | 說明 |
143
+ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
144
+ | `WsProvider` 載入且 `autoConnect: true` | 自動連線 |
145
+ | unmount | 取消重連(`reconnectAttempt` 與 `reconnectExhausted` 歸零)、停止探活、清空 outbound 佇列;store 同步為 `status: "closed"`、`phase: "idle"`;關閉 socket 並 emit `close`(reason: `"provider unmount"`) |
146
+ | `disconnect()` | unmount cleanup store 重置,但不觸發自動重連;emit `close`(reason: `"client disconnect"`) |
147
+ | 重連 | 非主動斷線且 `reconnectMs > 0` 時,以固定間隔重試(無 exponential backoff);`reconnectMax > 0` 時超過次數即停止 |
148
+ | `connect()` 時已有舊 socket | 先關閉舊 socket 並 emit `close`(reason: `"reconnect"`),再建立新連線(手動 connect 或自動重連皆同) |
147
149
 
148
150
  ---
149
151
 
@@ -151,27 +153,29 @@ createWsContext(options)
151
153
 
152
154
  必須在對應的 `WsProvider` 內使用。回傳值以 `useMemo` 穩定引用,**不會**因 store 或訊息更新而重繪元件。
153
155
 
154
- | 方法 | 簽名 | 說明 |
155
- | ------------ | ---------------------------- | ------------------------------------------------------------------------------------------ |
156
- | `send` | `(data) => boolean` | 傳送原始資料(`string`、`ArrayBuffer`、`Blob` 等)。已 OPEN 則立即送出;否則視佇列設定入隊 |
157
- | `sendJson` | `(data: unknown) => boolean` | `JSON.stringify` 後呼叫 `send` |
158
- | `connect` | `() => void` | 建立連線;若已有連線會先關閉舊 socket |
159
- | `disconnect` | `() => void` | 主動斷線,**不**觸發自動重連;清空 outbound 佇列 |
160
- | `getStatus` | `() => WsStatus` | 讀取當下 status;不訂閱、不觸發渲染 |
156
+ | 方法 | 簽名 | 說明 |
157
+ | ------------ | ---------------------------- | ------------------------------------------------------------------------------------------------ |
158
+ | `send` | `(data) => boolean` | 傳送原始資料(`string`、`ArrayBuffer`、`Blob` 等)。已 OPEN 則立即送出;否則視佇列設定入隊 |
159
+ | `sendJson` | `(data: unknown) => boolean` | `JSON.stringify` 後呼叫 `send`;回傳值同 `send`,無法序列化時為 `false` |
160
+ | `connect` | `() => void` | 建立連線;若已有連線會先關閉舊 socket(見 `WsProvider`) |
161
+ | `disconnect` | `() => void` | 主動斷線;store 設為 `phase: "idle"`、`status: "closed"`,**不**觸發自動重連;清空 outbound 佇列 |
162
+ | `getStatus` | `() => WsStatus` | 讀取當下 status;不訂閱、不觸發渲染 |
161
163
 
162
164
  **`send` / `sendJson` 回傳值:**
163
165
 
164
166
  - `true` — 已送出,或已成功入隊
165
- - `false` — OPEN 且佇列已滿(`outgoingQueueMax > 0` 且達上限),或佇列關閉(`outgoingQueueMax === 0`)
167
+ - `false` — 未送出:佇列已滿、佇列關閉,或 `sendJson` 無法序列化
166
168
 
167
169
  ---
168
170
 
169
171
  ### `useWsStore()`
170
172
 
171
- 必須在對應的 `WsProvider` 內使用。底層以 `useSyncExternalStore` 訂閱外部 store
173
+ 必須在對應的 `WsProvider` 內使用。底層以 `useSyncExternalStore` 訂閱外部 store;**partial 內欄位值未變時不會通知訂閱者**(shallow compare)。
172
174
 
173
175
  `WsState` 定位:**連線健康/outbound 佇列/重連** 等低頻、連線生命週期資訊。高頻訊息請用 `useWsEvents("message", …)`,不要寫進 store。
174
176
 
177
+ **建議:** 以 selector 只訂閱需要的欄位(例如 `(s) => s.phase`)。不帶 selector 的 `useWsStore()` 會訂閱整份 state,任一欄位變更都會觸發 re-render。
178
+
175
179
  ```ts
176
180
  useWsStore(): WsState
177
181
  useWsStore<T>(selector: (state: WsState) => T): T
@@ -193,9 +197,9 @@ interface WsState {
193
197
  }
194
198
  ```
195
199
 
196
- | 適合放進 store | 不適合 |
197
- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
198
- | `status`、`phase`、重連進度(`reconnectAttempt` / `reconnectExhausted`)、待送佇列長度、探活/stall 等連線健康摘要 | `lastMessage`、訊息歷史、業務 payload |
200
+ | 適合放進 store | 不適合 |
201
+ | ---------------------------------------------------------------------------------------------------- | ------------------------------------- |
202
+ | `status`、`phase`、重連進度(`reconnectAttempt` / `reconnectExhausted`)、探活/stall 等連線健康摘要 | `lastMessage`、訊息歷史、業務 payload |
199
203
 
200
204
  `CreateWsContextOptions`(如 `url`、`reconnectMax`)在 `createWsContext` 時凍結,**不在** `WsState`;UI 若需顯示 `n/max` 請自行保存設定值,或訂閱時與 store 欄位組合。
201
205
 
@@ -212,17 +216,17 @@ interface WsState {
212
216
 
213
217
  #### `WsPhase`
214
218
 
215
- | 值 | 意義 |
216
- | --------------- | -------------------------------------------------------------------- |
217
- | `idle` | 未連線、未排程重連(初始或手動 `disconnect()`) |
218
- | `connecting` | 首次或手動 `connect()` 連線中 |
219
- | `open` | 已連線 |
220
- | `reconnecting` | 自動重連週期(等待計時器或連線中);搭配 `status`、`reconnectAttempt` |
221
- | `stopped` | 不會再自動重連;`reconnectExhausted` 區分達上限或未啟用重連 |
219
+ | 值 | 意義 |
220
+ | -------------- | --------------------------------------------------------------------- |
221
+ | `idle` | 未連線、未排程重連(初始或手動 `disconnect()`) |
222
+ | `connecting` | 首次或手動 `connect()` 連線中 |
223
+ | `open` | 已連線 |
224
+ | `reconnecting` | 自動重連週期(等待計時器或連線中);搭配 `status`、`reconnectAttempt` |
225
+ | `stopped` | 不會再自動重連;`reconnectExhausted` 區分達上限或未啟用重連 |
222
226
 
223
227
  `status` 與 `phase` 常同時變化,但語意不同。例如 `phase === "reconnecting"` 且 `status === "closed"` 表示正在等待重連計時器;`status === "connecting"` 則表示計時器已觸發、正在嘗試連線。
224
228
 
225
- **建議:** 以 selector 只訂閱需要的欄位;state 擴充後可避免不必要的重繪。
229
+ **範例:**
226
230
 
227
231
  ```tsx
228
232
  const phase = useWsStore((s) => s.phase);
@@ -252,19 +256,23 @@ const canDisconnect =
252
256
 
253
257
  - `handler` 以 ref 保存最新引用,callback 重建**不會**導致重新訂閱
254
258
  - `type` 變更**會**重新訂閱
259
+ - 意外斷線時,`close` handler 觸發前 store 已更新為 `status: "closed"` 及對應 `phase`(`reconnecting` / `stopped` 等)
260
+ - 主動 `disconnect()` 或 Provider unmount 亦同:先更新 store,再 emit `close`
255
261
  - 需監聽多種事件時,分別呼叫多次 `useWsEvents`
256
262
 
257
263
  ---
258
264
 
259
- ### 探活:`LivenessOptions`
265
+ ### 探活
266
+
267
+ 透過 `createWsContext({ liveness: { … } })` 啟用。連線 OPEN 後週期性送出應用層 ping(經 `send` 送 JSON,非 WebSocket 控制帧);若在 `timeoutMs` 內未收到符合條件的 pong,主動 `close()` socket(進而觸發重連流程)。
260
268
 
261
- 透過 `createWsContext({ liveness: { … } })` 啟用。連線 OPEN 後開始週期性送 ping;若在 `timeoutMs` 內未收到符合條件的 pong,主動 `close()` socket(進而觸發重連流程)。
269
+ `liveness` 選項形狀如下:
262
270
 
263
271
  ```ts
264
272
  interface LivenessOptions {
265
273
  intervalMs: number; // ping 間隔(毫秒)
266
274
  timeoutMs: number; // 等待 pong 逾時(毫秒)
267
- ping: unknown | (() => unknown); // ping payload;函式則每次動態產生
275
+ ping: unknown | (() => unknown); // ping payload;函式則每次動態產生
268
276
  isPong: (data: unknown) => boolean; // 判定傳入 data 是否為 pong
269
277
  }
270
278
  ```
@@ -287,7 +295,7 @@ createWsContext({
287
295
  });
288
296
  ```
289
297
 
290
- 探活期間,`onmessage` 收到的每一筆資料都會先經 `isPong` 判定;若為 pong 則重置逾時計時,並照常 emit `"message"` 事件。
298
+ 探活期間,`onmessage` 收到的每一筆資料都會先經 `isPong` 判定;若為 pong 則清除逾時計時,並照常 emit `"message"` 事件。ping payload 一律以 `JSON.stringify` 送出(僅支援 JSON 格式)。
291
299
 
292
300
  ---
293
301
 
@@ -295,14 +303,14 @@ createWsContext({
295
303
 
296
304
  當 `outgoingQueueMax > 0` 時:
297
305
 
298
- | 時機 | 行為 |
299
- | ------------------------ | ------------------------------ |
300
- | `send` 且 socket 未 OPEN | 訊息入隊(FIFO) |
301
- | 佇列已滿 | 回傳 `false`,**不**丟棄舊訊息 |
302
- | socket OPEN | 依序 flush 全部佇列 |
303
- | `disconnect()` | 清空佇列 |
304
- | `WsProvider` unmount | 清空佇列 |
305
- | 自動重連等待期間 | **保留**佇列 |
306
+ | 時機 | 行為 |
307
+ | ------------------------ | --------------------------------------------------------- |
308
+ | `send` 且 socket 未 OPEN | 訊息入隊(FIFO) |
309
+ | 佇列已滿 | 回傳 `false`,**不**丟棄舊訊息 |
310
+ | socket OPEN | 依序 flush 全部佇列 |
311
+ | `disconnect()` | 清空佇列;store 設為 `idle` / `closed`(見 `WsProvider`) |
312
+ | `WsProvider` unmount | 清空佇列;store 同步(見 `WsProvider`) |
313
+ | 自動重連等待期間 | **保留**佇列 |
306
314
 
307
315
  ---
308
316
 
@@ -310,14 +318,15 @@ createWsContext({
310
318
 
311
319
  自 `react-ws-context` 主入口匯出:
312
320
 
313
- | 型別 | 說明 |
314
- | ------------------------ | -------------------------------------------------- |
315
- | `CreateWsContextOptions` | `createWsContext` 的選項 |
316
- | `WsContextValue` | `useWsActions()` 回傳型別 |
317
- | `WsEvents` | 事件名稱與 handler 的型別對應 |
318
- | `WsStatus` | WebSocket 連線狀態(`WsState` 的一環) |
321
+ | 型別 | 說明 |
322
+ | ------------------------ | --------------------------------------------------- |
323
+ | `CreateWsContextOptions` | `createWsContext` 的選項 |
324
+ | `LivenessOptions` | `createWsContext` 的 `liveness` 選項 |
325
+ | `WsContextValue` | `useWsActions()` 回傳型別 |
326
+ | `WsEvents` | 事件名稱與 handler 的型別對應 |
327
+ | `WsStatus` | WebSocket 連線狀態(`WsState` 的一環) |
319
328
  | `WsPhase` | Provider 連線意圖與重連策略階段(`WsState` 的一環) |
320
- | `WsState` | 可訂閱 store 的 state 形狀(連線健康/佇列/重連) |
329
+ | `WsState` | 可訂閱 store 的 state 形狀(連線健康/佇列/重連) |
321
330
 
322
331
  ---
323
332
 
@@ -337,15 +346,15 @@ import {
337
346
  } from "react-ws-context/stall";
338
347
  ```
339
348
 
340
- | 匯出 | 說明 |
341
- | ---------------------------- | --------------------------------------------------------------- |
342
- | `STALL_MESSAGE_TYPE` | 客戶端控制訊息 type 常數(`"STALL"`) |
343
- | `STALL_ACK_TYPE` | 伺服器確認 type 常數(`"STALL_ACK"`) |
344
- | `createStallMessage(action)` | 建立可 `sendJson` 的控制訊息 |
345
- | `parseStallMessage(data)` | 從 `useWsEvents("message")` 的 `data` 解析;格式不符回傳 `null` |
346
- | `StallAction` | `"stall" \| "release"` |
347
- | `StallMessage` | `{ type: "STALL"; action: StallAction }` |
348
- | `StallAck` | `{ type: "STALL_ACK"; action: StallAction; active: boolean }` |
349
+ | 匯出 | 說明 |
350
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------ |
351
+ | `STALL_MESSAGE_TYPE` | 客戶端控制訊息 type 常數(`"STALL"`) |
352
+ | `STALL_ACK_TYPE` | 伺服器確認 type 常數(`"STALL_ACK"`)— 僅供型別使用,無內建 parser |
353
+ | `createStallMessage(action)` | 建立可 `sendJson` 的客戶端 `STALL` 訊息 |
354
+ | `parseStallMessage(data)` | 從 `useWsEvents("message")` 的 `data` 解析客戶端 `STALL` 訊息;格式不符回傳 `null`(不含 `STALL_ACK`) |
355
+ | `StallAction` | `"stall" \| "release"` |
356
+ | `StallMessage` | `{ type: "STALL"; action: StallAction }` |
357
+ | `StallAck` | `{ type: "STALL_ACK"; action: StallAction; active: boolean }` — 伺服器 ack 請自行解析 |
349
358
 
350
359
  **範例:**
351
360
 
@@ -364,14 +373,15 @@ sendJson(createStallMessage("stall"));
364
373
 
365
374
  ## 設計取捨與限制
366
375
 
367
- | 項目 | 說明 |
368
- | -------------- | ------------------------------------------------------------------------------------------ |
369
- | 設定不可變 | `url`、`reconnectMs` 等建立後固定;需換 URL 請另建 context 或手動 `disconnect` + `connect` |
370
- | 重連策略 | 固定間隔,無 exponential backoff;`reconnectMax > 0` 可限制次數 |
371
- | SSR | 不在 server 建立 `WebSocket`;`connect()` 在 `window` 不存在時為 no-op |
372
- | 錯誤狀態 | 不設 `"error"` status;請監聽 `useWsEvents("error")` |
373
- | `WsState` 範圍 | 只含連線健康/佇列/重連;訊息與業務資料不走 store |
374
- | 訊息與渲染 | 只呼叫 `useWsActions` 的元件不會因 store 或 message 重繪 |
376
+ | 項目 | 說明 |
377
+ | -------------- | ------------------------------------------------------------------------ |
378
+ | 設定不可變 | `url`、`reconnectMs` 等建立後固定;要換 URL 請另建一組 `createWsContext` |
379
+ | 重連策略 | 固定間隔,無 exponential backoff;`reconnectMax > 0` 可限制次數 |
380
+ | SSR | 不在 server 建立 `WebSocket`;`connect()` 在 `window` 不存在時為 no-op |
381
+ | 錯誤狀態 | 不設 `"error"` status;請監聽 `useWsEvents("error")` |
382
+ | `WsState` 範圍 | 只含連線健康/佇列/重連;訊息與業務資料不走 store |
383
+ | 訊息與渲染 | 只呼叫 `useWsActions` 的元件不會因 store 或 message 重繪 |
384
+ | Store 更新 | 相同欄位值重複寫入不觸發訂閱;建議以 selector 訂閱 |
375
385
 
376
386
  ---
377
387
 
@@ -390,8 +400,8 @@ sendJson(createStallMessage("stall"));
390
400
  - **作者/維護:** [pmndrs](https://github.com/pmndrs)(Poimandres)
391
401
  - **授權:** [MIT](https://github.com/pmndrs/zustand/blob/main/LICENSE)
392
402
  - **借鑑範圍:**
393
- - 外部 store(`getState` / `setState` / `subscribe` / `getInitialState`)— 靈感與行為對齊 [`vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts),非完整搬移(無 middleware、replace、initializer factory
394
- - React 訂閱 hook — 靈感來自 [`react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) 的 `useStore`(selector 必填、無 `useDebugValue`)
403
+ - 外部 store(`getState` / `setState` / `subscribe` / `getInitialState`)— 靈感與行為對齊 [`vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts),非完整搬移(無 middleware、replace、initializer factory);`setState` 另含 partial shallow dedup(值未變不通知)
404
+ - React 訂閱 hook — 靈感來自 [`react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts) 的 `useStore`(無 `useDebugValue`);公開 API 的可選 selector overload `createUseWsStore`
395
405
  - **對應原始碼:** `src/ws-context/store.ts`、`src/ws-context/use-store.ts`
396
406
 
397
407
  ### [nanoevents](https://github.com/ai/nanoevents)
@@ -400,5 +410,5 @@ sendJson(createStallMessage("stall"));
400
410
  - **授權:** [MIT](https://github.com/ai/nanoevents/blob/main/LICENSE)
401
411
  - **借鑑範圍:**
402
412
  - Typed event emitter — 執行期邏輯幾乎對齊 [`createNanoEvents`](https://github.com/ai/nanoevents/blob/main/index.js);型別為本套件收斂版
403
- - `useEmitter` 為本套件自行新增(React `useState` 包裝)
404
- - **對應原始碼:** `src/ws-context/emitter.ts`
413
+ - React 訂閱包裝 — 本套件自行新增(`ws-events.ts`)
414
+ - **對應原始碼:** `src/ws-context/emitter.ts`、`src/ws-context/ws-events.ts`
package/dist/index.d.mts CHANGED
@@ -54,15 +54,15 @@ type WsState = {
54
54
  };
55
55
  //#endregion
56
56
  //#region src/ws-context/liveness/types.d.ts
57
- /** 探活選項 */
57
+ /** 探活設定 */
58
58
  interface LivenessOptions {
59
- /** 探活間隔(ms) */
59
+ /** ping 間隔(毫秒) */
60
60
  intervalMs: number;
61
- /** 探活超時(ms) */
61
+ /** 等待 pong 逾時(毫秒) */
62
62
  timeoutMs: number;
63
- /** 探活訊息 */
63
+ /** ping 內容;函式則每次動態產生 */
64
64
  ping: unknown | (() => unknown);
65
- /** 探活回應判定 */
65
+ /** 判定傳入資料是否為 pong */
66
66
  isPong: (data: unknown) => boolean;
67
67
  }
68
68
  //#endregion
@@ -74,77 +74,78 @@ interface CreateWsContextOptions {
74
74
  /** 連線協定 */
75
75
  protocols?: string | string[];
76
76
  /**
77
- * 是否自動重新連線。
77
+ * 是否在 WsProvider 載入時自動連線。
78
78
  *
79
- * @default true
79
+ * @default true
80
80
  */
81
81
  autoConnect?: boolean;
82
82
  /**
83
- * 自動重連間隔(ms);`0` 不重連。
83
+ * 非主動斷線後,自動重連的間隔(毫秒)。
84
+ *
85
+ * `0` 表示不重連。
84
86
  *
85
87
  * @default 0
86
88
  */
87
89
  reconnectMs?: number;
88
90
  /**
89
- * 非主動斷線後最多自動重連幾次(不含首次 `autoConnect`)。
90
- * `reconnectAttempt` 於成功 `open`、手動 `connect()` 或 `disconnect()` 歸零。
91
+ * 非主動斷線後,最多自動重連幾次。
91
92
  *
92
- * `0` 不限制(只要 `reconnectMs > 0`)。
93
+ * `0` 表示不限制;需搭配 `reconnectMs > 0` 才會重連。
93
94
  *
94
95
  * @default 0
95
96
  */
96
97
  reconnectMax?: number;
97
98
  /**
98
- * 未連線時發送訊息的佇列上限;`0` 關閉。
99
+ * 未連線時,待送訊息的佇列上限。
100
+ *
101
+ * `0` 表示關閉佇列。
99
102
  *
100
103
  * @default 0
101
104
  */
102
105
  outgoingQueueMax?: number;
103
106
  /**
104
- * 資料解析函數。
107
+ * 將原始 `MessageEvent.data` 轉成業務資料。
105
108
  *
106
- * 預設處理方式:如果資料為字串,嘗試 `JSON.parse`,否則原樣返回。
109
+ * 預設:字串嘗試 `JSON.parse`,失敗則原樣回傳;非字串原樣回傳。
107
110
  */
108
111
  parse?: (data: MessageEvent["data"]) => unknown;
109
- /** 探活 */
112
+ /** 探活設定;省略則不啟用 */
110
113
  liveness?: LivenessOptions;
111
114
  }
112
115
  interface WsEvents {
113
116
  /**
114
117
  * 收到訊息
115
118
  *
116
- * @param data 訊息內容,已經過 `parse` 處理
117
- * @param event 原始事件物件
119
+ * @param data `parse` 處理後的資料
120
+ * @param event 原始 `MessageEvent`
118
121
  */
119
122
  message: (data: unknown, event: MessageEvent) => void;
120
123
  /** 連線建立 */
121
124
  open: (event: Event) => void;
122
- /** 發生錯誤 */
125
+ /** 連線錯誤 */
123
126
  error: (event: Event) => void;
124
- /** 連線斷開 */
127
+ /** 連線關閉 */
125
128
  close: (event: CloseEvent) => void;
126
129
  }
127
- /** 連線操作 API;可訂閱的連線層 state(健康/佇列/重連)請用 `useWsStore` */
130
+ /** 連線操作 API;連線層 state 請用 `useWsStore` 訂閱 */
128
131
  interface WsContextValue {
129
132
  /**
130
- * 傳送訊息
133
+ * 傳送原始資料。
131
134
  *
132
- * @param data 訊息內容
133
- * @returns 是否已送出或已入隊
135
+ * @returns `true` 表示已送出或已入隊;`false` 表示未送出
134
136
  */
135
137
  send: (data: Parameters<WebSocket["send"]>[0]) => boolean;
136
138
  /**
137
- * 傳送 JSON 資料
139
+ * JSON 傳送資料。
138
140
  *
139
- * @param data 資料
140
- * @returns 是否已送出或已入隊
141
+ * @returns `send`;無法序列化時為 `false`
141
142
  */
142
143
  sendJson: (data: unknown) => boolean;
143
144
  /** 建立連線 */
144
145
  connect: () => void;
145
- /** 斷開連線 */
146
+ /** 主動斷開連線 */
146
147
  disconnect: () => void;
147
- /** 讀取當下 `status`;不訂閱 store、不觸發渲染 */
148
+ /** 讀取當下 `status`;不訂閱、不觸發渲染 */
148
149
  getStatus: () => WsStatus;
149
150
  }
150
151
  //#endregion
@@ -166,5 +167,5 @@ declare function createWsContext(options: CreateWsContextOptions): {
166
167
  useWsEvents: <E extends keyof WsEvents>(type: E, handler: WsEvents[E]) => void;
167
168
  };
168
169
  //#endregion
169
- export { type CreateWsContextOptions, type WsContextValue, type WsEvents, type WsPhase, type WsState, type WsStatus, createWsContext };
170
+ export { type CreateWsContextOptions, type LivenessOptions, type WsContextValue, type WsEvents, type WsPhase, type WsState, type WsStatus, createWsContext };
170
171
  //# sourceMappingURL=index.d.mts.map
@@ -1 +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;;;;;;;;;;;;KAaA;;;;;;;;;;KAgBA;;EAEV,QAAQ;;EAER,OAAO;;;;;;;;EAQP;;;;;;EAMA;;;;;UC5De;;EAEf;;EAEA;;EAEA;;EAEA,SAAS;;;;;UCLM;;EAEf;;EAEA;;;;;;EAMA;;;;;;EAMA;;;;;;;;;EASA;;;;;;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;;;;;;;;;;;iBC1CH,gBAAgB,SAAS;EAmBL,eAAA,YAAA,sCAAiB,IAAA"}
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;;;;;;;;;;;;KAaA;;;;;;;;;;KAYA;;EAEV,QAAQ;;EAER,OAAO;;;;;;;;EAQP;;;;;;EAMA;;;;;UCxDe;;EAEf;;EAEA;;EAEA;;EAEA,SAAS;;;;;UCLM;;EAEf;;EAEA;;;;;;EAMA;;;;;;;;EAQA;;;;;;;;EAQA;;;;;;;;EAQA;;;;;;EAMA,SAAS,MAAM;;EAEf,WAAW;;UAGI;;;;;;;EAOf,UAAU,eAAe,OAAO;;EAEhC,OAAO,OAAO;;EAEd,QAAQ,OAAO;;EAEf,QAAQ,OAAO;;;UAIA;;;;;;EAMf,OAAO,MAAM,WAAW;;;;;;EAMxB,WAAW;;EAEX;;EAEA;;EAEA,iBAAiB;;;;;;;;;;;iBCxCH,gBAAgB,SAAS;EAmBL,eAAA,YAAA,sCAAiB,IAAA"}