@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.
Files changed (52) hide show
  1. package/dist/cjs/genproto/mochabugapis/adapt/automations/v1/automations_pb.js +7 -2
  2. package/dist/cjs/genproto/mochabugapis/adapt/automations/v1/automations_pb.js.map +1 -1
  3. package/dist/cjs/genproto/mochabugapis/adapt/graph/exchange_pb.js +1 -1
  4. package/dist/cjs/genproto/mochabugapis/adapt/graph/exchange_pb.js.map +1 -1
  5. package/dist/cjs/genproto/mochabugapis/adapt/graph/receiver_pb.js +1 -1
  6. package/dist/cjs/genproto/mochabugapis/adapt/graph/receiver_pb.js.map +1 -1
  7. package/dist/cjs/genproto/mochabugapis/adapt/graph/signal_descriptor_pb.js +1 -1
  8. package/dist/cjs/genproto/mochabugapis/adapt/graph/signal_descriptor_pb.js.map +1 -1
  9. package/dist/cjs/genproto/mochabugapis/adapt/graph/transceiver_pb.js +1 -1
  10. package/dist/cjs/genproto/mochabugapis/adapt/graph/transceiver_pb.js.map +1 -1
  11. package/dist/cjs/genproto/mochabugapis/adapt/graph/transmitter_pb.js +1 -1
  12. package/dist/cjs/genproto/mochabugapis/adapt/graph/transmitter_pb.js.map +1 -1
  13. package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/manifest_pb.js +1 -1
  14. package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/manifest_pb.js.map +1 -1
  15. package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/service_binding_pb.js +1 -1
  16. package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/service_binding_pb.js.map +1 -1
  17. package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/service_definition_pb.js +1 -1
  18. package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/service_definition_pb.js.map +1 -1
  19. package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/vertex_pb.js +1 -1
  20. package/dist/cjs/genproto/mochabugapis/adapt/plugins/v1/vertex_pb.js.map +1 -1
  21. package/dist/cjs/genproto/mochabugapis/adapt/runtime/v1/runtime_pb.js +1 -1
  22. package/dist/cjs/genproto/mochabugapis/adapt/runtime/v1/runtime_pb.js.map +1 -1
  23. package/dist/cjs/index.js +174 -318
  24. package/dist/cjs/index.js.map +1 -1
  25. package/dist/esm/genproto/mochabugapis/adapt/automations/v1/automations_pb.js +6 -1
  26. package/dist/esm/genproto/mochabugapis/adapt/automations/v1/automations_pb.js.map +1 -1
  27. package/dist/esm/genproto/mochabugapis/adapt/graph/exchange_pb.js +1 -1
  28. package/dist/esm/genproto/mochabugapis/adapt/graph/exchange_pb.js.map +1 -1
  29. package/dist/esm/genproto/mochabugapis/adapt/graph/receiver_pb.js +1 -1
  30. package/dist/esm/genproto/mochabugapis/adapt/graph/receiver_pb.js.map +1 -1
  31. package/dist/esm/genproto/mochabugapis/adapt/graph/signal_descriptor_pb.js +1 -1
  32. package/dist/esm/genproto/mochabugapis/adapt/graph/signal_descriptor_pb.js.map +1 -1
  33. package/dist/esm/genproto/mochabugapis/adapt/graph/transceiver_pb.js +1 -1
  34. package/dist/esm/genproto/mochabugapis/adapt/graph/transceiver_pb.js.map +1 -1
  35. package/dist/esm/genproto/mochabugapis/adapt/graph/transmitter_pb.js +1 -1
  36. package/dist/esm/genproto/mochabugapis/adapt/graph/transmitter_pb.js.map +1 -1
  37. package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/manifest_pb.js +1 -1
  38. package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/manifest_pb.js.map +1 -1
  39. package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/service_binding_pb.js +1 -1
  40. package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/service_binding_pb.js.map +1 -1
  41. package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/service_definition_pb.js +1 -1
  42. package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/service_definition_pb.js.map +1 -1
  43. package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/vertex_pb.js +1 -1
  44. package/dist/esm/genproto/mochabugapis/adapt/plugins/v1/vertex_pb.js.map +1 -1
  45. package/dist/esm/genproto/mochabugapis/adapt/runtime/v1/runtime_pb.js +1 -1
  46. package/dist/esm/genproto/mochabugapis/adapt/runtime/v1/runtime_pb.js.map +1 -1
  47. package/dist/esm/index.js +169 -319
  48. package/dist/esm/index.js.map +1 -1
  49. package/dist/types/genproto/mochabugapis/adapt/automations/v1/automations_pb.d.ts +148 -16
  50. package/dist/types/genproto/mochabugapis/adapt/runtime/v1/runtime_pb.d.ts +27 -21
  51. package/dist/types/index.d.ts +22 -20
  52. 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 { connect, createInbox, credsAuthenticator, JSONCodec, } from "nats.ws";
