@songfreedom/sse-client 0.1.0 → 0.1.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/client.d.ts CHANGED
@@ -1,97 +1,107 @@
1
1
  import { SSEError } from "./errors.js";
2
2
  import type { SSECloseReason, SSEEventMap, SSEMessage, SSEStatus, Unsubscribe } from "./types.js";
3
- /** 与 `fetch` 同形,便于注入自定义 transport(SSR、测试、带签名的请求) */
3
+ /** Same shape as `fetch`; inject a custom transport for SSR, tests or signed requests. */
4
4
  export type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
5
- /** 重试决策上下文 */
5
+ /** Context passed to a retry policy. */
6
6
  export interface RetryContext {
7
- /** 第几次重试,从 1 开始 */
7
+ /** Retry number, starting at 1. */
8
8
  attempt: number;
9
- /** 触发重连的原因;服务端正常结束流时为 undefined */
9
+ /** Why the reconnect happens; `undefined` when the server closed the stream normally. */
10
10
  error?: SSEError;
11
- /** 本次连接存活时长(毫秒) */
11
+ /** How long the attempt was alive, in milliseconds. */
12
12
  elapsed: number;
13
13
  }
14
14
  /**
15
- * 自定义重试策略:返回下次重连延迟(毫秒),返回 `false` 表示终止。
16
- * 支持 async,便于「401 → 先刷新 token → 再重试」这类需要等待的决策。
17
- * 不传则使用内置的「指数退避 + 抖动 + 最大次数」。
15
+ * Custom retry policy: return the delay in milliseconds before the next attempt,
16
+ * or `false` to give up. May be async, which makes flows such as
17
+ * "401 → refresh the token → retry" straightforward. When omitted, the built-in
18
+ * exponential backoff with jitter and a max attempt count is used.
18
19
  */
19
20
  export type RetryPolicy = (context: RetryContext) => number | false | Promise<number | false>;
