@mochabug/adapt-web 0.0.61 → 0.0.64
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/cjs/genproto/mochabugapis/adapt/automations/v1/automations_pb.js +7 -2
- package/dist/cjs/genproto/mochabugapis/adapt/automations/v1/automations_pb.js.map +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/graph/exchange_pb.js +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/graph/exchange_pb.js.map +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/graph/receiver_pb.js +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/graph/receiver_pb.js.map +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/graph/signal_descriptor_pb.js +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/graph/signal_descriptor_pb.js.map +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/graph/transceiver_pb.js +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/graph/transceiver_pb.js.map +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/graph/transmitter_pb.js +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/graph/transmitter_pb.js.map +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/manifest_pb.js +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/manifest_pb.js.map +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/service_binding_pb.js +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/service_binding_pb.js.map +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/service_definition_pb.js +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/service_definition_pb.js.map +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/vertex_pb.js +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/vertex_pb.js.map +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/runtime/v1/runtime_pb.js +1 -1
- package/dist/cjs/genproto/mochabugapis/adapt/runtime/v1/runtime_pb.js.map +1 -1
- package/dist/cjs/index.js +174 -318
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/automations/v1/automations_pb.js +6 -1
- package/dist/esm/genproto/mochabugapis/adapt/automations/v1/automations_pb.js.map +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/graph/exchange_pb.js +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/graph/exchange_pb.js.map +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/graph/receiver_pb.js +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/graph/receiver_pb.js.map +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/graph/signal_descriptor_pb.js +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/graph/signal_descriptor_pb.js.map +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/graph/transceiver_pb.js +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/graph/transceiver_pb.js.map +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/graph/transmitter_pb.js +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/graph/transmitter_pb.js.map +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/manifest_pb.js +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/manifest_pb.js.map +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/service_binding_pb.js +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/service_binding_pb.js.map +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/service_definition_pb.js +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/service_definition_pb.js.map +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/vertex_pb.js +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/vertex_pb.js.map +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/runtime/v1/runtime_pb.js +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/runtime/v1/runtime_pb.js.map +1 -1
- package/dist/esm/index.js +169 -319
- package/dist/esm/index.js.map +1 -1
- package/dist/types/genproto/mochabugapis/adapt/automations/v1/automations_pb.d.ts +148 -16
- package/dist/types/genproto/mochabugapis/adapt/runtime/v1/runtime_pb.d.ts +27 -21
- package/dist/types/index.d.ts +22 -20
- package/package.json +14 -5
package/dist/esm/index.js
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import { fromBinary, toJson } from "@bufbuild/protobuf";
|
|
2
2
|
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const CONSUMER_MSG_NEXT_PREFIX = "$JS.API.CONSUMER.MSG.NEXT.";
|
|
7
|
-
const ACK_NEGATIVE = new TextEncoder().encode("-NAK");
|
|
8
|
-
// JetStream Status Codes
|
|
9
|
-
const STATUS_NO_MESSAGES = 404;
|
|
10
|
-
const STATUS_REQUEST_TIMEOUT = 408;
|
|
11
|
-
const STATUS_MAX_ACK_PENDING = 409;
|
|
12
|
-
const STATUS_FLOW_CONTROL = 100;
|
|
3
|
+
import WebSocket from "isomorphic-ws";
|
|
4
|
+
import { parse as parseUuid } from "uuid";
|
|
5
|
+
import { SessionSchema, UrlSchema, WebsocketMessageSchema, } from "./genproto/mochabugapis/adapt/automations/v1/automations_pb.js";
|
|
13
6
|
// Default configuration
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
const defaultConfig = {
|
|
8
|
+
baseUrl: "https://adapt-dev.mochabugapis.com/v1/automations",
|
|
9
|
+
wsBaseUrl: "wss://adapt-dev.mochabugapis.com/v1/automations",
|
|
10
|
+
};
|
|
11
|
+
// Global configuration
|
|
12
|
+
let globalConfig = { ...defaultConfig };
|
|
13
|
+
// Configure the Adapt client
|
|
14
|
+
export function configure(config) {
|
|
15
|
+
globalConfig = { ...globalConfig, ...config };
|
|
16
|
+
}
|
|
17
|
+
// Get current configuration
|
|
18
|
+
export function getConfig() {
|
|
19
|
+
return { ...globalConfig };
|
|
20
|
+
}
|
|
21
|
+
// Reset to default configuration
|
|
22
|
+
export function resetConfig() {
|
|
23
|
+
globalConfig = { ...defaultConfig };
|
|
24
|
+
}
|
|
17
25
|
// REST API functions
|
|
18
26
|
export async function startSession(req, token) {
|
|
19
27
|
const headers = new Headers({
|
|
@@ -23,7 +31,7 @@ export async function startSession(req, token) {
|
|
|
23
31
|
if (token) {
|
|
24
32
|
headers.set("Authorization", `Bearer ${token}`);
|
|
25
33
|
}
|
|
26
|
-
const response = await fetch(`${baseUrl}/${req.automation.organization}/${req.automation.group}/${req.automation.automation}/start`, {
|
|
34
|
+
const response = await fetch(`${globalConfig.baseUrl}/${req.automation.organization}/${req.automation.group}/${req.automation.automation}/start`, {
|
|
27
35
|
method: "POST",
|
|
28
36
|
body: JSON.stringify(req),
|
|
29
37
|
headers,
|
|
@@ -34,7 +42,7 @@ export async function startSession(req, token) {
|
|
|
34
42
|
return await response.json();
|
|
35
43
|
}
|
|
36
44
|
export async function inheritSession(automation, sessionToken) {
|
|
37
|
-
const response = await fetch(`${baseUrl}/${automation.organization}/${automation.group}/${automation.automation}/session/inherit`, {
|
|
45
|
+
const response = await fetch(`${globalConfig.baseUrl}/${automation.organization}/${automation.group}/${automation.automation}/session/inherit`, {
|
|
38
46
|
method: "POST",
|
|
39
47
|
body: JSON.stringify(automation),
|
|
40
48
|
headers: {
|
|
@@ -49,7 +57,7 @@ export async function inheritSession(automation, sessionToken) {
|
|
|
49
57
|
return await response.json();
|
|
50
58
|
}
|
|
51
59
|
export async function getSession(automation, sessionToken) {
|
|
52
|
-
const response = await fetch(`${baseUrl}/${automation.organization}/${automation.group}/${automation.automation}/session/status`, {
|
|
60
|
+
const response = await fetch(`${globalConfig.baseUrl}/${automation.organization}/${automation.group}/${automation.automation}/session/status`, {
|
|
53
61
|
method: "GET",
|
|
54
62
|
headers: {
|
|
55
63
|
"Response-Type": "application/json",
|
|
@@ -62,7 +70,7 @@ export async function getSession(automation, sessionToken) {
|
|
|
62
70
|
return await response.json();
|
|
63
71
|
}
|
|
64
72
|
export async function readOutput(automation, sessionToken) {
|
|
65
|
-
const response = await fetch(`${baseUrl}/${automation.organization}/${automation.group}/${automation.automation}/session/output`, {
|
|
73
|
+
const response = await fetch(`${globalConfig.baseUrl}/${automation.organization}/${automation.group}/${automation.automation}/session/output`, {
|
|
66
74
|
method: "GET",
|
|
67
75
|
headers: {
|
|
68
76
|
"Response-Type": "application/json",
|
|
@@ -75,7 +83,7 @@ export async function readOutput(automation, sessionToken) {
|
|
|
75
83
|
return await response.json();
|
|
76
84
|
}
|
|
77
85
|
export async function readUrls(automation, sessionToken) {
|
|
78
|
-
const response = await fetch(`${baseUrl}/${automation.organization}/${automation.group}/${automation.automation}/session/urls`, {
|
|
86
|
+
const response = await fetch(`${globalConfig.baseUrl}/${automation.organization}/${automation.group}/${automation.automation}/session/urls`, {
|
|
79
87
|
method: "GET",
|
|
80
88
|
headers: {
|
|
81
89
|
"Response-Type": "application/json",
|
|
@@ -88,7 +96,7 @@ export async function readUrls(automation, sessionToken) {
|
|
|
88
96
|
return await response.json();
|
|
89
97
|
}
|
|
90
98
|
export async function stopSession(automation, sessionToken) {
|
|
91
|
-
const response = await fetch(`${baseUrl}/${automation.organization}/${automation.group}/${automation.automation}/session/stop`, {
|
|
99
|
+
const response = await fetch(`${globalConfig.baseUrl}/${automation.organization}/${automation.group}/${automation.automation}/session/stop`, {
|
|
92
100
|
method: "DELETE",
|
|
93
101
|
headers: {
|
|
94
102
|
Authorization: `Bearer ${sessionToken}`,
|
|
@@ -117,19 +125,19 @@ var ConnectionState;
|
|
|
117
125
|
})(ConnectionState || (ConnectionState = {}));
|
|
118
126
|
export class PubsubClient {
|
|
119
127
|
constructor(debug = false) {
|
|
120
|
-
this.
|
|
121
|
-
this.
|
|
122
|
-
this.
|
|
123
|
-
this.consumerName = null;
|
|
128
|
+
this.ws = null;
|
|
129
|
+
this.sessionToken = null;
|
|
130
|
+
this.automation = null;
|
|
124
131
|
this.outputHandler = null;
|
|
125
132
|
this.sessionHandler = null;
|
|
126
133
|
this.urlHandler = null;
|
|
127
|
-
this.isReconnecting = false;
|
|
128
|
-
this.inbox = null;
|
|
129
134
|
this.connectionState = ConnectionState.DISCONNECTED;
|
|
130
|
-
this.
|
|
131
|
-
//
|
|
132
|
-
this.
|
|
135
|
+
this.reconnectAttempts = 0;
|
|
136
|
+
this.maxReconnectAttempts = -1; // -1 = infinite
|
|
137
|
+
this.reconnectTimeWait = 1000; // Start with 1 second
|
|
138
|
+
this.maxReconnectTimeWait = 30000; // Max 30 seconds
|
|
139
|
+
this.shouldReconnect = true;
|
|
140
|
+
this.ackedMessages = new Map(); // Track ACKed messages with timestamp to prevent duplicate processing
|
|
133
141
|
this.debug = debug;
|
|
134
142
|
}
|
|
135
143
|
log(level, message, data) {
|
|
@@ -146,17 +154,19 @@ export class PubsubClient {
|
|
|
146
154
|
}
|
|
147
155
|
async subscribe(opts) {
|
|
148
156
|
this.log("debug", "Subscribe called with:", {
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
automation: opts.automation,
|
|
158
|
+
hasToken: !!opts.sessionToken,
|
|
151
159
|
});
|
|
152
|
-
if (!opts.
|
|
153
|
-
throw new Error("
|
|
160
|
+
if (!opts.sessionToken || !opts.automation) {
|
|
161
|
+
throw new Error("Session token and automation are required");
|
|
154
162
|
}
|
|
155
|
-
// Only update the handlers if we stay on the same
|
|
163
|
+
// Only update the handlers if we stay on the same session
|
|
156
164
|
if (this.connectionState === ConnectionState.CONNECTED &&
|
|
157
|
-
this.
|
|
158
|
-
this.
|
|
159
|
-
this.
|
|
165
|
+
this.sessionToken === opts.sessionToken &&
|
|
166
|
+
this.automation?.organization === opts.automation.organization &&
|
|
167
|
+
this.automation?.group === opts.automation.group &&
|
|
168
|
+
this.automation?.automation === opts.automation.automation) {
|
|
169
|
+
this.log("debug", "Already connected to same session, updating handlers only");
|
|
160
170
|
this.outputHandler = opts.onOutput || null;
|
|
161
171
|
this.sessionHandler = opts.onSession || null;
|
|
162
172
|
this.urlHandler = opts.onUrl || null;
|
|
@@ -165,329 +175,181 @@ export class PubsubClient {
|
|
|
165
175
|
if (this.connectionState === ConnectionState.CONNECTING) {
|
|
166
176
|
throw new Error("Already connecting, please wait");
|
|
167
177
|
}
|
|
168
|
-
// If already connected,
|
|
178
|
+
// If already connected, unsubscribe first
|
|
169
179
|
if (this.connectionState === ConnectionState.CONNECTED) {
|
|
170
180
|
await this.unsubscribe();
|
|
171
181
|
}
|
|
182
|
+
// Store configuration
|
|
183
|
+
this.sessionToken = opts.sessionToken;
|
|
184
|
+
this.automation = opts.automation;
|
|
185
|
+
this.outputHandler = opts.onOutput || null;
|
|
186
|
+
this.sessionHandler = opts.onSession || null;
|
|
187
|
+
this.urlHandler = opts.onUrl || null;
|
|
188
|
+
this.shouldReconnect = true;
|
|
189
|
+
this.reconnectAttempts = 0;
|
|
190
|
+
this.reconnectTimeWait = 1000;
|
|
191
|
+
await this.connect();
|
|
192
|
+
}
|
|
193
|
+
async connect() {
|
|
194
|
+
if (!this.sessionToken || !this.automation) {
|
|
195
|
+
throw new Error("Session token and automation are required");
|
|
196
|
+
}
|
|
172
197
|
try {
|
|
173
198
|
this.connectionState = ConnectionState.CONNECTING;
|
|
174
|
-
|
|
175
|
-
this.
|
|
176
|
-
this.
|
|
177
|
-
this.
|
|
178
|
-
this.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
this.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
throw error;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
setupStatusMonitoring() {
|
|
215
|
-
if (!this.nc)
|
|
216
|
-
return;
|
|
217
|
-
(async () => {
|
|
218
|
-
try {
|
|
219
|
-
for await (const status of this.nc.status()) {
|
|
220
|
-
switch (status.type) {
|
|
221
|
-
case "disconnect":
|
|
222
|
-
this.log("warn", "⚠ Disconnected", { data: status.data });
|
|
223
|
-
break;
|
|
224
|
-
case "reconnect":
|
|
225
|
-
this.log("info", "↻ Reconnected");
|
|
226
|
-
if (!this.isReconnecting &&
|
|
227
|
-
this.connectionState === ConnectionState.CONNECTED) {
|
|
228
|
-
this.isReconnecting = true;
|
|
229
|
-
try {
|
|
230
|
-
await this.resubscribe();
|
|
231
|
-
}
|
|
232
|
-
catch (error) {
|
|
233
|
-
this.log("error", "Failed to resubscribe after reconnection:", error);
|
|
234
|
-
}
|
|
235
|
-
finally {
|
|
236
|
-
this.isReconnecting = false;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
break;
|
|
240
|
-
case "error":
|
|
241
|
-
this.log("error", "Connection error:", status.data);
|
|
242
|
-
break;
|
|
243
|
-
case "update":
|
|
244
|
-
this.log("debug", "Cluster update received:", status.data);
|
|
245
|
-
break;
|
|
246
|
-
case "ldm":
|
|
247
|
-
this.log("warn", "Server requesting reconnection (LDM)");
|
|
248
|
-
break;
|
|
249
|
-
case "reconnecting":
|
|
250
|
-
this.log("debug", "Attempting to reconnect...");
|
|
251
|
-
break;
|
|
252
|
-
case "staleConnection":
|
|
253
|
-
this.log("warn", "Connection is stale, may reconnect soon");
|
|
254
|
-
break;
|
|
255
|
-
default:
|
|
256
|
-
if (status.type !== "pingTimer" || this.debug) {
|
|
257
|
-
this.log("debug", `Status event: ${status.type}`, status.data);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
199
|
+
const wsUrl = `${globalConfig.wsBaseUrl}/${this.automation.organization}/${this.automation.group}/${this.automation.automation}/ws?token=${encodeURIComponent(this.sessionToken)}`;
|
|
200
|
+
this.log("info", `Connecting to WebSocket: ${wsUrl}`);
|
|
201
|
+
this.ws = new WebSocket(wsUrl);
|
|
202
|
+
this.ws.binaryType = "arraybuffer";
|
|
203
|
+
this.ws.onopen = () => {
|
|
204
|
+
this.log("info", "✓ WebSocket connected");
|
|
205
|
+
this.connectionState = ConnectionState.CONNECTED;
|
|
206
|
+
};
|
|
207
|
+
this.ws.onmessage = (event) => {
|
|
208
|
+
this.handleMessage(event.data);
|
|
209
|
+
};
|
|
210
|
+
this.ws.onerror = (error) => {
|
|
211
|
+
this.log("error", "WebSocket error:", error);
|
|
212
|
+
};
|
|
213
|
+
this.ws.onclose = (event) => {
|
|
214
|
+
this.log("info", `WebSocket closed: ${event.code} - ${event.reason}`);
|
|
215
|
+
this.connectionState = ConnectionState.DISCONNECTED;
|
|
216
|
+
// Handle different close codes
|
|
217
|
+
switch (event.code) {
|
|
218
|
+
case 1000: // Normal closure (server closed friendly)
|
|
219
|
+
this.log("info", "Normal WebSocket closure");
|
|
220
|
+
this.shouldReconnect = false;
|
|
221
|
+
this.unsubscribe().catch((error) => {
|
|
222
|
+
this.log("error", "Error during unsubscribe:", error);
|
|
223
|
+
});
|
|
224
|
+
break;
|
|
225
|
+
case 1002: // Protocol error
|
|
226
|
+
this.log("error", "WebSocket protocol error");
|
|
227
|
+
break;
|
|
228
|
+
case 1006: // Abnormal closure (connection lost)
|
|
229
|
+
this.log("warn", "Abnormal WebSocket closure - connection lost");
|
|
230
|
+
break;
|
|
231
|
+
case 1008: // Policy violation (e.g., unsupported message type)
|
|
232
|
+
this.log("error", "WebSocket policy violation - unsupported data");
|
|
233
|
+
break;
|
|
234
|
+
default:
|
|
235
|
+
this.log("warn", `WebSocket closed with code: ${event.code}`);
|
|
260
236
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
this.log("error", "Fatal status handler error:", error);
|
|
237
|
+
// Only reconnect if not a normal closure (1000) and reconnect is enabled
|
|
238
|
+
if (this.shouldReconnect && event.code !== 1000) {
|
|
239
|
+
this.scheduleReconnect();
|
|
265
240
|
}
|
|
266
|
-
}
|
|
267
|
-
})();
|
|
268
|
-
}
|
|
269
|
-
async createPullSubscription() {
|
|
270
|
-
if (!this.nc || !this.streamName || !this.consumerName) {
|
|
271
|
-
throw new Error("Not properly initialized");
|
|
272
|
-
}
|
|
273
|
-
this.clearSubscriptions();
|
|
274
|
-
try {
|
|
275
|
-
this.log("info", `Creating pull subscription for consumer: ${this.consumerName} on stream: ${this.streamName}`);
|
|
276
|
-
// Create inbox for receiving messages
|
|
277
|
-
this.inbox = createInbox(`_INBOX_${this.consumerName}`);
|
|
278
|
-
const sub = this.nc.subscribe(this.inbox);
|
|
279
|
-
this.subscription = sub;
|
|
280
|
-
this.pending = 0;
|
|
281
|
-
// Start message processing loop
|
|
282
|
-
this.processMessages(sub);
|
|
283
|
-
this.pull();
|
|
284
|
-
this.log("info", "Pull subscription created successfully");
|
|
241
|
+
};
|
|
285
242
|
}
|
|
286
243
|
catch (error) {
|
|
287
|
-
this.
|
|
244
|
+
this.connectionState = ConnectionState.DISCONNECTED;
|
|
245
|
+
this.log("error", "Failed to connect:", error);
|
|
288
246
|
throw error;
|
|
289
247
|
}
|
|
290
248
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
this.log("info", "Subscription closing");
|
|
298
|
-
break;
|
|
299
|
-
}
|
|
300
|
-
try {
|
|
301
|
-
await this.handleMessage(msg);
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
this.log("error", "Error handling message:", error);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
249
|
+
scheduleReconnect() {
|
|
250
|
+
if (this.maxReconnectAttempts !== -1 &&
|
|
251
|
+
this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
252
|
+
this.log("error", "Max reconnection attempts reached");
|
|
253
|
+
this.shouldReconnect = false;
|
|
254
|
+
return;
|
|
307
255
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
256
|
+
this.reconnectAttempts++;
|
|
257
|
+
const delay = Math.min(this.reconnectTimeWait * Math.pow(2, this.reconnectAttempts - 1), this.maxReconnectTimeWait);
|
|
258
|
+
this.log("info", `Scheduling reconnect attempt ${this.reconnectAttempts} in ${delay}ms`);
|
|
259
|
+
setTimeout(() => {
|
|
260
|
+
if (this.shouldReconnect &&
|
|
261
|
+
this.connectionState === ConnectionState.DISCONNECTED) {
|
|
262
|
+
this.connect().catch((error) => {
|
|
263
|
+
this.log("error", "Reconnection failed:", error);
|
|
264
|
+
});
|
|
311
265
|
}
|
|
312
|
-
}
|
|
266
|
+
}, delay);
|
|
313
267
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
this.
|
|
322
|
-
await this.handleStatusMessage(status, description, msg);
|
|
268
|
+
handleMessage(data) {
|
|
269
|
+
try {
|
|
270
|
+
const message = fromBinary(WebsocketMessageSchema, new Uint8Array(data));
|
|
271
|
+
const now = Date.now();
|
|
272
|
+
// Check if we've already processed this message
|
|
273
|
+
if (this.ackedMessages.has(message.id)) {
|
|
274
|
+
this.log("debug", `Skipping already processed message: ${message.id}`);
|
|
275
|
+
this.sendAck(message.id);
|
|
323
276
|
return;
|
|
324
277
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
this.log("
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
// Now, we need to count the messages that are actually on the consumer's subject
|
|
332
|
-
// which is what we are pulling
|
|
333
|
-
this.pending--;
|
|
334
|
-
if (this.pending <= 0) {
|
|
335
|
-
this.pull();
|
|
336
|
-
}
|
|
337
|
-
// Process the message based on the last part of the subject
|
|
338
|
-
const parts = msg.subject.split(".");
|
|
339
|
-
const action = parts[parts.length - 1];
|
|
340
|
-
this.log("info", `↓ message: ${action} on ${msg.subject}`);
|
|
341
|
-
// Check if we have data to process
|
|
342
|
-
if (!msg.data || msg.data.length === 0) {
|
|
343
|
-
this.log("warn", `Empty message for action ${action}`);
|
|
344
|
-
this.ack(msg);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
try {
|
|
348
|
-
switch (action) {
|
|
278
|
+
this.sendAck(message.id);
|
|
279
|
+
// Mark as processed with timestamp
|
|
280
|
+
this.ackedMessages.set(message.id, now);
|
|
281
|
+
this.log("debug", `Received message with ID: ${message.id}`);
|
|
282
|
+
// Process the message based on its type
|
|
283
|
+
switch (message.message.case) {
|
|
349
284
|
case "output": {
|
|
350
285
|
if (this.outputHandler) {
|
|
351
|
-
const output =
|
|
286
|
+
const output = message.message.value;
|
|
352
287
|
this.outputHandler({
|
|
353
288
|
vertex: output.vertex,
|
|
354
289
|
fork: output.fork,
|
|
355
290
|
data: parseOutputData(output.data),
|
|
356
|
-
created:
|
|
291
|
+
created: output.created
|
|
292
|
+
? timestampDate(output.created)
|
|
293
|
+
: undefined,
|
|
357
294
|
});
|
|
358
295
|
}
|
|
359
296
|
break;
|
|
360
297
|
}
|
|
361
298
|
case "session": {
|
|
299
|
+
const session = message.message.value;
|
|
362
300
|
if (this.sessionHandler) {
|
|
363
|
-
const session = fromBinary(SessionSchema, msg.data);
|
|
364
301
|
this.sessionHandler(toJson(SessionSchema, session));
|
|
365
302
|
}
|
|
366
303
|
break;
|
|
367
304
|
}
|
|
368
305
|
case "url": {
|
|
369
306
|
if (this.urlHandler) {
|
|
370
|
-
const url =
|
|
307
|
+
const url = message.message.value;
|
|
371
308
|
this.urlHandler(toJson(UrlSchema, url));
|
|
372
309
|
}
|
|
373
310
|
break;
|
|
374
311
|
}
|
|
375
312
|
default:
|
|
376
|
-
this.log("
|
|
313
|
+
this.log("warn", "Received message with unknown type");
|
|
377
314
|
}
|
|
378
|
-
this.ack(msg);
|
|
379
315
|
}
|
|
380
316
|
catch (error) {
|
|
381
|
-
this.log("error",
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
async handleStatusMessage(status, description, msg) {
|
|
386
|
-
switch (status) {
|
|
387
|
-
// This should never be received in a pull consumer without no_wait: true
|
|
388
|
-
case STATUS_NO_MESSAGES:
|
|
389
|
-
this.log("warn", `No messages available: ${description}`);
|
|
390
|
-
this.log("debug", "Headers", msg.headers);
|
|
391
|
-
break;
|
|
392
|
-
// This should never be received in a pull consumer without expires: ns
|
|
393
|
-
case STATUS_REQUEST_TIMEOUT:
|
|
394
|
-
this.log("warn", `Request timeout: ${description}`);
|
|
395
|
-
this.log("debug", "Headers", msg.headers);
|
|
396
|
-
break;
|
|
397
|
-
case STATUS_MAX_ACK_PENDING:
|
|
398
|
-
this.log("error", `Max ack pending: ${description}`);
|
|
399
|
-
this.log("error", "You are throwing excpetion in your handlers or processing messages too slowly. Please ensure you are acknowledging messages properly.");
|
|
400
|
-
await this.unsubscribe();
|
|
401
|
-
break;
|
|
402
|
-
case STATUS_FLOW_CONTROL:
|
|
403
|
-
this.log("debug", `Flow control: ${description}`);
|
|
404
|
-
this.ack(msg);
|
|
405
|
-
break;
|
|
406
|
-
default:
|
|
407
|
-
this.log("debug", `Unknown status ${status}: ${description}`);
|
|
317
|
+
this.log("error", "Error processing message:", error);
|
|
318
|
+
// Note: We already sent ACK, as per the requirement to ACK immediately
|
|
408
319
|
}
|
|
409
320
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (!msg.reply) {
|
|
415
|
-
this.log("warn", "Message has no reply subject for NAK");
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
this.log("debug", `Sending NAK for message: '${msg.subject}' on '${msg.reply}'`);
|
|
419
|
-
this.nc.publish(msg.reply, ACK_NEGATIVE);
|
|
420
|
-
}
|
|
421
|
-
ack(msg) {
|
|
422
|
-
if (!msg || !this.nc) {
|
|
423
|
-
throw new Error("Invalid message or connection not established");
|
|
424
|
-
}
|
|
425
|
-
if (!msg.reply) {
|
|
426
|
-
this.log("warn", "Message has no reply subject for ACK");
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
this.log("debug", `Sending ACK for message: '${msg.subject}' on '${msg.reply}'`);
|
|
430
|
-
this.nc.publish(msg.reply, new Uint8Array(0));
|
|
431
|
-
}
|
|
432
|
-
pull() {
|
|
433
|
-
if (!this.nc || !this.inbox) {
|
|
434
|
-
this.log("error", "NATS connection or inbox not initialized");
|
|
435
|
-
throw new Error("NATS connection or inbox not initialized");
|
|
436
|
-
}
|
|
437
|
-
if (this.connectionState !== ConnectionState.CONNECTED) {
|
|
438
|
-
this.log("warn", "Not connected, cannot pull messages");
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
if (this.pending > 0) {
|
|
442
|
-
this.log("warn", `Already have outstanding messages: ${this.pending}`);
|
|
321
|
+
sendAck(messageId) {
|
|
322
|
+
this.cleanupOldAckedMessages(Date.now());
|
|
323
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
324
|
+
this.log("error", "Cannot send ACK: WebSocket not connected");
|
|
443
325
|
return;
|
|
444
326
|
}
|
|
445
327
|
try {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}), {
|
|
452
|
-
reply: this.inbox,
|
|
453
|
-
});
|
|
454
|
-
this.pending = MESSAGE_BATCH_SIZE;
|
|
455
|
-
this.log("debug", `Pull sent: batch=${MESSAGE_BATCH_SIZE}, pending=${this.pending}`);
|
|
328
|
+
// Parse UUID string to bytes (16 bytes)
|
|
329
|
+
const uuid = new Uint8Array(parseUuid(messageId));
|
|
330
|
+
// Send the UUID as binary message
|
|
331
|
+
this.ws.send(uuid);
|
|
332
|
+
this.log("debug", `Sent ACK for message: ${messageId}`);
|
|
456
333
|
}
|
|
457
334
|
catch (error) {
|
|
458
|
-
this.log("error",
|
|
459
|
-
throw error;
|
|
335
|
+
this.log("error", `Failed to send ACK for ${messageId}:`, error);
|
|
460
336
|
}
|
|
461
337
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
this.log("debug", "✓ Subscription cleared");
|
|
469
|
-
}
|
|
470
|
-
catch (error) {
|
|
471
|
-
this.log("error", "Error clearing subscription:", error);
|
|
338
|
+
cleanupOldAckedMessages(now) {
|
|
339
|
+
const deadline = now - 60000;
|
|
340
|
+
for (const [messageId, timestamp] of this.ackedMessages.entries()) {
|
|
341
|
+
if (timestamp < deadline) {
|
|
342
|
+
this.ackedMessages.delete(messageId);
|
|
343
|
+
}
|
|
472
344
|
}
|
|
473
|
-
this.subscription = null;
|
|
474
|
-
}
|
|
475
|
-
async resubscribe() {
|
|
476
|
-
if (!this.streamName || !this.consumerName)
|
|
477
|
-
return;
|
|
478
|
-
this.log("info", "Resubscribing after reconnection");
|
|
479
|
-
// Recreate subscription
|
|
480
|
-
await this.createPullSubscription();
|
|
481
|
-
this.log("info", "✓ Resubscribed successfully");
|
|
482
345
|
}
|
|
483
346
|
cleanupState() {
|
|
484
|
-
this.
|
|
485
|
-
this.
|
|
347
|
+
this.sessionToken = null;
|
|
348
|
+
this.automation = null;
|
|
486
349
|
this.outputHandler = null;
|
|
487
350
|
this.sessionHandler = null;
|
|
488
351
|
this.urlHandler = null;
|
|
489
|
-
this.
|
|
490
|
-
this.pending = 0;
|
|
352
|
+
this.ackedMessages.clear();
|
|
491
353
|
}
|
|
492
354
|
async unsubscribe() {
|
|
493
355
|
if (this.connectionState === ConnectionState.DISCONNECTED ||
|
|
@@ -496,19 +358,18 @@ export class PubsubClient {
|
|
|
496
358
|
}
|
|
497
359
|
this.log("info", "Starting unsubscribe process");
|
|
498
360
|
this.connectionState = ConnectionState.CLOSING;
|
|
499
|
-
this.
|
|
361
|
+
this.shouldReconnect = false;
|
|
500
362
|
this.cleanupState();
|
|
501
|
-
this.
|
|
502
|
-
if (this.nc && !this.nc.isClosed()) {
|
|
363
|
+
if (this.ws) {
|
|
503
364
|
try {
|
|
504
|
-
|
|
505
|
-
this.log("debug", "
|
|
365
|
+
this.ws.close(1000, "Client disconnecting");
|
|
366
|
+
this.log("debug", "WebSocket closed");
|
|
506
367
|
}
|
|
507
368
|
catch (error) {
|
|
508
|
-
this.log("error", "Error closing
|
|
369
|
+
this.log("error", "Error closing WebSocket:", error);
|
|
509
370
|
}
|
|
371
|
+
this.ws = null;
|
|
510
372
|
}
|
|
511
|
-
this.nc = null;
|
|
512
373
|
this.connectionState = ConnectionState.DISCONNECTED;
|
|
513
374
|
this.log("info", "Unsubscribed successfully");
|
|
514
375
|
}
|
|
@@ -530,15 +391,4 @@ function parseOutputData(data) {
|
|
|
530
391
|
}
|
|
531
392
|
}));
|
|
532
393
|
}
|
|
533
|
-
function createAuthenticator(val) {
|
|
534
|
-
if (typeof val === "string") {
|
|
535
|
-
return credsAuthenticator(Uint8Array.from(atob(val), (c) => c.charCodeAt(0)));
|
|
536
|
-
}
|
|
537
|
-
else if (val instanceof Uint8Array) {
|
|
538
|
-
return credsAuthenticator(val);
|
|
539
|
-
}
|
|
540
|
-
else {
|
|
541
|
-
throw new Error("Invalid credentials format");
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
394
|
//# sourceMappingURL=index.js.map
|