@diegoaltoworks/chatter 0.40.0 → 0.41.0

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.
@@ -156,6 +156,8 @@ var MAX_PAIRING_RECONNECTS = 12;
156
156
  var PAIRING_RECONNECT_DELAY_MS = 2e3;
157
157
  var PAIRING_RECONNECT_MAX_DELAY_MS = 8e3;
158
158
  var RESTART_REQUIRED_DELAY_MS = 500;
159
+ var REGISTERED_CONFIRM_TIMEOUT_MS = 15e3;
160
+ var REGISTERED_CONFIRM_POLL_MS = 500;
159
161
  function pairingReconnectDelayMs(statusCode, attempt) {
160
162
  if (statusCode === RESTART_REQUIRED_STATUS) return RESTART_REQUIRED_DELAY_MS;
161
163
  return Math.min(
@@ -192,6 +194,41 @@ function runPairing(deps) {
192
194
  resolve(result);
193
195
  };
194
196
  const status = (message) => deps.onStatus?.(message);
197
+ const wait = (ms) => new Promise((resolve2) => {
198
+ schedule(() => resolve2(), ms);
199
+ });
200
+ let saves = Promise.resolve();
201
+ const queueSave = (saveCreds) => {
202
+ saves = saves.then(saveCreds).catch((error) => {
203
+ status(`Failed to save the session credentials: ${describeError(error)}`);
204
+ });
205
+ };
206
+ const flushSaves = async () => {
207
+ let awaited;
208
+ while (awaited !== saves) {
209
+ awaited = saves;
210
+ await awaited;
211
+ }
212
+ };
213
+ const confirmRegistered = async () => {
214
+ await flushSaves();
215
+ const verify = deps.verifyRegistered;
216
+ if (!verify) return true;
217
+ const pollMs = deps.registeredPollMs ?? REGISTERED_CONFIRM_POLL_MS;
218
+ const polls = Math.max(
219
+ 1,
220
+ Math.ceil((deps.registeredTimeoutMs ?? REGISTERED_CONFIRM_TIMEOUT_MS) / pollMs)
221
+ );
222
+ for (let poll = 0; poll < polls; poll++) {
223
+ if (await verify()) return true;
224
+ if (poll === polls - 1) break;
225
+ if (poll === 0)
226
+ status("Link is open \u2014 waiting for the session to be saved as registered...");
227
+ await wait(pollMs);
228
+ await flushSaves();
229
+ }
230
+ return false;
231
+ };
195
232
  const requestCode = async (sock) => {
196
233
  if (settled || codeRequested || !deps.phoneNumber) return;
197
234
  try {
@@ -216,7 +253,15 @@ function runPairing(deps) {
216
253
  }
217
254
  }
218
255
  if (connection === "open") {
219
- settle({ ok: true, userId: sock.user?.id });
256
+ if (await confirmRegistered()) {
257
+ settle({ ok: true, userId: sock.user?.id });
258
+ } else {
259
+ settle({
260
+ ok: false,
261
+ reason: "unregistered",
262
+ message: "The link opened but the stored session never read back as registered, so the server would not find it. Run wa-pair again (add --reset to pair from scratch)."
263
+ });
264
+ }
220
265
  return;
221
266
  }
222
267
  if (connection === "close") {
@@ -252,13 +297,29 @@ function runPairing(deps) {
252
297
  if (settled) return;
253
298
  const mine = generation;
254
299
  const attempt = attempts;
300
+ await flushSaves();
301
+ if (settled) return;
255
302
  let sock;
303
+ let saveCreds;
256
304
  try {
257
- sock = await deps.connect(attempt);
305
+ const opened = await deps.connect(attempt);
306
+ if ("sock" in opened) {
307
+ sock = opened.sock;
308
+ saveCreds = opened.saveCreds;
309
+ } else {
310
+ sock = opened;
311
+ }
258
312
  } catch (error) {
259
313
  settle({ ok: false, reason: "error", message: describeError(error) });
260
314
  return;
261
315
  }
316
+ if (saveCreds) {
317
+ const save = saveCreds;
318
+ sock.ev.on("creds.update", () => {
319
+ if (settled || mine !== generation) return;
320
+ queueSave(save);
321
+ });
322
+ }
262
323
  sock.ev.on("connection.update", (update) => {
263
324
  if (mine !== generation || settled) return;
264
325
  handleUpdate(update, sock).catch((error) => {
@@ -418,11 +479,15 @@ async function start() {
418
479
  logger,
419
480
  markOnlineOnConnect: false
420
481
  });
421
- sock.ev.on("creds.update", saveCreds);
422
- return sock;
482
+ return { sock, saveCreds };
483
+ };
484
+ const storedIsRegistered = async () => {
485
+ const { state } = await useTursoAuthState(db, sessionSecret, sessionId, runtime);
486
+ return state.creds.registered === true;
423
487
  };
424
488
  const result = await runPairing({
425
489
  connect,
490
+ verifyRegistered: storedIsRegistered,
426
491
  loggedOutCode: baileys.DisconnectReason.loggedOut,
427
492
  phoneNumber,
428
493
  onQr: renderQr,
@@ -440,8 +505,10 @@ async function start() {
440
505
  console.error(`\u274C ${result.message}`);
441
506
  process.exit(1);
442
507
  }
443
- console.log(`
444
- \u2705 Paired session "${sessionId}" as ${result.userId}. Session saved (encrypted).`);
508
+ console.log(
509
+ `
510
+ \u2705 Paired session "${sessionId}" as ${result.userId}. Session saved (encrypted) and verified registered.`
511
+ );
445
512
  console.log("The running server will pick this session up on its next connect.");
446
513
  process.exit(0);
447
514
  }
@@ -14,6 +14,15 @@
14
14
  * So a close during pairing is normal, not fatal: reconnect with stored creds,
15
15
  * bounded, until `open` confirms the link. Only an explicit logout or running
16
16
  * out of attempts ends the run.
17
+ *
18
+ * Credentials are persisted by this loop rather than by the caller's socket
19
+ * wiring, because the saves have to be ordered against the reconnects: the
20
+ * registration that a 515 interrupts is only useful if it reaches storage
21
+ * BEFORE the next attempt re-reads the session, and the flag that marks the
22
+ * session usable (`registered`) is often written by a `creds.update` that
23
+ * lands around or after `open`. A run therefore ends successfully only once
24
+ * the caller confirms — by reading storage back — that the stored session is
25
+ * registered.
17
26
  */
18
27
  /** Baileys' "restart required" — emitted immediately after a QR scan registers the device. */
19
28
  export declare const RESTART_REQUIRED_STATUS = 515;
@@ -52,7 +61,7 @@ export declare function decidePairingClose(input: PairingCloseInput): PairingClo
52
61
  /** The slice of a Baileys socket the pairing loop uses. */
53
62
  export interface PairingSocket {
54
63
  ev: {
55
- on: (event: "connection.update", listener: (update: PairingConnectionUpdate) => void) => void;
64
+ on: (event: "connection.update" | "creds.update", listener: (update: PairingConnectionUpdate) => void) => void;
56
65
  };
57
66
  user?: {
58
67
  id?: string;
@@ -61,6 +70,16 @@ export interface PairingSocket {
61
70
  /** Closes a superseded socket so it stops writing to the session. */
62
71
  end?: (error?: Error) => void;
63
72
  }
73
+ /**
74
+ * A socket plus the way to persist the credentials it mutates. Returning the
75
+ * pair (instead of wiring `creds.update` at the call site) lets the loop order
76
+ * saves against reconnects — see the module comment.
77
+ */
78
+ export interface PairingAttempt {
79
+ sock: PairingSocket;
80
+ /** Writes this attempt's credentials to storage; called on every `creds.update`. */
81
+ saveCreds?: () => Promise<void>;
82
+ }
64
83
  export interface PairingConnectionUpdate {
65
84
  connection?: string;
66
85
  qr?: string;
@@ -73,17 +92,29 @@ export type PairingResult = {
73
92
  userId?: string;
74
93
  } | {
75
94
  ok: false;
76
- reason: "loggedOut" | "exhausted" | "error";
95
+ reason: "loggedOut" | "exhausted" | "error" | "unregistered";
77
96
  message: string;
78
97
  };
79
98
  export interface PairingRunDeps {
80
99
  /**
81
- * Opens a socket for `attempt` (0 = first). MUST reuse the stored
100
+ * Opens a socket for `attempt` (0 = first). MUST re-read the stored
82
101
  * credentials — a reconnect that starts from blank creds shows a fresh QR
83
102
  * instead of finishing the link the phone is waiting on.
84
103
  */
85
- connect: (attempt: number) => PairingSocket | Promise<PairingSocket>;
104
+ connect: (attempt: number) => PairingSocket | PairingAttempt | Promise<PairingSocket | PairingAttempt>;
86
105
  loggedOutCode: number;
106
+ /**
107
+ * Reads the STORED session back and answers whether it is registered — i.e.
108
+ * whether the server would find a usable session. Polled after `open` while
109
+ * late `creds.update` events are still being persisted; a run that never
110
+ * sees `true` fails with reason `unregistered` rather than claiming success
111
+ * over a session nothing can use. Omit to skip the check.
112
+ */
113
+ verifyRegistered?: () => boolean | Promise<boolean>;
114
+ /** How long to wait for the stored session to read back registered. */
115
+ registeredTimeoutMs?: number;
116
+ /** Gap between those reads. */
117
+ registeredPollMs?: number;
87
118
  /** Digits-only number for pairing-code mode; omit for QR mode. */
88
119
  phoneNumber?: string;
89
120
  maxAttempts?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/pairing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/pairing.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegoaltoworks/chatter",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "Embeddable AI chatbot framework with RAG, authentication, and customizable widgets",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",