4
- import { OutputSchema, SessionSchema, UrlSchema, } from "./genproto/mochabugapis/adapt/automations/v1/automations_pb.js";
5
- // Constants for JetStream protocol
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 MESSAGE_BATCH_SIZE = 10;
15
- const natsUrl = "wss://adapt-dev.mochabugapis.com:443/pubsub";
16
- const baseUrl = "https://adapt-dev.mochabugapis.com/v1/automations";
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.nc = null;
121
- this.subscription = null;
122
- this.streamName = null;
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.jc = JSONCodec();
131
- // Pull consumer state
132
- this.pending = 0;
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
- consumer: opts.consumer,
150
- stream: opts.stream,
157
+ automation: opts.automation,
158
+ hasToken: !!opts.sessionToken,
151
159
  });
152
- if (!opts.consumer || !opts.stream) {
153
- throw new Error("Consumer and stream names are required");
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 consumer and stream
163
+ // Only update the handlers if we stay on the same session
156
164
  if (this.connectionState === ConnectionState.CONNECTED &&
157
- this.streamName === opts.stream &&
158
- this.consumerName === opts.consumer) {
159
- this.log("debug", "Already connected to same subject, updating handlers only");
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, let's ubsubscribe first
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
- // Store configuration
175
- this.streamName = opts.stream;
176
- this.consumerName = opts.consumer;
177
- this.outputHandler = opts.onOutput || null;
178
- this.sessionHandler = opts.onSession || null;
179
- this.urlHandler = opts.onUrl || null;
180
- // Connect to NATS
181
- const inboxPrefix = `_INBOX_${opts.consumer}`;
182
- this.log("debug", `Connecting with inbox prefix: ${inboxPrefix}`);
183
- this.nc = await connect({
184
- servers: [natsUrl],
185
- authenticator: createAuthenticator(opts.credentials),
186
- inboxPrefix: inboxPrefix,
187
- reconnect: true,
188
- noEcho: true,
189
- maxReconnectAttempts: -1,
190
- reconnectTimeWait: 1000,
191
- pingInterval: 10 * 1000,
192
- });
193
- this.connectionState = ConnectionState.CONNECTED;
194
- this.log("info", "Connected successfully");
195
- this.setupStatusMonitoring();
196
- await this.createPullSubscription();
197
- }
198
- catch (error) {
199
- this.connectionState = ConnectionState.DISCONNECTED;
200
- this.cleanupState();
201
- if (this.nc) {
202
- try {
203
- await this.nc.close();
204
- }
205
- catch (closeError) {
206
- this.log("error", "Error closing connection after failed subscribe:", closeError);
207
- }
208
- this.nc = null;
209
- }
210
- this.log("error", "Failed to subscribe:", error);
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
- catch (error) {
263
- if (this.connectionState !== ConnectionState.CLOSING) {
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.log("error", "Failed to create subscriptions:", error);
244
+ this.connectionState = ConnectionState.DISCONNECTED;
245
+ this.log("error", "Failed to connect:", error);
288
246
  throw error;
289
247
  }
290
248
  }
291
- async processMessages(sub) {
292
- try {
293
- for await (const msg of sub) {
294
- // Early exit if we're closing or disconnected
295
- if (this.connectionState === ConnectionState.CLOSING ||
296
- this.connectionState === ConnectionState.DISCONNECTED) {
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
- catch (error) {
309
- if (this.connectionState !== ConnectionState.CLOSING) {
310
- this.log("error", "Message processing error:", error);
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
- async handleMessage(msg) {
315
- console.log("debug", "The complete message", msg);
316
- // Check if we have headers with status codes (JetStream control messages)
317
- if (msg.headers) {
318
- const status = msg.headers.code;
319
- const description = msg.headers.description;
320
- if (status) {
321
- this.log("debug", `Status message: ${status} - ${description}`);
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
- // Get the actual stream subject
327
- if (!msg.subject) {
328
- this.log("warn", `Message without valid subject`);
329
- return;
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 = fromBinary(OutputSchema, msg.data);
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: timestampDate(output.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 = fromBinary(UrlSchema, msg.data);
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("error", `Unknown message action: ${action}`);
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", `Error processing ${action} message:`, error);
382
- this.nak(msg);
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
- async nak(msg) {
411
- if (!msg || !this.nc) {
412
- throw new Error("Invalid message or connection not established");
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
- const pullSubject = `${CONSUMER_MSG_NEXT_PREFIX}${this.streamName}.${this.consumerName}`;
447
- this.log("debug", `Pulling messages from: ${pullSubject}`);
448
- this.nc.publish(pullSubject, this.jc.encode({
449
- batch: MESSAGE_BATCH_SIZE,
450
- no_wait: false,
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", "Error sending pull request:", error);
459
- throw error;
335
+ this.log("error", `Failed to send ACK for ${messageId}:`, error);
460
336
  }
461
337
  }
462
- clearSubscriptions() {
463
- if (!this.subscription)
464
- return;
465
- this.log("debug", "Clearing subscription");
466
- try {
467
- this.subscription.unsubscribe();
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.streamName = null;
485
- this.consumerName = null;
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.inbox = null;
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.clearSubscriptions();
361
+ this.shouldReconnect = false;
500
362
  this.cleanupState();
501
- this.isReconnecting = false;
502
- if (this.nc && !this.nc.isClosed()) {
363
+ if (this.ws) {
503
364
  try {
504
- await this.nc.close();
505
- this.log("debug", "Connection closed");
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 connection:", error);
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