20
- /** 客户端配置 */
21
+ /** Client options. */
21
22
  export interface SSEClientOptions {
22
- /** 请求地址(需自行带上网关前缀) */
23
+ /** Request URL (include your gateway prefix yourself). */
23
24
  url: string;
24
- /** 请求方法,业务推送一般用 POST */
25
+ /** HTTP method; business push endpoints usually use POST. */
25
26
  method?: "GET" | "POST";
26
- /** 请求体,非 GET 时按 JSON 序列化;也支持直接传字符串 / Blob / FormData */
27
+ /** Request body. Serialized as JSON, or passed through when it is a string / Blob / FormData. */
27
28
  body?: unknown;
28
29
  /**
29
- * 额外请求头。传函数则每次(重)连都重新取,
30
- * 便于拿到最新 token,因此推荐 `() => getCommonHeaders()`。
30
+ * Extra headers. Pass a function to recompute them before every (re)connect,
31
+ * which is the recommended way to pick up a refreshed token:
32
+ * `() => getAuthHeaders()`.
31
33
  */
32
34
  headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
33
- /** data 解析,例如 `(raw) => JSON.parse(raw)`;不传则 data 为原始字符串 */
35
+ /** Payload decoder, e.g. `(raw) => JSON.parse(raw)`. Defaults to the raw string. */
34
36
  parse?: (raw: string) => unknown;
35
- /** 空闲超时(毫秒):多久没收到任何字节就断开重连,0 表示关闭。建议取心跳间隔的 2~3 倍 */
37
+ /**
38
+ * Idle timeout in milliseconds: reconnect when no bytes arrived for this long.
39
+ * `0` disables it. Two to three times the server heartbeat interval is a good value.
40
+ */
36
41
  idleTimeout?: number;
37
- /** 主动轮换间隔(毫秒):到点后主动重连,防止连接假活,0 表示关闭 */
42
+ /** Proactive rotation interval in milliseconds: reconnect on schedule to avoid zombie connections. `0` disables it. */
38
43
  rotateInterval?: number;
39
- /** 自定义重试策略,覆盖内置退避 */
44
+ /** Custom retry policy, overrides the built-in backoff. */
40
45
  retry?: RetryPolicy;
41
- /** 最大重试次数,默认无限(仅在未传 `retry` 时生效) */
46
+ /** Maximum retry count, unlimited by default (only used when `retry` is not provided). */
42
47
  maxRetry?: number;
43
- /** 首次重连延迟(毫秒),默认 1000 */
48
+ /** Delay before the first retry in milliseconds, defaults to 1000. */
44
49
  retryBaseDelay?: number;
45
- /** 重连延迟上限(毫秒),默认 30000 */
50
+ /** Upper bound for reconnect delays in milliseconds, defaults to 30000. */
46
51
  retryMaxDelay?: number;
47
- /** 服务端正常结束流后是否自动重连,默认 true */
52
+ /** Reconnect when the server closes the stream normally, defaults to true. */
48
53
  reconnectOnEnd?: boolean;
49
- /** 连接存活超过该时长即视为稳定,重置重试计数,默认 10000 */
54
+ /** An attempt that lived at least this long resets the retry counter, defaults to 10000. */
50
55
  stableAfter?: number;
51
56
  /**
52
- * 强校验响应 `content-type` 必须是 `text/event-stream`,默认 true。
53
- * 能拦住「网关/鉴权页返回 200 HTML」这类静默失败;后端不规范时可关闭。
57
+ * Require the response `content-type` to be `text/event-stream`, defaults to true.
58
+ * This catches silent failures such as a gateway returning a 200 HTML page;
59
+ * disable it for non-compliant backends.
54
60
  */
55
61
  strictContentType?: boolean;
56
- /** 单条消息缓冲上限(字符数),默认 1MB,防止异常流撑爆内存 */
62
+ /** Cap for a single message buffer in characters, defaults to 1MB, guards against runaway streams. */
57
63
  maxBufferSize?: number;
58
64
  /**
59
- * 流结束哨兵:data 等于该值时视为正常结束(OpenAI / AI SDK 风格用 `[DONE]`),
60
- * 触发后按「正常结束」收尾且不再重连。默认不启用。
65
+ * Stream-end sentinel: when `data` equals this value the stream is treated as
66
+ * finished (OpenAI / AI SDK style `[DONE]`) and no reconnect happens.
67
+ * Disabled by default.
61
68
  */
62
69
  endSignal?: string;
63
- /** 事件白名单:只投递这些事件名,缺省全部投递 */
70
+ /** Event allow-list: only these event names are dispatched. All events are dispatched when omitted. */
64
71
  events?: string[];
65
72
  /**
66
- * 页面隐藏时挂起连接、回到前台自动重连,默认 false。
67
- * WebView / 移动端建议开启(后台会被系统挂起,留着连接只会变僵尸)。
73
+ * Suspend the connection while the page is hidden and reconnect when it becomes
74
+ * visible again. Defaults to false; recommended for WebViews and mobile, where
75
+ * the OS freezes background pages and leaves zombie connections behind.
68
76
  */
69
77
  pauseWhenHidden?: boolean;
70
- /** 是否在构造后立即连接,默认 true */
78
+ /** Connect in the constructor, defaults to true. */
71
79
  autoStart?: boolean;
72
- /** 注入 fetch 实现,默认使用全局 fetch */
80
+ /** Inject a fetch implementation, defaults to the global `fetch`. */
73
81
  fetch?: FetchLike;
74
- /** 事件处理函数抛错时的兜底,默认打到控制台 */
82
+ /** Fallback when a handler throws, defaults to `console.error`. */
75
83
  onHandlerError?: (error: unknown) => void;
76
- /** 连接建立(已收到响应头)时触发,等价于 `client.onOpen()` */
84
+ /** Fired once the connection is established (response headers received). Same as `client.onOpen()`. */
77
85
  onOpen?: (response: Response) => void;
78
- /** 所有消息都会触发,等价于 `client.onMessage()` */
86
+ /** Fired for every message. Same as `client.onMessage()`. */
79
87
  onMessage?: (message: SSEMessage) => void;
80
- /** 状态变化,等价于 `client.onStatus()` */
88
+ /** Fired on every state change. Same as `client.onStatus()`. */
81
89
  onStatus?: (status: SSEStatus) => void;
82
- /** 每次失败触发,等价于 `client.onError()` */
90
+ /** Fired for every failed attempt. Same as `client.onError()`. */
83
91
  onError?: (error: SSEError, attempt: number) => void;
84
- /** 最终关闭时触发一次,等价于 `client.onClose()` */
92
+ /** Fired once when the connection is finally closed. Same as `client.onClose()`. */
85
93
  onClose?: (reason: SSECloseReason, error?: SSEError) => void;
86
94
  }
