@kadoa/node-sdk 0.23.0 → 0.24.2

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/dist/index.d.mts CHANGED
@@ -385,13 +385,9 @@ type MonitoredFieldOperatorEnum = typeof MonitoredFieldOperatorEnum[keyof typeof
385
385
  */
386
386
  interface MonitoringConfig {
387
387
  /**
388
- * Whether monitoring is enabled
388
+ * Fields to monitor (at least one required)
389
389
  */
390
- 'enabled': boolean;
391
- /**
392
- * Fields to monitor
393
- */
394
- 'fields'?: Array<MonitoredField>;
390
+ 'fields': Array<MonitoredField>;
395
391
  /**
396
392
  * Notification channels
397
393
  */
@@ -1182,10 +1178,38 @@ interface CreateCrawlerConfigRequestBlueprintInner {
1182
1178
  interface CreateCrawlerConfigRequestCrawlMethod {
1183
1179
  [key: string]: any;
1184
1180
  /**
1185
- * Crawler execution type (e.g. puppeteer, impit)
1181
+ * Worker selection mode. \'auto\' (default) picks cheapest compatible worker, \'browser\' forces browser.
1186
1182
  */
1187
- 'type': string;
1183
+ 'mode'?: CreateCrawlerConfigRequestCrawlMethodModeEnum;
1184
+ /**
1185
+ * Explicit Scrape V2 worker override. BrightData is blocked in crawler runtime.
1186
+ */
1187
+ 'worker'?: CreateCrawlerConfigRequestCrawlMethodWorkerEnum;
1188
+ /**
1189
+ * Explicit proxy routing override for Scrape V2
1190
+ */
1191
+ 'proxy'?: CreateCrawlerConfigRequestCrawlMethodProxyEnum;
1188
1192
  }
1193
+ declare const CreateCrawlerConfigRequestCrawlMethodModeEnum: {
1194
+ readonly Auto: "auto";
1195
+ readonly Browser: "browser";
1196
+ };
1197
+ type CreateCrawlerConfigRequestCrawlMethodModeEnum = typeof CreateCrawlerConfigRequestCrawlMethodModeEnum[keyof typeof CreateCrawlerConfigRequestCrawlMethodModeEnum];
1198
+ declare const CreateCrawlerConfigRequestCrawlMethodWorkerEnum: {
1199
+ readonly Auto: "auto";
1200
+ readonly CurlCffi: "curl-cffi";
1201
+ readonly Patchright: "patchright";
1202
+ readonly Camoufox: "camoufox";
1203
+ readonly Brightdata: "brightdata";
1204
+ };
1205
+ type CreateCrawlerConfigRequestCrawlMethodWorkerEnum = typeof CreateCrawlerConfigRequestCrawlMethodWorkerEnum[keyof typeof CreateCrawlerConfigRequestCrawlMethodWorkerEnum];
1206
+ declare const CreateCrawlerConfigRequestCrawlMethodProxyEnum: {
1207
+ readonly Auto: "auto";
1208
+ readonly Dc: "dc";
1209
+ readonly Resi: "resi";
1210
+ readonly Isp: "isp";
1211
+ };
1212
+ type CreateCrawlerConfigRequestCrawlMethodProxyEnum = typeof CreateCrawlerConfigRequestCrawlMethodProxyEnum[keyof typeof CreateCrawlerConfigRequestCrawlMethodProxyEnum];
1189
1213
 
1190
1214
  /**
1191
1215
  * Kadoa API
@@ -4137,6 +4161,10 @@ interface V4ChangesGet200ResponseChangesInner {
4137
4161
  * URL where the change was detected
4138
4162
  */
4139
4163
  'url'?: string;
4164
+ /**
4165
+ * AI-generated one-sentence summary of the data change. Null if generation failed, feature is disabled, or change predates the feature.
4166
+ */
4167
+ 'summary'?: string | null;
4140
4168
  /**
4141
4169
  * URL of the screenshot taken when the change was detected
4142
4170
  */
@@ -5742,13 +5770,9 @@ type V4WorkflowsWorkflowIdMetadataPutRequestMonitoringFieldsInnerOperatorEnum =
5742
5770
  */
5743
5771
 
5744
5772
  /**
5745
- * The new monitoring config for the workflow
5773
+ * The monitoring config for the workflow
5746
5774
  */
5747
5775
  interface V4WorkflowsWorkflowIdMetadataPutRequestMonitoring {
5748
- /**
5749
- * Whether monitoring is enabled for the workflow
5750
- */
5751
- 'enabled'?: boolean;
5752
5776
  'fields'?: Array<V4WorkflowsWorkflowIdMetadataPutRequestMonitoringFieldsInner>;
5753
5777
  'conditions'?: V4WorkflowsWorkflowIdMetadataPutRequestMonitoringConditions;
5754
5778
  }
@@ -10119,6 +10143,7 @@ interface RealtimeEvent {
10119
10143
  message: unknown;
10120
10144
  id?: string;
10121
10145
  timestamp: number;
10146
+ _cursor?: string;
10122
10147
  }
10123
10148
  interface RealtimeConfig {
10124
10149
  apiKey: string;
@@ -10127,7 +10152,10 @@ interface RealtimeConfig {
10127
10152
  missedHeartbeatsLimit?: number;
10128
10153
  }
10129
10154
  declare class Realtime {
10130
- private socket?;
10155
+ private static readonly DEFAULT_RECONNECT_DELAY_MS;
10156
+ private static readonly MAX_RECONNECT_DELAY_MS;
10157
+ private activeSocket?;
10158
+ private drainingSockets;
10131
10159
  private heartbeatInterval;
10132
10160
  private reconnectDelay;
10133
10161
  private lastHeartbeat;
@@ -10138,8 +10166,26 @@ declare class Realtime {
10138
10166
  private eventListeners;
10139
10167
  private connectionListeners;
10140
10168
  private errorListeners;
10169
+ private isClosed;
10170
+ private lastCursor?;
10171
+ private reconnectTimer?;
10172
+ private hasConnectedOnce;
10173
+ private readonly recentEventIds;
10174
+ private readonly recentEventIdQueue;
10175
+ private readonly maxRecentEventIds;
10141
10176
  constructor(config: RealtimeConfig);
10142
10177
  connect(): Promise<void>;
10178
+ private getOAuthToken;
10179
+ private openSocket;
10180
+ private promoteSocket;
10181
+ private handleSocketMessage;
10182
+ private handleDrainSignal;
10183
+ private handleSocketClose;
10184
+ private handleUnexpectedDisconnect;
10185
+ private scheduleReconnect;
10186
+ private scheduleDrainReconnect;
10187
+ private normalizeReconnectDelay;
10188
+ private isDuplicateEvent;
10143
10189
  private handleHeartbeat;
10144
10190
  private notifyEventListeners;
10145
10191
  private notifyConnectionListeners;
@@ -10370,8 +10416,8 @@ declare class ApiRegistry {
10370
10416
  interface TeamInfo {
10371
10417
  id: string;
10372
10418
  name: string;
10373
- role: string;
10374
10419
  memberRole: string;
10420
+ adminEmail: string | null;
10375
10421
  }
10376
10422
  interface BearerAuthOptions {
10377
10423
  bearerToken: string;
package/dist/index.d.ts CHANGED
@@ -385,13 +385,9 @@ type MonitoredFieldOperatorEnum = typeof MonitoredFieldOperatorEnum[keyof typeof
385
385
  */
386
386
  interface MonitoringConfig {
387
387
  /**
388
- * Whether monitoring is enabled
388
+ * Fields to monitor (at least one required)
389
389
  */
390
- 'enabled': boolean;
391
- /**
392
- * Fields to monitor
393
- */
394
- 'fields'?: Array<MonitoredField>;
390
+ 'fields': Array<MonitoredField>;
395
391
  /**
396
392
  * Notification channels
397
393
  */
@@ -1182,10 +1178,38 @@ interface CreateCrawlerConfigRequestBlueprintInner {
1182
1178
  interface CreateCrawlerConfigRequestCrawlMethod {
1183
1179
  [key: string]: any;
1184
1180
  /**
1185
- * Crawler execution type (e.g. puppeteer, impit)
1181
+ * Worker selection mode. \'auto\' (default) picks cheapest compatible worker, \'browser\' forces browser.
1186
1182
  */
1187
- 'type': string;
1183
+ 'mode'?: CreateCrawlerConfigRequestCrawlMethodModeEnum;
1184
+ /**
1185
+ * Explicit Scrape V2 worker override. BrightData is blocked in crawler runtime.
1186
+ */
1187
+ 'worker'?: CreateCrawlerConfigRequestCrawlMethodWorkerEnum;
1188
+ /**
1189
+ * Explicit proxy routing override for Scrape V2
1190
+ */
1191
+ 'proxy'?: CreateCrawlerConfigRequestCrawlMethodProxyEnum;
1188
1192
  }
1193
+ declare const CreateCrawlerConfigRequestCrawlMethodModeEnum: {
1194
+ readonly Auto: "auto";
1195
+ readonly Browser: "browser";
1196
+ };
1197
+ type CreateCrawlerConfigRequestCrawlMethodModeEnum = typeof CreateCrawlerConfigRequestCrawlMethodModeEnum[keyof typeof CreateCrawlerConfigRequestCrawlMethodModeEnum];
1198
+ declare const CreateCrawlerConfigRequestCrawlMethodWorkerEnum: {
1199
+ readonly Auto: "auto";
1200
+ readonly CurlCffi: "curl-cffi";
1201
+ readonly Patchright: "patchright";
1202
+ readonly Camoufox: "camoufox";
1203
+ readonly Brightdata: "brightdata";
1204
+ };
1205
+ type CreateCrawlerConfigRequestCrawlMethodWorkerEnum = typeof CreateCrawlerConfigRequestCrawlMethodWorkerEnum[keyof typeof CreateCrawlerConfigRequestCrawlMethodWorkerEnum];
1206
+ declare const CreateCrawlerConfigRequestCrawlMethodProxyEnum: {
1207
+ readonly Auto: "auto";
1208
+ readonly Dc: "dc";
1209
+ readonly Resi: "resi";
1210
+ readonly Isp: "isp";
1211
+ };
1212
+ type CreateCrawlerConfigRequestCrawlMethodProxyEnum = typeof CreateCrawlerConfigRequestCrawlMethodProxyEnum[keyof typeof CreateCrawlerConfigRequestCrawlMethodProxyEnum];
1189
1213
 
1190
1214
  /**
1191
1215
  * Kadoa API
@@ -4137,6 +4161,10 @@ interface V4ChangesGet200ResponseChangesInner {
4137
4161
  * URL where the change was detected
4138
4162
  */
4139
4163
  'url'?: string;
4164
+ /**
4165
+ * AI-generated one-sentence summary of the data change. Null if generation failed, feature is disabled, or change predates the feature.
4166
+ */
4167
+ 'summary'?: string | null;
4140
4168
  /**
4141
4169
  * URL of the screenshot taken when the change was detected
4142
4170
  */
@@ -5742,13 +5770,9 @@ type V4WorkflowsWorkflowIdMetadataPutRequestMonitoringFieldsInnerOperatorEnum =
5742
5770
  */
5743
5771
 
5744
5772
  /**
5745
- * The new monitoring config for the workflow
5773
+ * The monitoring config for the workflow
5746
5774
  */
5747
5775
  interface V4WorkflowsWorkflowIdMetadataPutRequestMonitoring {
5748
- /**
5749
- * Whether monitoring is enabled for the workflow
5750
- */
5751
- 'enabled'?: boolean;
5752
5776
  'fields'?: Array<V4WorkflowsWorkflowIdMetadataPutRequestMonitoringFieldsInner>;
5753
5777
  'conditions'?: V4WorkflowsWorkflowIdMetadataPutRequestMonitoringConditions;
5754
5778
  }
@@ -10119,6 +10143,7 @@ interface RealtimeEvent {
10119
10143
  message: unknown;
10120
10144
  id?: string;
10121
10145
  timestamp: number;
10146
+ _cursor?: string;
10122
10147
  }
10123
10148
  interface RealtimeConfig {
10124
10149
  apiKey: string;
@@ -10127,7 +10152,10 @@ interface RealtimeConfig {
10127
10152
  missedHeartbeatsLimit?: number;
10128
10153
  }
10129
10154
  declare class Realtime {
10130
- private socket?;
10155
+ private static readonly DEFAULT_RECONNECT_DELAY_MS;
10156
+ private static readonly MAX_RECONNECT_DELAY_MS;
10157
+ private activeSocket?;
10158
+ private drainingSockets;
10131
10159
  private heartbeatInterval;
10132
10160
  private reconnectDelay;
10133
10161
  private lastHeartbeat;
@@ -10138,8 +10166,26 @@ declare class Realtime {
10138
10166
  private eventListeners;
10139
10167
  private connectionListeners;
10140
10168
  private errorListeners;
10169
+ private isClosed;
10170
+ private lastCursor?;
10171
+ private reconnectTimer?;
10172
+ private hasConnectedOnce;
10173
+ private readonly recentEventIds;
10174
+ private readonly recentEventIdQueue;
10175
+ private readonly maxRecentEventIds;
10141
10176
  constructor(config: RealtimeConfig);
10142
10177
  connect(): Promise<void>;
10178
+ private getOAuthToken;
10179
+ private openSocket;
10180
+ private promoteSocket;
10181
+ private handleSocketMessage;
10182
+ private handleDrainSignal;
10183
+ private handleSocketClose;
10184
+ private handleUnexpectedDisconnect;
10185
+ private scheduleReconnect;
10186
+ private scheduleDrainReconnect;
10187
+ private normalizeReconnectDelay;
10188
+ private isDuplicateEvent;
10143
10189
  private handleHeartbeat;
10144
10190
  private notifyEventListeners;
10145
10191
  private notifyConnectionListeners;
@@ -10370,8 +10416,8 @@ declare class ApiRegistry {
10370
10416
  interface TeamInfo {
10371
10417
  id: string;
10372
10418
  name: string;
10373
- role: string;
10374
10419
  memberRole: string;
10420
+ adminEmail: string | null;
10375
10421
  }
10376
10422
  interface BearerAuthOptions {
10377
10423
  bearerToken: string;
package/dist/index.js CHANGED
@@ -6043,7 +6043,7 @@ var NotificationSetupService = class {
6043
6043
  (s) => s.eventType === eventType
6044
6044
  );
6045
6045
  if (existing?.id) {
6046
- const existingChannelIds = (existing.channels || []).map((c) => c.id).filter(Boolean);
6046
+ const existingChannelIds = (existing.channels || []).map((channel) => channel.id).filter(Boolean);
6047
6047
  const mergedChannelIds = [
6048
6048
  .../* @__PURE__ */ new Set([...existingChannelIds, ...channelIds])
6049
6049
  ];
@@ -6209,7 +6209,7 @@ process.env.KADOA_WSS_NEO_API_URI ?? "wss://events.kadoa.com/events/ws";
6209
6209
  var REALTIME_API_URI = process.env.KADOA_REALTIME_API_URI ?? "https://realtime.kadoa.com";
6210
6210
 
6211
6211
  // src/version.ts
6212
- var SDK_VERSION = "0.23.0";
6212
+ var SDK_VERSION = "0.24.2";
6213
6213
  var SDK_NAME = "kadoa-node-sdk";
6214
6214
  var SDK_LANGUAGE = "node";
6215
6215
 
@@ -6218,89 +6218,256 @@ var debug5 = logger.wss;
6218
6218
  if (typeof WebSocket === "undefined") {
6219
6219
  global.WebSocket = __require("ws");
6220
6220
  }
6221
- var Realtime = class {
6221
+ var isDrainControlMessage = (message) => message.type === "control.draining";
6222
+ var isRealtimeEvent = (message) => message.type !== "heartbeat" && message.type !== "control.draining";
6223
+ var _Realtime = class _Realtime {
6222
6224
  constructor(config) {
6225
+ this.drainingSockets = /* @__PURE__ */ new Set();
6223
6226
  this.lastHeartbeat = Date.now();
6224
6227
  this.isConnecting = false;
6225
6228
  this.eventListeners = /* @__PURE__ */ new Set();
6226
6229
  this.connectionListeners = /* @__PURE__ */ new Set();
6227
6230
  this.errorListeners = /* @__PURE__ */ new Set();
6231
+ this.isClosed = false;
6232
+ this.hasConnectedOnce = false;
6233
+ this.recentEventIds = /* @__PURE__ */ new Set();
6234
+ this.recentEventIdQueue = [];
6235
+ this.maxRecentEventIds = 1e3;
6228
6236
  this.apiKey = config.apiKey;
6229
6237
  this.heartbeatInterval = config.heartbeatInterval || 1e4;
6230
- this.reconnectDelay = config.reconnectDelay || 5e3;
6238
+ this.reconnectDelay = this.normalizeReconnectDelay(config.reconnectDelay);
6231
6239
  this.missedHeartbeatsLimit = config.missedHeartbeatsLimit || 3e4;
6232
6240
  }
6233
6241
  async connect() {
6234
- if (this.isConnecting) return;
6242
+ if (this.isClosed || this.isConnecting || this.activeSocket) {
6243
+ return;
6244
+ }
6235
6245
  this.isConnecting = true;
6236
6246
  try {
6237
- const response = await fetch(`${PUBLIC_API_URI}/v4/oauth2/token`, {
6238
- method: "POST",
6239
- headers: {
6240
- "Content-Type": "application/json",
6241
- "x-api-key": `${this.apiKey}`,
6242
- "x-sdk-version": SDK_VERSION
6247
+ const { access_token, team_id } = await this.getOAuthToken();
6248
+ await this.openSocket(access_token, team_id, "active");
6249
+ this.hasConnectedOnce = true;
6250
+ } catch (err) {
6251
+ debug5("Failed to connect: %O", err);
6252
+ this.isConnecting = false;
6253
+ this.notifyErrorListeners(err);
6254
+ if (!this.hasConnectedOnce) {
6255
+ throw err;
6256
+ }
6257
+ this.scheduleReconnect();
6258
+ }
6259
+ }
6260
+ async getOAuthToken() {
6261
+ const response = await fetch(`${PUBLIC_API_URI}/v4/oauth2/token`, {
6262
+ method: "POST",
6263
+ headers: {
6264
+ "Content-Type": "application/json",
6265
+ "x-api-key": `${this.apiKey}`,
6266
+ "x-sdk-version": SDK_VERSION
6267
+ }
6268
+ });
6269
+ return await response.json();
6270
+ }
6271
+ async openSocket(accessToken, teamId, role) {
6272
+ await new Promise((resolve, reject) => {
6273
+ const socket = new WebSocket(
6274
+ `${WSS_API_URI}?access_token=${accessToken}`
6275
+ );
6276
+ let settled = false;
6277
+ socket.onopen = () => {
6278
+ const subscribeMessage = {
6279
+ action: "subscribe",
6280
+ channel: teamId
6281
+ };
6282
+ if (this.lastCursor) {
6283
+ subscribeMessage.lastCursor = this.lastCursor;
6243
6284
  }
6244
- });
6245
- const { access_token, team_id } = await response.json();
6246
- await new Promise((resolve, reject) => {
6247
- this.socket = new WebSocket(
6248
- `${WSS_API_URI}?access_token=${access_token}`
6249
- );
6250
- this.socket.onopen = () => {
6251
- this.isConnecting = false;
6252
- this.lastHeartbeat = Date.now();
6253
- if (this.socket?.readyState === WebSocket.OPEN) {
6254
- this.socket.send(
6255
- JSON.stringify({
6256
- action: "subscribe",
6257
- channel: team_id
6258
- })
6259
- );
6260
- debug5("Connected to WebSocket");
6261
- this.notifyConnectionListeners(true);
6262
- }
6263
- this.startHeartbeatCheck();
6285
+ socket.send(JSON.stringify(subscribeMessage));
6286
+ this.promoteSocket(socket, role);
6287
+ this.isConnecting = false;
6288
+ this.lastHeartbeat = Date.now();
6289
+ this.startHeartbeatCheck();
6290
+ debug5("Connected to WebSocket");
6291
+ if (!settled) {
6292
+ settled = true;
6264
6293
  resolve();
6265
- };
6266
- this.socket.onmessage = (event) => {
6267
- try {
6268
- const data = JSON.parse(event.data);
6269
- if (data.type === "heartbeat") {
6270
- this.handleHeartbeat();
6271
- } else {
6272
- if (data?.id) {
6273
- fetch(`${REALTIME_API_URI}/api/v1/events/ack`, {
6274
- method: "POST",
6275
- headers: { "Content-Type": "application/json" },
6276
- body: JSON.stringify({ id: data.id })
6277
- });
6278
- }
6279
- this.notifyEventListeners(data);
6280
- }
6281
- } catch (err) {
6282
- debug5("Failed to parse incoming message: %O", err);
6283
- }
6284
- };
6285
- this.socket.onclose = () => {
6286
- debug5("WebSocket disconnected. Attempting to reconnect...");
6287
- this.isConnecting = false;
6288
- this.stopHeartbeatCheck();
6289
- this.notifyConnectionListeners(false, "Connection closed");
6290
- setTimeout(() => this.connect(), this.reconnectDelay);
6291
- };
6292
- this.socket.onerror = (error) => {
6293
- debug5("WebSocket error: %O", error);
6294
- this.isConnecting = false;
6295
- this.notifyErrorListeners(error);
6294
+ }
6295
+ };
6296
+ socket.onmessage = (event) => {
6297
+ this.handleSocketMessage(socket, event.data);
6298
+ };
6299
+ socket.onclose = () => {
6300
+ this.handleSocketClose(socket);
6301
+ if (!settled) {
6302
+ settled = true;
6303
+ reject(new Error("WebSocket closed before opening"));
6304
+ }
6305
+ };
6306
+ socket.onerror = (error) => {
6307
+ this.notifyErrorListeners(error);
6308
+ if (!settled) {
6309
+ settled = true;
6296
6310
  reject(error);
6297
- };
6298
- });
6311
+ return;
6312
+ }
6313
+ if (socket === this.activeSocket) {
6314
+ this.handleUnexpectedDisconnect("Socket error");
6315
+ }
6316
+ };
6317
+ });
6318
+ }
6319
+ promoteSocket(socket, role) {
6320
+ if (role === "replacement" && this.activeSocket && this.activeSocket !== socket) {
6321
+ this.drainingSockets.add(this.activeSocket);
6322
+ }
6323
+ this.activeSocket = socket;
6324
+ this.drainingSockets.delete(socket);
6325
+ if (role === "active" || !this.hasConnectedOnce) {
6326
+ this.notifyConnectionListeners(true);
6327
+ }
6328
+ }
6329
+ handleSocketMessage(socket, rawData) {
6330
+ try {
6331
+ const payload = typeof rawData === "string" ? rawData : rawData.toString?.() ?? "";
6332
+ const data = JSON.parse(payload);
6333
+ if (data.type === "heartbeat") {
6334
+ if (socket === this.activeSocket) {
6335
+ this.handleHeartbeat();
6336
+ }
6337
+ return;
6338
+ }
6339
+ if (isDrainControlMessage(data)) {
6340
+ this.handleDrainSignal(socket, data);
6341
+ return;
6342
+ }
6343
+ if (!isRealtimeEvent(data)) {
6344
+ return;
6345
+ }
6346
+ if (typeof data._cursor === "string") {
6347
+ this.lastCursor = data._cursor;
6348
+ }
6349
+ if (typeof data.id === "string") {
6350
+ fetch(`${REALTIME_API_URI}/api/v1/events/ack`, {
6351
+ method: "POST",
6352
+ headers: { "Content-Type": "application/json" },
6353
+ body: JSON.stringify({ id: data.id })
6354
+ }).catch((error) => {
6355
+ debug5("Failed to acknowledge event %s: %O", data.id, error);
6356
+ });
6357
+ }
6358
+ if (this.isDuplicateEvent(data.id)) {
6359
+ return;
6360
+ }
6361
+ this.notifyEventListeners(data);
6299
6362
  } catch (err) {
6300
- debug5("Failed to connect: %O", err);
6301
- this.isConnecting = false;
6302
- setTimeout(() => this.connect(), this.reconnectDelay);
6363
+ debug5("Failed to parse incoming message: %O", err);
6364
+ }
6365
+ }
6366
+ handleDrainSignal(socket, message) {
6367
+ if (socket !== this.activeSocket || this.isClosed) {
6368
+ return;
6369
+ }
6370
+ debug5("Received drain signal, preparing replacement socket");
6371
+ this.drainingSockets.add(socket);
6372
+ this.scheduleDrainReconnect(message.retryAfterMs);
6373
+ }
6374
+ handleSocketClose(socket) {
6375
+ const wasActiveSocket = socket === this.activeSocket;
6376
+ this.drainingSockets.delete(socket);
6377
+ if (!wasActiveSocket) {
6378
+ return;
6379
+ }
6380
+ this.activeSocket = void 0;
6381
+ this.stopHeartbeatCheck();
6382
+ if (this.isClosed) {
6383
+ return;
6384
+ }
6385
+ if (this.drainingSockets.size > 0) {
6386
+ debug5("Draining socket closed after replacement was scheduled");
6387
+ return;
6388
+ }
6389
+ this.handleUnexpectedDisconnect("Connection closed");
6390
+ }
6391
+ handleUnexpectedDisconnect(reason) {
6392
+ this.isConnecting = false;
6393
+ this.notifyConnectionListeners(false, reason);
6394
+ this.scheduleReconnect();
6395
+ }
6396
+ scheduleReconnect(replacement = false) {
6397
+ if (this.isClosed || this.reconnectTimer) {
6398
+ return;
6399
+ }
6400
+ this.reconnectTimer = setTimeout(async () => {
6401
+ this.reconnectTimer = void 0;
6402
+ if (this.isClosed || this.isConnecting || !replacement && this.activeSocket) {
6403
+ return;
6404
+ }
6405
+ this.isConnecting = true;
6406
+ try {
6407
+ const { access_token, team_id } = await this.getOAuthToken();
6408
+ await this.openSocket(
6409
+ access_token,
6410
+ team_id,
6411
+ replacement ? "replacement" : "active"
6412
+ );
6413
+ } catch (err) {
6414
+ debug5("Reconnect failed: %O", err);
6415
+ this.isConnecting = false;
6416
+ this.notifyErrorListeners(err);
6417
+ this.scheduleReconnect(replacement);
6418
+ }
6419
+ }, this.reconnectDelay);
6420
+ }
6421
+ scheduleDrainReconnect(retryAfterMs) {
6422
+ if (this.isClosed || this.reconnectTimer) {
6423
+ return;
6303
6424
  }
6425
+ let safeDelayMs = this.reconnectDelay;
6426
+ if (typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0 && retryAfterMs <= _Realtime.MAX_RECONNECT_DELAY_MS) {
6427
+ safeDelayMs = Math.trunc(retryAfterMs);
6428
+ }
6429
+ this.reconnectTimer = setTimeout(async () => {
6430
+ this.reconnectTimer = void 0;
6431
+ if (this.isClosed || this.isConnecting) {
6432
+ return;
6433
+ }
6434
+ this.isConnecting = true;
6435
+ try {
6436
+ const { access_token, team_id } = await this.getOAuthToken();
6437
+ await this.openSocket(access_token, team_id, "replacement");
6438
+ } catch (err) {
6439
+ debug5("Reconnect failed: %O", err);
6440
+ this.isConnecting = false;
6441
+ this.notifyErrorListeners(err);
6442
+ this.scheduleReconnect(true);
6443
+ }
6444
+ }, safeDelayMs);
6445
+ }
6446
+ normalizeReconnectDelay(delay) {
6447
+ if (typeof delay !== "number" || !Number.isFinite(delay)) {
6448
+ return _Realtime.DEFAULT_RECONNECT_DELAY_MS;
6449
+ }
6450
+ return Math.min(
6451
+ Math.max(0, Math.trunc(delay)),
6452
+ _Realtime.MAX_RECONNECT_DELAY_MS
6453
+ );
6454
+ }
6455
+ isDuplicateEvent(eventId) {
6456
+ if (!eventId) {
6457
+ return false;
6458
+ }
6459
+ if (this.recentEventIds.has(eventId)) {
6460
+ return true;
6461
+ }
6462
+ this.recentEventIds.add(eventId);
6463
+ this.recentEventIdQueue.push(eventId);
6464
+ if (this.recentEventIdQueue.length > this.maxRecentEventIds) {
6465
+ const expiredId = this.recentEventIdQueue.shift();
6466
+ if (expiredId) {
6467
+ this.recentEventIds.delete(expiredId);
6468
+ }
6469
+ }
6470
+ return false;
6304
6471
  }
6305
6472
  handleHeartbeat() {
6306
6473
  debug5("Heartbeat received");
@@ -6328,22 +6495,24 @@ var Realtime = class {
6328
6495
  this.errorListeners.forEach((listener) => {
6329
6496
  try {
6330
6497
  listener(error);
6331
- } catch (error2) {
6332
- debug5("Error in error listener: %O", error2);
6498
+ } catch (listenerError) {
6499
+ debug5("Error in error listener: %O", listenerError);
6333
6500
  }
6334
6501
  });
6335
6502
  }
6336
6503
  startHeartbeatCheck() {
6504
+ this.stopHeartbeatCheck();
6337
6505
  this.missedHeartbeatCheckTimer = setInterval(() => {
6338
- if (Date.now() - this.lastHeartbeat > this.missedHeartbeatsLimit) {
6506
+ if (this.activeSocket && Date.now() - this.lastHeartbeat > this.missedHeartbeatsLimit) {
6339
6507
  debug5("No heartbeat received in 30 seconds! Closing connection.");
6340
- this.socket?.close();
6508
+ this.activeSocket.close();
6341
6509
  }
6342
6510
  }, this.heartbeatInterval);
6343
6511
  }
6344
6512
  stopHeartbeatCheck() {
6345
6513
  if (this.missedHeartbeatCheckTimer) {
6346
6514
  clearInterval(this.missedHeartbeatCheckTimer);
6515
+ this.missedHeartbeatCheckTimer = void 0;
6347
6516
  }
6348
6517
  }
6349
6518
  /**
@@ -6364,6 +6533,9 @@ var Realtime = class {
6364
6533
  */
6365
6534
  onConnection(listener) {
6366
6535
  this.connectionListeners.add(listener);
6536
+ if (this.isConnected()) {
6537
+ listener(true);
6538
+ }
6367
6539
  return () => {
6368
6540
  this.connectionListeners.delete(listener);
6369
6541
  };
@@ -6380,19 +6552,30 @@ var Realtime = class {
6380
6552
  };
6381
6553
  }
6382
6554
  close() {
6383
- if (this.socket) {
6384
- this.stopHeartbeatCheck();
6385
- this.socket.close();
6386
- this.socket = void 0;
6555
+ this.isClosed = true;
6556
+ if (this.reconnectTimer) {
6557
+ clearTimeout(this.reconnectTimer);
6558
+ this.reconnectTimer = void 0;
6387
6559
  }
6560
+ this.stopHeartbeatCheck();
6561
+ this.activeSocket?.close();
6562
+ this.activeSocket = void 0;
6563
+ this.drainingSockets.forEach((socket) => {
6564
+ socket.close();
6565
+ });
6566
+ this.drainingSockets.clear();
6567
+ this.isConnecting = false;
6388
6568
  this.eventListeners.clear();
6389
6569
  this.connectionListeners.clear();
6390
6570
  this.errorListeners.clear();
6391
6571
  }
6392
6572
  isConnected() {
6393
- return this.socket?.readyState === WebSocket.OPEN;
6573
+ return this.activeSocket?.readyState === WebSocket.OPEN;
6394
6574
  }
6395
6575
  };
6576
+ _Realtime.DEFAULT_RECONNECT_DELAY_MS = 5e3;
6577
+ _Realtime.MAX_RECONNECT_DELAY_MS = 6e4;
6578
+ var Realtime = _Realtime;
6396
6579
 
6397
6580
  // src/domains/user/user.service.ts
6398
6581
  var UserService = class {