@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/README.md +2 -0
- package/dist/browser/index.global.js +8 -8
- package/dist/browser/index.global.js.map +1 -1
- package/dist/index.d.mts +61 -15
- package/dist/index.d.ts +61 -15
- package/dist/index.js +259 -76
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +259 -76
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
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
|
-
*
|
|
388
|
+
* Fields to monitor (at least one required)
|
|
389
389
|
*/
|
|
390
|
-
'
|
|
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
|
-
*
|
|
1181
|
+
* Worker selection mode. \'auto\' (default) picks cheapest compatible worker, \'browser\' forces browser.
|
|
1186
1182
|
*/
|
|
1187
|
-
'
|
|
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
|
|
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
|
|
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
|
-
*
|
|
388
|
+
* Fields to monitor (at least one required)
|
|
389
389
|
*/
|
|
390
|
-
'
|
|
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
|
-
*
|
|
1181
|
+
* Worker selection mode. \'auto\' (default) picks cheapest compatible worker, \'browser\' forces browser.
|
|
1186
1182
|
*/
|
|
1187
|
-
'
|
|
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
|
|
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
|
|
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((
|
|
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.
|
|
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
|
|
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
|
|
6238
|
+
this.reconnectDelay = this.normalizeReconnectDelay(config.reconnectDelay);
|
|
6231
6239
|
this.missedHeartbeatsLimit = config.missedHeartbeatsLimit || 3e4;
|
|
6232
6240
|
}
|
|
6233
6241
|
async connect() {
|
|
6234
|
-
if (this.isConnecting)
|
|
6242
|
+
if (this.isClosed || this.isConnecting || this.activeSocket) {
|
|
6243
|
+
return;
|
|
6244
|
+
}
|
|
6235
6245
|
this.isConnecting = true;
|
|
6236
6246
|
try {
|
|
6237
|
-
const
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
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
|
-
|
|
6246
|
-
|
|
6247
|
-
this.
|
|
6248
|
-
|
|
6249
|
-
);
|
|
6250
|
-
|
|
6251
|
-
|
|
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
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
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
|
|
6301
|
-
|
|
6302
|
-
|
|
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 (
|
|
6332
|
-
debug5("Error in error listener: %O",
|
|
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.
|
|
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
|
-
|
|
6384
|
-
|
|
6385
|
-
this.
|
|
6386
|
-
this.
|
|
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.
|
|
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 {
|