87
95
  /**
88
- * SSE 客户端。
96
+ * Server-Sent Events client.
89
97
  *
90
- * - 基于 `fetch` + `ReadableStream`,支持自定义请求头与 POST body
91
- * - 断线自动重连(指数退避 + 抖动),401 等不可重试错误直接终止
92
- * - 用 `Last-Event-ID` 做断点续传
93
- * - 按事件名路由到多个订阅者,一条连接服务多个组件
94
- * - 空闲看门狗、主动轮换、断网/切后台挂起、连接共享(见 `acquireSSEClient`)
98
+ * - Built on `fetch` + `ReadableStream`, so custom headers and POST bodies work
99
+ * - Reconnects automatically (exponential backoff with jitter); terminal errors
100
+ * such as 401 stop immediately
101
+ * - Resumes with `Last-Event-ID`
102
+ * - Routes events by name to multiple subscribers, so one connection serves many components
103
+ * - Idle watchdog, proactive rotation, offline/background suspension, shared
104
+ * connections (see `acquireSSEClient`)
95
105
  */
96
106
  export declare class SSEClient<E extends object = SSEEventMap> {
97
107
  private readonly options;
@@ -123,29 +133,29 @@ export declare class SSEClient<E extends object = SSEEventMap> {
123
133
  private readonly openHandlers;
124
134
  private readonly closeHandlers;
125
135
  constructor(options: SSEClientOptions);
126
- /** 当前状态 */
136
+ /** Current state. */
127
137
  get status(): SSEStatus;
128
- /** 是否已最终关闭(不会再重连) */
138
+ /** Whether the client is finally closed and will not reconnect. */
129
139
  get closed(): boolean;
130
- /** 连续重试次数,连接稳定后归零 */
140
+ /** Consecutive retry count; resets once a connection proves stable. */
131
141
  get attempts(): number;
132
- /** 最近一次事件 id,重连时通过 Last-Event-ID 带给服务端 */
142
+ /** Latest event id, sent back as `Last-Event-ID` on reconnect. */
133
143
  get lastEventId(): string;
134
- /** 连接终止(无论正常关闭还是放弃重连)后 resolve,不会 reject */
144
+ /** Resolves when the connection terminates (closed or retries exhausted). Never rejects. */
135
145
  get done(): Promise<void>;
136
- /** 建立连接;已连接或已挂起时调用无副作用 */
146
+ /** Open the connection. No-op while already running or suspended. */
137
147
  start(): this;
138
- /** 主动关闭,不再重连 */
148
+ /** Close the connection and stop reconnecting. */
139
149
  close(): void;
140
- /** 关闭后重新连接,保留 lastEventId 以便续传 */
150
+ /** Reconnect after closing, keeping `lastEventId` so the server can resume. */
141
151
  restart(): this;
142
- /** 订阅指定事件名的消息,返回取消订阅函数 */
152
+ /** Subscribe to one event name; returns an unsubscribe function. */
143
153
  on<K extends Extract<keyof E, string>>(event: K, handler: (data: E[K], message: SSEMessage) => void): Unsubscribe;
144
- /** 订阅所有消息(event 为 "*") */
154
+ /** Subscribe to every message by passing `"*"`. */
145
155
  on(event: "*", handler: (data: unknown, message: SSEMessage) => void): Unsubscribe;
146
- /** 只订阅一次,触发后自动退订 */
156
+ /** Subscribe to one event name for a single delivery. */
147
157
  once<K extends Extract<keyof E, string>>(event: K, handler: (data: E[K], message: SSEMessage) => void): Unsubscribe;
148
- /** 订阅所有消息(不区分事件名) */
158
+ /** Subscribe to every message regardless of event name. */
149
159
  onMessage(handler: (message: SSEMessage) => void): Unsubscribe;
150
160
  onStatus(handler: (status: SSEStatus) => void): Unsubscribe;
151
161
  onError(handler: (error: SSEError, attempt: number) => void): Unsubscribe;
@@ -158,7 +168,7 @@ export declare class SSEClient<E extends object = SSEEventMap> {
158
168
  private get reconnectOnEnd();
159
169
  private reset;
160
170
  private connect;
161
- /** 建立一次连接并持续读取,直到流结束(失败时抛错) */
171
+ /** Open one connection and read it until the stream ends (throws on failure). */
162
172
  private openOnce;
163
173
  private resolveHeaders;
164
174
  private handleParsedEvent;
@@ -167,7 +177,7 @@ export declare class SSEClient<E extends object = SSEEventMap> {
167
177
  private safeCall;
168
178
  private reportHandlerError;
169
179
  private decideDelay;
170
- /** 重连延迟:服务端 `retry:` 优先,否则指数退避 + ±20% 抖动 */
180
+ /** Reconnect delay: the server's `retry:` wins, otherwise exponential backoff with ±20% jitter. */
171
181
  private nextDelay;
172
182
  private sleep;
173
183
  private waitForResume;
@@ -177,7 +187,7 @@ export declare class SSEClient<E extends object = SSEEventMap> {
177
187
  private clearRotate;
178
188
  private clearTimers;
179
189
  private setState;
180
- /** 网络上下线始终启用:断网期间不重连,恢复后立即重连 */
190
+ /** Network state is always observed: no reconnects while offline, an immediate one when back online. */
181
191
  private bindLifecycle;
182
192
  private unbindLifecycle;
183
193
  private suspend;
package/client.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/client.js CHANGED
@@ -1,16 +1,18 @@
1
1
  import { SSEError, isRetryableStatus, toSSEError } from "./errors.js";
2
2
  import { DEFAULT_MAX_BUFFER_SIZE, createSSEParser, } from "./parser.js";
3
3
  const EVENT_STREAM_CONTENT_TYPE = "text/event-stream";
4
- /** 防止「刚连上就断」时形成紧循环 */
4
+ /** Prevents tight reconnect loops when the server drops the connection immediately. */
5
5
  const MIN_RETRY_DELAY = 200;
6
6
  /**
7
- * SSE 客户端。
7
+ * Server-Sent Events client.
8
8
  *
9
- * - 基于 `fetch` + `ReadableStream`,支持自定义请求头与 POST body
10
- * - 断线自动重连(指数退避 + 抖动),401 等不可重试错误直接终止
11
- * - 用 `Last-Event-ID` 做断点续传
12
- * - 按事件名路由到多个订阅者,一条连接服务多个组件
13
- * - 空闲看门狗、主动轮换、断网/切后台挂起、连接共享(见 `acquireSSEClient`)
9
+ * - Built on `fetch` + `ReadableStream`, so custom headers and POST bodies work
10
+ * - Reconnects automatically (exponential backoff with jitter); terminal errors
11
+ * such as 401 stop immediately
12
+ * - Resumes with `Last-Event-ID`
13
+ * - Routes events by name to multiple subscribers, so one connection serves many components
14
+ * - Idle watchdog, proactive rotation, offline/background suspension, shared
15
+ * connections (see `acquireSSEClient`)
14
16
  */
15
17
  export class SSEClient {
16
18
  options;
@@ -43,7 +45,7 @@ export class SSEClient {
43
45
  closeHandlers = new Set();
44
46
  constructor(options) {
45
47
  if (!options?.url) {
46
- throw new Error("[sse-client] 缺少 url");
48
+ throw new Error("[sse-client] missing required option: url");
47
49
  }
48
50
  this.options = options;
49
51
  this.parser = createSSEParser({
@@ -68,27 +70,27 @@ export class SSEClient {
68
70
  this.start();
69
71
  }
70
72
  }
71
- /** 当前状态 */
73
+ /** Current state. */
72
74
  get status() {
73
75
  return this.state;
74
76
  }
75
- /** 是否已最终关闭(不会再重连) */
77
+ /** Whether the client is finally closed and will not reconnect. */
76
78
  get closed() {
77
79
  return this.finished;
78
80
  }
79
- /** 连续重试次数,连接稳定后归零 */
81
+ /** Consecutive retry count; resets once a connection proves stable. */
80
82
  get attempts() {
81
83
  return this.attempt;
82
84
  }
83
- /** 最近一次事件 id,重连时通过 Last-Event-ID 带给服务端 */
85
+ /** Latest event id, sent back as `Last-Event-ID` on reconnect. */
84
86
  get lastEventId() {
85
87
  return this.lastEventIdValue;
86
88
  }
87
- /** 连接终止(无论正常关闭还是放弃重连)后 resolve,不会 reject */
89
+ /** Resolves when the connection terminates (closed or retries exhausted). Never rejects. */
88
90
  get done() {
89
91
  return this.runPromise ?? Promise.resolve();
90
92
  }
91
- /** 建立连接;已连接或已挂起时调用无副作用 */
93
+ /** Open the connection. No-op while already running or suspended. */
92
94
  start() {
93
95
  if (this.running || this.suspended) {
94
96
  return this;
@@ -96,7 +98,8 @@ export class SSEClient {
96
98
  if (this.finished) {
97
99
  this.reset();
98
100
  }
99
- // 页面本来就不可见时先不发请求,等 visibilitychange 再建连(WebView 预加载场景常见)
101
+ // Do not request anything while the page is hidden; wait for visibilitychange.
102
+ // This is the usual case for WebView preloading.
100
103
  if (this.options.pauseWhenHidden &&
101
104
  typeof document !== "undefined" &&
102
105
  document.hidden) {
@@ -108,7 +111,7 @@ export class SSEClient {
108
111
  this.bindLifecycle();
109
112
  this.runPromise = this.connect()
110
113
  .catch((error) => {
111
- // 兜底:调用方回调抛错也不能变成未处理的 Promise 拒绝
114
+ // Safety net: a throwing callback must not become an unhandled rejection.
112
115
  this.finish("error", toSSEError(error));
113
116
  })
114
117
  .finally(() => {
@@ -116,14 +119,14 @@ export class SSEClient {
116
119
  });
117
120
  return this;
118
121
  }
119
- /** 主动关闭,不再重连 */
122
+ /** Close the connection and stop reconnecting. */
120
123
  close() {
121
124
  if (this.finished) {
122
125
  return;
123
126
  }
124
127
  this.finish("user");
125
128
  }
126
- /** 关闭后重新连接,保留 lastEventId 以便续传 */
129
+ /** Reconnect after closing, keeping `lastEventId` so the server can resume. */
127
130
  restart() {
128
131
  this.close();
129
132
  return this.start();
@@ -146,7 +149,7 @@ export class SSEClient {
146
149
  }
147
150
  };
148
151
  }
149
- /** 只订阅一次,触发后自动退订 */
152
+ /** Subscribe to one event name for a single delivery. */
150
153
  once(event, handler) {
151
154
  const off = this.on(event, (data, message) => {
152
155
  off();
@@ -154,7 +157,7 @@ export class SSEClient {
154
157
  });
155
158
  return off;
156
159
  }
157
- /** 订阅所有消息(不区分事件名) */
160
+ /** Subscribe to every message regardless of event name. */
158
161
  onMessage(handler) {
159
162
  return subscribe(this.messageHandlers, handler);
160
163
  }
@@ -171,7 +174,7 @@ export class SSEClient {
171
174
  return subscribe(this.closeHandlers, handler);
172
175
  }
173
176
  // ---------------------------------------------------------------------------
174
- // 内部实现
177
+ // Internals
175
178
  // ---------------------------------------------------------------------------
176
179
  get retryBaseDelay() {
177
180
  return this.options.retryBaseDelay ?? 1000;
@@ -203,7 +206,7 @@ export class SSEClient {
203
206
  async connect() {
204
207
  while (!this.finished) {
205
208
  if (this.suspended) {
206
- // 挂起期间不重连,等 resume() 唤醒
209
+ // Do not reconnect while suspended; wait for resume() to wake us up.
207
210
  await this.waitForResume();
208
211
  continue;
209
212
  }
@@ -227,19 +230,24 @@ export class SSEClient {
227
230
  this.setState("idle");
228
231
  continue;
229
232
  }
230
- // 主动轮换:立即重连并保留 lastEventId,不走退避
233
+ // Proactive rotation: reconnect immediately, keeping lastEventId and skipping backoff.
231
234
  if (this.rotating) {
232
235
  this.rotating = false;
233
236
  this.attempt = 0;
234
237
  continue;
235
238
  }
236
- // 空闲超时按「可重试的网络异常」处理,避免心跳缺失时连接假死
239
+ // Treat an idle timeout as a retryable network error so a missing
240
+ // heartbeat cannot leave a zombie connection behind.
237
241
  if (this.idleTimedOut) {
238
242
  this.idleTimedOut = false;
239
- failure = new SSEError("SSE 连接空闲超时,准备重连", { kind: "heartbeat", retryable: true });
243
+ failure = new SSEError("SSE idle timeout, reconnecting", {
244
+ kind: "heartbeat",
245
+ retryable: true,
246
+ });
240
247
  }
241
248
  const elapsed = Date.now() - startedAt;
242
- // 连接存活足够久,说明本次失败是偶发的,重置重试计数
249
+ // A connection that lived long enough means the failure was transient,
250
+ // so reset the retry budget.
243
251
  if (elapsed >= this.stableAfter) {
244
252
  this.attempt = 0;
245
253
  }
@@ -264,7 +272,7 @@ export class SSEClient {
264
272
  await this.sleep(delay);
265
273
  }
266
274
  }
267
- /** 建立一次连接并持续读取,直到流结束(失败时抛错) */
275
+ /** Open one connection and read it until the stream ends (throws on failure). */
268
276
  async openOnce(signal) {
269
277
  const { url, method = "POST", body, fetch: fetchImpl } = this.options;
270
278
  const init = {
@@ -277,7 +285,7 @@ export class SSEClient {
277
285
  };
278
286
  const response = fetchImpl ? await fetchImpl(url, init) : await fetch(url, init);
279
287
  if (!response.ok) {
280
- throw new SSEError(`SSE 请求失败:HTTP ${response.status}`, {
288
+ throw new SSEError(`SSE request failed: HTTP ${response.status}`, {
281
289
  kind: "http",
282
290
  status: response.status,
283
291
  retryable: isRetryableStatus(response.status),
@@ -286,12 +294,13 @@ export class SSEClient {
286
294
  if (this.options.strictContentType !== false) {
287
295
  const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
288
296
  if (!contentType.startsWith(EVENT_STREAM_CONTENT_TYPE)) {
289
- // 最典型的两种原因:被网关/鉴权页拦截返回 HTML,或后端没设 produces
290
- throw new SSEError(`SSE 响应的 content-type 不是 ${EVENT_STREAM_CONTENT_TYPE},实际为 "${contentType || "(空)"}"`, { kind: "protocol", status: response.status, retryable: false });
297
+ // Two usual causes: a gateway/auth page returned HTML, or the backend
298
+ // did not declare produces.
299
+ throw new SSEError(`SSE response content-type is not ${EVENT_STREAM_CONTENT_TYPE} (got "${contentType || "(empty)"}")`, { kind: "protocol", status: response.status, retryable: false });
291
300
  }
292
301
  }
293
302
  if (!response.body) {
294
- throw new SSEError("当前环境不支持流式响应读取(response.body 为空)", {
303
+ throw new SSEError("Streaming is not supported in this environment (response.body is null)", {
295
304
  kind: "protocol",
296
305
  status: response.status,
297
306
  retryable: false,
@@ -302,7 +311,7 @@ export class SSEClient {
302
311
  this.emit(this.openHandlers, response);
303
312
  this.parser.reset();
304
313
  const reader = response.body.getReader();
305
- // 用 stream 模式解码,避免多字节字符被切在两个 chunk 之间时出现乱码
314
+ // Decode in streaming mode so multi-byte characters split across chunks survive.
306
315
  const decoder = new TextDecoder("utf-8");
307
316
  this.armIdle();
308
317
  try {
@@ -314,8 +323,9 @@ export class SSEClient {
314
323
  this.armIdle();
315
324
  this.parser.feed(decoder.decode(value, { stream: true }));
316
325
  if (this.parser.overflowed) {
317
- // 服务端从不发空行或单条消息过大,重连也不会好转
318
- throw new SSEError("SSE 数据超出解析缓冲上限,已重置连接", {
326
+ // The server never terminates a line, or a single message is huge;
327
+ // reconnecting would not help.
328
+ throw new SSEError("SSE buffer size limit exceeded, connection reset", {
319
329
  kind: "protocol",
320
330
  retryable: false,
321
331
  });
@@ -328,7 +338,7 @@ export class SSEClient {
328
338
  reader.releaseLock();
329
339
  }
330
340
  catch {
331
- // abort 之后 releaseLock 可能抛错,忽略即可
341
+ // releaseLock can throw after an abort; that is fine.
332
342
  }
333
343
  this.clearIdle();
334
344
  this.clearRotate();
@@ -350,7 +360,8 @@ export class SSEClient {
350
360
  return resolved;
351
361
  }
352
362
  handleParsedEvent(event) {
353
- // 结束哨兵要在 parse 之前判断:`[DONE]` 不是合法 JSON,先解析会直接报错
363
+ // Check the end sentinel before parsing: `[DONE]` is not valid JSON and
364
+ // would make the parser throw.
354
365
  if (this.options.endSignal !== undefined && event.data === this.options.endSignal) {
355
366
  this.finish("end");
356
367
  return;
@@ -370,7 +381,7 @@ export class SSEClient {
370
381
  this.lastEventIdValue = id;
371
382
  }
372
383
  const name = event.event || "message";
373
- // 白名单过滤放在 id 更新之后,保证续传位置不回退
384
+ // Filter after updating the id so the resume position never moves backwards.
374
385
  if (this.eventFilter && !this.eventFilter.has(name)) {
375
386
  return;
376
387
  }
@@ -417,7 +428,7 @@ export class SSEClient {
417
428
  this.options.onHandlerError(error);
418
429
  return;
419
430
  }
420
- console.error("[sse-client] 回调函数抛出异常", error);
431
+ console.error("[sse-client] a callback threw", error);
421
432
  }
422
433
  async decideDelay(context) {
423
434
  if (this.options.retry) {
@@ -431,7 +442,7 @@ export class SSEClient {
431
442
  }
432
443
  return this.nextDelay(context.attempt);
433
444
  }
434
- /** 重连延迟:服务端 `retry:` 优先,否则指数退避 + ±20% 抖动 */
445
+ /** Reconnect delay: the server's `retry:` wins, otherwise exponential backoff with ±20% jitter. */
435
446
  nextDelay(attempt) {
436
447
  if (this.serverRetryDelay !== undefined) {
437
448
  return Math.max(Math.min(this.serverRetryDelay, this.retryMaxDelay), MIN_RETRY_DELAY);
@@ -462,7 +473,7 @@ export class SSEClient {
462
473
  return Promise.resolve();
463
474
  }
464
475
  return new Promise((resolve) => {
465
- // 只由 resume() / finish() 唤醒,循环因此不会在挂起时退出
476
+ // Only resume() / finish() wake this up, so the loop never exits while suspended.
466
477
  this.resumeWaiter = resolve;
467
478
  });
468
479
  }
@@ -512,9 +523,9 @@ export class SSEClient {
512
523
  this.emit(this.statusHandlers, next);
513
524
  }
514
525
  // ---------------------------------------------------------------------------
515
- // 断网 / 切后台
526
+ // Offline / background handling
516
527
  // ---------------------------------------------------------------------------
517
- /** 网络上下线始终启用:断网期间不重连,恢复后立即重连 */
528
+ /** Network state is always observed: no reconnects while offline, an immediate one when back online. */
518
529
  bindLifecycle() {
519
530
  if (this.lifecycleBound) {
520
531
  return;
@@ -590,21 +601,21 @@ export class SSEClient {
590
601
  this.unbindLifecycle();
591
602
  this.cancelController.abort();
592
603
  this.attemptController?.abort();
593
- // 唤醒可能卡在「挂起等待」里的连接循环
604
+ // Wake the connection loop in case it is waiting on a suspended state.
594
605
  this.resumeWaiter?.();
595
606
  this.resumeWaiter = null;
596
607
  this.setState("closed");
597
608
  this.emit(this.closeHandlers, reason, error);
598
609
  }
599
610
  }
600
- /** 请求体序列化:字符串 / Blob / FormData 原样发送,其余按 JSON */
611
+ /** Serialize the body: strings / Blob / FormData pass through, everything else becomes JSON. */
601
612
  function serializeBody(body) {
602
613
  if (typeof body === "string" || body instanceof Blob || body instanceof FormData) {
603
614
  return body;
604
615
  }
605
616
  return JSON.stringify(body);
606
617
  }
607
- /** 统一的订阅/退订实现 */
618
+ /** Shared subscribe/unsubscribe implementation. */
608
619
  function subscribe(handlers, handler) {
609
620
  handlers.add(handler);
610
621
  return () => {