@ardennguyen/zalo-agent-cli 2.2.0 → 2.2.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.
@@ -28,6 +28,8 @@ export class MessageBuffer {
28
28
  this._globalCursor = 0;
29
29
  /** @type {Map<string, { cursor: number, at: number }>} */
30
30
  this._readCursors = new Map();
31
+ /** @type {Set<{ threadId: string|null, resolve: Function, timer: NodeJS.Timeout }>} */
32
+ this._waiters = new Set();
31
33
  }
32
34
 
33
35
  /**
@@ -40,7 +42,8 @@ export class MessageBuffer {
40
42
  }
41
43
 
42
44
  /**
43
- * Add message to thread buffer. Auto-evicts stale messages.
45
+ * Add message to thread buffer. Auto-evicts stale messages and wakes any
46
+ * long-poll waiters matching the thread.
44
47
  * @param {string} threadId
45
48
  * @param {object} message - Normalized message object
46
49
  */
@@ -54,6 +57,64 @@ export class MessageBuffer {
54
57
  thread.messages.push(message);
55
58
  thread.lastActivity = Date.now();
56
59
  this._evict(threadId);
60
+
61
+ for (const waiter of [...this._waiters]) {
62
+ if (!waiter.threadId || waiter.threadId === String(threadId)) {
63
+ this._waiters.delete(waiter);
64
+ clearTimeout(waiter.timer);
65
+ waiter.resolve(message);
66
+ }
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Wait for the next buffered message (or return immediately if `since` is
72
+ * provided and a newer message is already in the buffer).
73
+ *
74
+ * @param {object} [opts]
75
+ * @param {string} [opts.threadId] - Optional thread filter
76
+ * @param {number} [opts.since] - Optional cursor; if a message > since already exists, resolves immediately
77
+ * @param {number} [opts.timeoutMs=60000] - Max wait duration in milliseconds
78
+ * @param {AbortSignal} [opts.signal] - Optional abort signal (e.g. when HTTP client disconnects)
79
+ * @returns {Promise<object|null>} The message object, or null on timeout/abort
80
+ */
81
+ waitForMessage({ threadId = null, since = undefined, timeoutMs = 60000, signal = undefined } = {}) {
82
+ if (since !== undefined && since !== null && Number.isFinite(Number(since))) {
83
+ const existing = this.read(threadId || undefined, Number(since), 1);
84
+ if (existing.messages.length > 0) {
85
+ return Promise.resolve(existing.messages[0]);
86
+ }
87
+ }
88
+ if (signal?.aborted) {
89
+ return Promise.resolve(null);
90
+ }
91
+ return new Promise((resolve) => {
92
+ const waiter = {
93
+ threadId: threadId ? String(threadId) : null,
94
+ resolve: (msg) => {
95
+ signal?.removeEventListener("abort", onAbort);
96
+ resolve(msg);
97
+ },
98
+ timer: null,
99
+ };
100
+ const onAbort = () => {
101
+ if (this._waiters.delete(waiter)) {
102
+ clearTimeout(waiter.timer);
103
+ resolve(null);
104
+ }
105
+ };
106
+ waiter.timer = setTimeout(
107
+ () => {
108
+ if (this._waiters.delete(waiter)) {
109
+ signal?.removeEventListener("abort", onAbort);
110
+ resolve(null);
111
+ }
112
+ },
113
+ Math.max(1, Number(timeoutMs) || 60000),
114
+ );
115
+ signal?.addEventListener("abort", onAbort, { once: true });
116
+ this._waiters.add(waiter);
117
+ });
57
118
  }
58
119
 
59
120
  /**