@gwakko/shared-websocket 0.13.0 → 0.14.5

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.
@@ -8,6 +8,8 @@ interface SharedSocketOptions {
8
8
  reconnectMaxDelay?: number;
9
9
  /** Max reconnect attempts before giving up (default: Infinity). */
10
10
  reconnectMaxRetries?: number;
11
+ /** Close codes that mean "auth failed — stop reconnect." Default: [1008]. */
12
+ authFailureCloseCodes?: number[];
11
13
  heartbeatInterval?: number;
12
14
  sendBuffer?: number;
13
15
  auth?: () => string | Promise<string>;
@@ -34,7 +36,8 @@ export class SharedSocket implements Disposable {
34
36
 
35
37
  private reconnectAttempts = 0;
36
38
 
37
- private readonly opts: Required<Omit<SharedSocketOptions, 'auth' | 'authToken' | 'authParam' | 'pingPayload' | 'serialize' | 'deserialize'>> & {
39
+ private readonly opts: Required<Omit<SharedSocketOptions, 'auth' | 'authToken' | 'authParam' | 'pingPayload' | 'serialize' | 'deserialize' | 'authFailureCloseCodes'>> & {
40
+ authFailureCloseCodes: ReadonlySet<number>;
38
41
  auth?: () => string | Promise<string>;
39
42
  authToken?: string;
40
43
  authParam: string;
@@ -52,6 +55,7 @@ export class SharedSocket implements Disposable {
52
55
  reconnect: options.reconnect ?? true,
53
56
  reconnectMaxDelay: options.reconnectMaxDelay ?? 30_000,
54
57
  reconnectMaxRetries: options.reconnectMaxRetries ?? Infinity,
58
+ authFailureCloseCodes: new Set(options.authFailureCloseCodes ?? [1008]),
55
59
  heartbeatInterval: options.heartbeatInterval ?? 30_000,
56
60
  sendBuffer: options.sendBuffer ?? 100,
57
61
  auth: options.auth,
@@ -76,7 +80,15 @@ export class SharedSocket implements Disposable {
76
80
 
77
81
  this.setState('connecting');
78
82
 
79
- const connectUrl = await this.buildUrl();
83
+ let connectUrl: string;
84
+ try {
85
+ connectUrl = await this.buildUrl();
86
+ } catch {
87
+ // auth() threw or returned no token — pause reconnect until user
88
+ // provides fresh creds via ws.authenticate(token) or ws.reconnect().
89
+ this.setState('failed');
90
+ return;
91
+ }
80
92
  this.ws = new WebSocket(connectUrl, this.opts.protocols);
81
93
 
82
94
  this.ws.onopen = () => {
@@ -96,8 +108,14 @@ export class SharedSocket implements Disposable {
96
108
  for (const fn of this.onMessageFns) fn(data);
97
109
  };
98
110
 
99
- this.ws.onclose = () => {
111
+ this.ws.onclose = (ev) => {
100
112
  this.stopHeartbeat();
113
+ if (this.opts.authFailureCloseCodes.has(ev.code)) {
114
+ // Auth-failure close code — don't burn retries with stale creds.
115
+ // User must call ws.authenticate(freshToken) or ws.reconnect() to resume.
116
+ this.setState('failed');
117
+ return;
118
+ }
101
119
  if (!this.disposed && this.opts.reconnect) {
102
120
  this.scheduleReconnect();
103
121
  } else {
@@ -228,7 +246,14 @@ export class SharedSocket implements Disposable {
228
246
  // Resolve token: callback > static > none
229
247
  let token: string | undefined;
230
248
  if (this.opts.auth) {
249
+ // If the auth callback throws, let it propagate — connect() catches and
250
+ // pauses reconnect until the user supplies fresh creds.
231
251
  token = await this.opts.auth();
252
+ if (!token) {
253
+ // Configured auth callback returned no token. Treat as a fatal auth
254
+ // condition (don't silently connect without credentials).
255
+ throw new Error('SharedSocket: auth() returned no token');
256
+ }
232
257
  } else if (this.opts.authToken) {
233
258
  token = this.opts.authToken;
234
259
  }