@oxidezap/baileyrs 0.2.11 → 0.2.13
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/lib/Bridge/index.d.ts +1 -1
- package/lib/Bridge/index.js +1 -1
- package/lib/Bridge/primitives.d.ts +18 -6
- package/lib/Bridge/primitives.js +26 -13
- package/lib/Bridge/schema.js +98 -16
- package/lib/Bridge/types.d.ts +11 -0
- package/lib/Compatibility/encode-proto.d.ts +7 -3
- package/lib/Compatibility/encode-proto.js +10 -5
- package/lib/Compatibility/proto-runtime.js +54 -7
- package/lib/Socket/events.js +1 -36
- package/lib/Socket/groups.js +35 -2
- package/lib/Socket/index.js +102 -18
- package/lib/Socket/terminal-close-reporter.d.ts +29 -8
- package/lib/Socket/terminal-close-reporter.js +31 -8
- package/lib/Socket/terminal-close.d.ts +2 -36
- package/lib/Socket/terminal-close.js +29 -5
- package/lib/Socket/unsupported-config.d.ts +1 -1
- package/lib/Socket/unsupported-config.js +1 -0
- package/lib/Types/Socket.d.ts +8 -0
- package/lib/Utils/use-bridge-store.d.ts +74 -3
- package/lib/Utils/use-bridge-store.js +541 -201
- package/package.json +3 -2
package/lib/Socket/index.js
CHANGED
|
@@ -31,6 +31,7 @@ import { makeBridgeClientOwner } from './bridge-client-owner.js';
|
|
|
31
31
|
import { warnUnsupportedConfig } from './unsupported-config.js';
|
|
32
32
|
import { wrapBridgeClient } from './bridge-error-boundary.js';
|
|
33
33
|
import { makeTerminalCloseReporter } from './terminal-close-reporter.js';
|
|
34
|
+
import { mapConnectFailureToDisconnect } from './terminal-close.js';
|
|
34
35
|
import { makeEventHandlers } from './events.js';
|
|
35
36
|
import { makeGroupMethods } from './groups.js';
|
|
36
37
|
import { makeInternalMethods, makeUnexpectedErrorReporter } from './internals.js';
|
|
@@ -71,6 +72,29 @@ const browserToPlatformType = (browser) => {
|
|
|
71
72
|
return 'CHROME';
|
|
72
73
|
}
|
|
73
74
|
};
|
|
75
|
+
const COMPLETION_FAILURE_CODES = new Map([
|
|
76
|
+
['Generic', 400],
|
|
77
|
+
['LoggedOut', 401],
|
|
78
|
+
['TempBanned', 402],
|
|
79
|
+
['AccountLocked', 403],
|
|
80
|
+
['UnknownLogout', 406],
|
|
81
|
+
['ClientOutdated', 405],
|
|
82
|
+
['BadUserAgent', 409],
|
|
83
|
+
['CatExpired', 413],
|
|
84
|
+
['CatInvalid', 414],
|
|
85
|
+
['NotFound', 415],
|
|
86
|
+
['ClientUnknown', 418],
|
|
87
|
+
['InternalServerError', 500],
|
|
88
|
+
['Experimental', 501],
|
|
89
|
+
['ServiceUnavailable', 503]
|
|
90
|
+
]);
|
|
91
|
+
const completionFailureCode = (reason) => {
|
|
92
|
+
const named = COMPLETION_FAILURE_CODES.get(reason);
|
|
93
|
+
if (named !== undefined)
|
|
94
|
+
return named;
|
|
95
|
+
const unknown = /^Unknown\((-?\d+)\)$/.exec(reason)?.[1];
|
|
96
|
+
return unknown === undefined ? undefined : Number(unknown);
|
|
97
|
+
};
|
|
74
98
|
/** Build the ws EventEmitter with auto-enable raw node forwarding */
|
|
75
99
|
const makeWASocket = (config) => {
|
|
76
100
|
const fullConfig = { ...DEFAULT_CONNECTION_CONFIG, ...config };
|
|
@@ -261,6 +285,37 @@ const makeWASocket = (config) => {
|
|
|
261
285
|
let autoReconnectEnabled = true;
|
|
262
286
|
/** Owns reporting the terminal close: once, after teardown, never not at all. */
|
|
263
287
|
const terminalClose = makeTerminalCloseReporter({ logger });
|
|
288
|
+
const runCompletionError = (completion) => {
|
|
289
|
+
let statusCode = DisconnectReason.connectionClosed;
|
|
290
|
+
let message = 'Connection closed';
|
|
291
|
+
if (completion.reason === 'unknown') {
|
|
292
|
+
message = `Connection run ended: ${completion.detail}`;
|
|
293
|
+
}
|
|
294
|
+
else if (completion.reason === 'stopped') {
|
|
295
|
+
message = 'Connection run stopped';
|
|
296
|
+
}
|
|
297
|
+
else if (completion.reason === 'already-running') {
|
|
298
|
+
message = 'Connection run was already running';
|
|
299
|
+
}
|
|
300
|
+
else if (completion.reason === 'auto-reconnect-disabled') {
|
|
301
|
+
const protocol = completion.protocolError;
|
|
302
|
+
if (protocol?.kind === 'conflict') {
|
|
303
|
+
statusCode = DisconnectReason.connectionReplaced;
|
|
304
|
+
message = 'Connection replaced';
|
|
305
|
+
}
|
|
306
|
+
else if (protocol?.kind === 'stream-error') {
|
|
307
|
+
statusCode = mapConnectFailureToDisconnect(protocol.code);
|
|
308
|
+
}
|
|
309
|
+
else if (protocol?.kind === 'connect-failure') {
|
|
310
|
+
statusCode = mapConnectFailureToDisconnect(completionFailureCode(protocol.reason));
|
|
311
|
+
}
|
|
312
|
+
else if (completion.connection?.kind === 'server-close') {
|
|
313
|
+
message = completion.connection.reason;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return new Boom(message, { statusCode, data: { runCompletion: completion } });
|
|
317
|
+
};
|
|
318
|
+
const reportTerminalClose = (error, publish) => terminalClose.reportAfter(() => owner.close(error).finally(() => initPromise), publish);
|
|
264
319
|
/**
|
|
265
320
|
* Held in a reporter rather than captured, because `sock.onUnexpectedError`
|
|
266
321
|
* is an assignable property: a consumer that replaces it has to be the one
|
|
@@ -401,7 +456,7 @@ const makeWASocket = (config) => {
|
|
|
401
456
|
// is still owned. This waits for the real teardown, and cannot
|
|
402
457
|
// deadlock because `reportAfter` runs it detached; nothing in the
|
|
403
458
|
// teardown is waiting on this.
|
|
404
|
-
|
|
459
|
+
reportTerminalClose(error, publish);
|
|
405
460
|
},
|
|
406
461
|
isAutoReconnectEnabled: () => autoReconnectEnabled,
|
|
407
462
|
// Timers the dispatcher armed outlive the events that armed them, and
|
|
@@ -454,7 +509,13 @@ const makeWASocket = (config) => {
|
|
|
454
509
|
}
|
|
455
510
|
if (useNativeMemory)
|
|
456
511
|
logger.debug('auth: using socket-local native memory backend');
|
|
457
|
-
const created = await createWhatsAppClient(makeTransport(fullConfig), makeHttpClient(fullConfig), eventHandlers, bridgeStore, fullConfig.cache ?? null, fullConfig.version, fullConfig.wantedPreKeyCount ?? null
|
|
512
|
+
const created = await createWhatsAppClient(makeTransport(fullConfig), makeHttpClient(fullConfig), eventHandlers, bridgeStore, fullConfig.cache ?? null, fullConfig.version, fullConfig.wantedPreKeyCount ?? null,
|
|
513
|
+
// Passed through exactly as configured, never normalized by truthiness:
|
|
514
|
+
// the bridge only honours a literal `true` here and rejects any
|
|
515
|
+
// other truthy value at construction, so a `!!`/ternary-style
|
|
516
|
+
// coercion could promote a malformed opt-out into an opt-in.
|
|
517
|
+
// Absent stays strict.
|
|
518
|
+
fullConfig.dangerSkipCertChainVerify);
|
|
458
519
|
// `end()` can land while the client is still being built — a `sock.end()`
|
|
459
520
|
// or `await using` right after `makeWASocket()` does exactly that. When
|
|
460
521
|
// it has, `adopt` frees this client and tells us to stop: nothing else
|
|
@@ -521,23 +582,46 @@ const makeWASocket = (config) => {
|
|
|
521
582
|
// starting the read loop now would run against a handle about to go.
|
|
522
583
|
if (owner.isClosing())
|
|
523
584
|
return;
|
|
524
|
-
// `run()`
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
// Consequence: the loop's exit is not observable from here. The engine
|
|
530
|
-
// clears `enable_auto_reconnect` and breaks out on every terminal
|
|
531
|
-
// disconnect (conflict/401/409/516, and any `<failure>` whose reason is
|
|
532
|
-
// not 500/503), and when it does, the `WasmWhatsAppClient` is dead
|
|
533
|
-
// weight that only `sock.end()` can free — nothing else can, because
|
|
534
|
-
// the bridge holds the JS event callbacks as wasm-bindgen externrefs,
|
|
535
|
-
// those close over `ctx`, and `ctx` closes over `client`, so the cycle
|
|
536
|
-
// crosses the JS/wasm boundary and no `FinalizationRegistry` fires.
|
|
537
|
-
// Freeing that automatically needs the bridge to expose loop completion
|
|
538
|
-
// (a terminal callback or an awaitable handle); until it does, the
|
|
539
|
-
// consumer has to call `sock.end()` on a terminal close.
|
|
585
|
+
// `run()` deliberately returns immediately so callers can use the
|
|
586
|
+
// client while supervision owns its background task. Registering the
|
|
587
|
+
// completion observer after it is started is safe: bridge 0.21.0 admits
|
|
588
|
+
// late observers against the stored result for this run generation.
|
|
540
589
|
created.run();
|
|
590
|
+
const observedClient = created;
|
|
591
|
+
void created
|
|
592
|
+
.waitForRunCompletion()
|
|
593
|
+
.then(completion => {
|
|
594
|
+
// The owner identity is the socket generation fence. A completion
|
|
595
|
+
// from a client that teardown already released must never close a
|
|
596
|
+
// later socket using the same auth state.
|
|
597
|
+
if (owner.isClosing() || owner.peek() !== observedClient)
|
|
598
|
+
return;
|
|
599
|
+
const error = runCompletionError(completion);
|
|
600
|
+
reportTerminalClose(error, () => ev.emit('connection.update', {
|
|
601
|
+
connection: 'close',
|
|
602
|
+
lastDisconnect: { error, date: new Date() }
|
|
603
|
+
}));
|
|
604
|
+
}, error => {
|
|
605
|
+
if (owner.isClosing() || owner.peek() !== observedClient)
|
|
606
|
+
return;
|
|
607
|
+
const closeError = new Boom('Connection run ended without a completion result', {
|
|
608
|
+
statusCode: DisconnectReason.connectionClosed
|
|
609
|
+
});
|
|
610
|
+
reportTerminalClose(closeError, () => ev.emit('connection.update', {
|
|
611
|
+
connection: 'close',
|
|
612
|
+
lastDisconnect: { error: closeError, date: new Date() }
|
|
613
|
+
}));
|
|
614
|
+
try {
|
|
615
|
+
logger.error({ err: error }, 'bridge run completion observation failed');
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
// A consumer logger cannot prevent the terminal cleanup above.
|
|
619
|
+
}
|
|
620
|
+
})
|
|
621
|
+
.catch(() => {
|
|
622
|
+
// The reporter contains teardown and publish failures; this final guard
|
|
623
|
+
// also contains a consumer logger that throws from an observation path.
|
|
624
|
+
});
|
|
541
625
|
initialized = true;
|
|
542
626
|
};
|
|
543
627
|
/**
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reports the terminal `connection.update { close }` — exactly once,
|
|
3
|
-
*
|
|
2
|
+
* Reports the terminal `connection.update { close }` — exactly once, and
|
|
3
|
+
* never silently not at all.
|
|
4
|
+
*
|
|
5
|
+
* The accepted claim publishes after its teardown settles, so a consumer that
|
|
6
|
+
* answers the close with a replacement socket does not overlap the old one's
|
|
7
|
+
* release. The watchdog is the deliberate exception: past the timeout the
|
|
8
|
+
* close goes out with teardown still running (and logged), because losing the
|
|
9
|
+
* event entirely is the worse failure. Nothing here promises that every close
|
|
10
|
+
* lands after every resource is released — only that at most one close lands.
|
|
4
11
|
*
|
|
5
12
|
* That sentence is the whole contract this branch sells, and getting it wrong
|
|
6
13
|
* has two opposite failure modes, both bad:
|
|
@@ -8,9 +15,8 @@
|
|
|
8
15
|
* - **Not reported.** The consumer's handler never runs, so it never builds a
|
|
9
16
|
* replacement socket. A bot offline with nothing in its logs — the original
|
|
10
17
|
* bug this branch exists to fix.
|
|
11
|
-
* - **Reported
|
|
12
|
-
* one
|
|
13
|
-
* notifications for one socket and a handler that cleans up on close loops.
|
|
18
|
+
* - **Reported twice.** Every listener sees two terminal notifications for
|
|
19
|
+
* one socket and a handler that cleans up on close loops.
|
|
14
20
|
*
|
|
15
21
|
* Keeping both away used to be inline logic split across the dispatcher hook
|
|
16
22
|
* and `logout()`, sharing a counter and a promise. Nine separate bugs came out
|
|
@@ -41,13 +47,26 @@ export interface TerminalCloseReporter {
|
|
|
41
47
|
*
|
|
42
48
|
* Never rejects and never leaves `publish` uncalled: teardown failures are
|
|
43
49
|
* logged, a throwing listener is contained, and a teardown that hangs is
|
|
44
|
-
*
|
|
50
|
+
* reported past by the watchdog.
|
|
51
|
+
*
|
|
52
|
+
* Idempotent per socket: the first claim wins and every later call is
|
|
53
|
+
* ignored entirely — neither its teardown nor its publish is invoked. A
|
|
54
|
+
* socket never gets a second generation — a terminal close means "build a
|
|
55
|
+
* new socket" — so a second terminal event (a logout racing a dispatcher
|
|
56
|
+
* close, a late duplicate dispatch) must not publish again.
|
|
57
|
+
*
|
|
58
|
+
* The watchdog bounds how long `published()` waits, not the teardown
|
|
59
|
+
* itself: it neither cancels nor settles the underlying teardown, and a
|
|
60
|
+
* teardown that finishes late publishes nothing further.
|
|
45
61
|
*/
|
|
46
62
|
reportAfter: (teardown: () => Promise<void>, publish: () => void) => void;
|
|
47
63
|
/**
|
|
48
64
|
* Publish immediately, for a close nothing else will announce — a `logout()`
|
|
49
65
|
* with no live client, or one whose `logout()` threw before the bridge
|
|
50
66
|
* dispatched anything.
|
|
67
|
+
*
|
|
68
|
+
* Part of the same single claim as `reportAfter`: a no-op when a close
|
|
69
|
+
* was already claimed.
|
|
51
70
|
*/
|
|
52
71
|
reportNow: (publish: () => void) => void;
|
|
53
72
|
/**
|
|
@@ -63,11 +82,13 @@ export interface TerminalCloseReporter {
|
|
|
63
82
|
*/
|
|
64
83
|
hasReported: () => boolean;
|
|
65
84
|
/**
|
|
66
|
-
* Settles once the
|
|
85
|
+
* Settles once the single report has been published — the moment the
|
|
67
86
|
* consumer sees it, not the moment teardown finishes, so a waiter is not
|
|
68
87
|
* left hanging when the watchdog is what released the event.
|
|
69
88
|
*
|
|
70
|
-
* Resolves immediately when nothing has been reported.
|
|
89
|
+
* Resolves immediately when nothing has been reported. Every waiter
|
|
90
|
+
* observes the same promise, so concurrent `logout()` and dispatcher
|
|
91
|
+
* paths cannot split across generations.
|
|
71
92
|
*/
|
|
72
93
|
published: () => Promise<void>;
|
|
73
94
|
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reports the terminal `connection.update { close }` — exactly once,
|
|
3
|
-
*
|
|
2
|
+
* Reports the terminal `connection.update { close }` — exactly once, and
|
|
3
|
+
* never silently not at all.
|
|
4
|
+
*
|
|
5
|
+
* The accepted claim publishes after its teardown settles, so a consumer that
|
|
6
|
+
* answers the close with a replacement socket does not overlap the old one's
|
|
7
|
+
* release. The watchdog is the deliberate exception: past the timeout the
|
|
8
|
+
* close goes out with teardown still running (and logged), because losing the
|
|
9
|
+
* event entirely is the worse failure. Nothing here promises that every close
|
|
10
|
+
* lands after every resource is released — only that at most one close lands.
|
|
4
11
|
*
|
|
5
12
|
* That sentence is the whole contract this branch sells, and getting it wrong
|
|
6
13
|
* has two opposite failure modes, both bad:
|
|
@@ -8,9 +15,8 @@
|
|
|
8
15
|
* - **Not reported.** The consumer's handler never runs, so it never builds a
|
|
9
16
|
* replacement socket. A bot offline with nothing in its logs — the original
|
|
10
17
|
* bug this branch exists to fix.
|
|
11
|
-
* - **Reported
|
|
12
|
-
* one
|
|
13
|
-
* notifications for one socket and a handler that cleans up on close loops.
|
|
18
|
+
* - **Reported twice.** Every listener sees two terminal notifications for
|
|
19
|
+
* one socket and a handler that cleans up on close loops.
|
|
14
20
|
*
|
|
15
21
|
* Keeping both away used to be inline logic split across the dispatcher hook
|
|
16
22
|
* and `logout()`, sharing a counter and a promise. Nine separate bugs came out
|
|
@@ -35,9 +41,24 @@ export const TERMINAL_CLOSE_PUBLISH_TIMEOUT_MS = 60000;
|
|
|
35
41
|
export const makeTerminalCloseReporter = (opts) => {
|
|
36
42
|
const { logger } = opts;
|
|
37
43
|
const publishTimeoutMs = opts.publishTimeoutMs ?? TERMINAL_CLOSE_PUBLISH_TIMEOUT_MS;
|
|
38
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Claims, not deliveries — see `hasReported`. At most one: a socket has a
|
|
46
|
+
* single terminal generation, so the first claim wins and later ones are
|
|
47
|
+
* ignored rather than published.
|
|
48
|
+
*/
|
|
39
49
|
let claimed = 0;
|
|
40
50
|
let publishedPromise;
|
|
51
|
+
/**
|
|
52
|
+
* True once the single terminal generation has been claimed. Deliberately
|
|
53
|
+
* log-free: a duplicate arrives on a path whose logger is
|
|
54
|
+
* consumer-replaceable, and an ignored signal must never throw.
|
|
55
|
+
*/
|
|
56
|
+
const claim = () => {
|
|
57
|
+
if (claimed > 0)
|
|
58
|
+
return false;
|
|
59
|
+
claimed++;
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
41
62
|
/** One publish per claim, whatever gets there first, and never throwing. */
|
|
42
63
|
const makeOnce = (publish, settle) => {
|
|
43
64
|
let done = false;
|
|
@@ -59,7 +80,8 @@ export const makeTerminalCloseReporter = (opts) => {
|
|
|
59
80
|
};
|
|
60
81
|
return {
|
|
61
82
|
reportAfter: (teardown, publish) => {
|
|
62
|
-
|
|
83
|
+
if (!claim())
|
|
84
|
+
return;
|
|
63
85
|
let settle;
|
|
64
86
|
publishedPromise = new Promise(resolve => {
|
|
65
87
|
settle = resolve;
|
|
@@ -94,7 +116,8 @@ export const makeTerminalCloseReporter = (opts) => {
|
|
|
94
116
|
}
|
|
95
117
|
},
|
|
96
118
|
reportNow: publish => {
|
|
97
|
-
|
|
119
|
+
if (!claim())
|
|
120
|
+
return;
|
|
98
121
|
let settle;
|
|
99
122
|
publishedPromise = new Promise(resolve => {
|
|
100
123
|
settle = resolve;
|
|
@@ -1,39 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Which bridge disconnects end the socket for good.
|
|
3
|
-
*
|
|
4
|
-
* This mirrors one decision that lives in the Rust engine, so keep it honest:
|
|
5
|
-
* `whatsapp-rust` clears `enable_auto_reconnect` and breaks out of its run loop
|
|
6
|
-
* on exactly these, and retries everything else on the WA Web Fibonacci backoff
|
|
7
|
-
* (`client/lifecycle.rs`).
|
|
8
|
-
*
|
|
9
|
-
* The socket layer needs the answer because `WasmWhatsAppClient.run()` returns
|
|
10
|
-
* `void` — it spawns the loop as a background task, so the loop's exit is not
|
|
11
|
-
* observable from JS (`whatsapp_rust_bridge.d.ts`, `run(): void`). Until the
|
|
12
|
-
* bridge exposes loop completion, this table is how the socket knows a client
|
|
13
|
-
* has become dead weight that only `free()` can reclaim.
|
|
14
|
-
*
|
|
15
|
-
* The upstream Baileys contract this buys us: `connection.update { close }`
|
|
16
|
-
* means "this socket is finished, build a new one" — which is what every
|
|
17
|
-
* consumer written against upstream already assumes, because upstream has no
|
|
18
|
-
* auto-reconnect at all. Transient drops therefore never surface as `close`;
|
|
19
|
-
* they surface as `connecting`.
|
|
20
|
-
*
|
|
21
|
-
* Events deliberately absent from the terminal set, and why:
|
|
22
|
-
* - `disconnected` — the engine only dispatches `Event::Disconnected` for an
|
|
23
|
-
* *unexpected* loop exit, and every terminal path sets `expected_disconnect`
|
|
24
|
-
* first, which suppresses it (`client/lifecycle.rs`). So this one is the
|
|
25
|
-
* "engine is retrying" signal — with one exception the dispatcher handles:
|
|
26
|
-
* under `setAutoReconnect(false)` the run loop dispatches `Disconnected`
|
|
27
|
-
* and only *then* tests the flag and breaks out, which makes the very same
|
|
28
|
-
* event terminal. Absence from this list means "not terminal on its own",
|
|
29
|
-
* not "never terminal".
|
|
30
|
-
* - `streamError` — reaches JS only from the engine's catch-all `<stream:error>`
|
|
31
|
-
* branch (unknown code, `<ack/>`, `<xml-not-well-formed>`); the coded ones
|
|
32
|
-
* (401/409/515/516/429/503) dispatch their own events instead. Every case
|
|
33
|
-
* that gets here keeps the connection or recycles it deliberately.
|
|
34
|
-
* - `pairError` — pairing failed, but the engine keeps its loop and re-emits a
|
|
35
|
-
* QR; nothing about the client is dead.
|
|
36
|
-
*/
|
|
37
1
|
/** True when the engine will keep retrying after this `<failure>`. */
|
|
38
2
|
export declare const isReconnectableConnectFailure: (reason: number | undefined) => boolean;
|
|
3
|
+
/** Map a typed bridge connect-failure code to Baileys' close status. */
|
|
4
|
+
export declare const mapConnectFailureToDisconnect: (reason: number | undefined) => number;
|
|
39
5
|
//# sourceMappingURL=terminal-close.d.ts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DisconnectReason } from '../Types/index.js';
|
|
1
2
|
/**
|
|
2
3
|
* Which bridge disconnects end the socket for good.
|
|
3
4
|
*
|
|
@@ -6,11 +7,10 @@
|
|
|
6
7
|
* on exactly these, and retries everything else on the WA Web Fibonacci backoff
|
|
7
8
|
* (`client/lifecycle.rs`).
|
|
8
9
|
*
|
|
9
|
-
* The socket layer needs the answer
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* has become dead weight that only `free()` can reclaim.
|
|
10
|
+
* The socket layer needs the answer for bridge events that classify a terminal
|
|
11
|
+
* close before the supervised run completion is observed. Bridge 0.21.0 also
|
|
12
|
+
* exposes `waitForRunCompletion()` for exits with no terminal event; that
|
|
13
|
+
* observer uses the same terminal-close reporter and owner as this table.
|
|
14
14
|
*
|
|
15
15
|
* The upstream Baileys contract this buys us: `connection.update { close }`
|
|
16
16
|
* means "this socket is finished, build a new one" — which is what every
|
|
@@ -48,4 +48,28 @@ const RECONNECTABLE_CONNECT_FAILURE_REASONS = new Set([
|
|
|
48
48
|
]);
|
|
49
49
|
/** True when the engine will keep retrying after this `<failure>`. */
|
|
50
50
|
export const isReconnectableConnectFailure = (reason) => reason !== undefined && RECONNECTABLE_CONNECT_FAILURE_REASONS.has(reason);
|
|
51
|
+
/** Map a typed bridge connect-failure code to Baileys' close status. */
|
|
52
|
+
export const mapConnectFailureToDisconnect = (reason) => {
|
|
53
|
+
switch (reason) {
|
|
54
|
+
case 401:
|
|
55
|
+
case 403:
|
|
56
|
+
case 406:
|
|
57
|
+
return DisconnectReason.loggedOut;
|
|
58
|
+
case 402:
|
|
59
|
+
return DisconnectReason.forbidden;
|
|
60
|
+
case 405:
|
|
61
|
+
return 405;
|
|
62
|
+
case 411:
|
|
63
|
+
return DisconnectReason.multideviceMismatch;
|
|
64
|
+
case 503:
|
|
65
|
+
case 501:
|
|
66
|
+
return DisconnectReason.unavailableService;
|
|
67
|
+
case 408:
|
|
68
|
+
return DisconnectReason.timedOut;
|
|
69
|
+
case 515:
|
|
70
|
+
return DisconnectReason.restartRequired;
|
|
71
|
+
default:
|
|
72
|
+
return DisconnectReason.connectionClosed;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
51
75
|
//# sourceMappingURL=terminal-close.js.map
|
|
@@ -33,7 +33,7 @@ export declare const UNSUPPORTED_CONFIG_KEYS: readonly ["keepAliveIntervalMs", "
|
|
|
33
33
|
* member of `SocketConfig` belongs to neither list — which is exactly how the
|
|
34
34
|
* first version of the catalog shipped twelve keys short.
|
|
35
35
|
*/
|
|
36
|
-
export declare const READ_CONFIG_KEYS: readonly ["waWebSocketUrl", "options", "logger", "version", "browser", "pushName", "auth", "cache", "deviceProps", "wantedPreKeyCount", "emitOwnEvents", "shouldIgnoreJid", "defaultQueryTimeoutMs", "transactionOpts", "makeSignalRepository"];
|
|
36
|
+
export declare const READ_CONFIG_KEYS: readonly ["waWebSocketUrl", "options", "logger", "version", "browser", "pushName", "auth", "cache", "deviceProps", "wantedPreKeyCount", "dangerSkipCertChainVerify", "emitOwnEvents", "shouldIgnoreJid", "defaultQueryTimeoutMs", "transactionOpts", "makeSignalRepository"];
|
|
37
37
|
/**
|
|
38
38
|
* Which unsupported options this caller actually passed.
|
|
39
39
|
*
|
package/lib/Types/Socket.d.ts
CHANGED
|
@@ -93,6 +93,14 @@ export type SocketConfig = {
|
|
|
93
93
|
* generated and encoded in one shot). Must be set before connecting.
|
|
94
94
|
*/
|
|
95
95
|
wantedPreKeyCount?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Testing-only bypass for the Noise server-cert chain check, for mock
|
|
98
|
+
* servers that cannot sign a chain rooted in WhatsApp's issuer. Strict by
|
|
99
|
+
* default: absent, null and false all verify, and only a literal `true`
|
|
100
|
+
* opts in — the bridge rejects any other truthy value at construction
|
|
101
|
+
* rather than treating it as opt-in. Never set this outside tests.
|
|
102
|
+
*/
|
|
103
|
+
dangerSkipCertChainVerify?: boolean;
|
|
96
104
|
/** @deprecated QR timeout is handled by the bridge connection state machine. */
|
|
97
105
|
qrTimeout?: number;
|
|
98
106
|
/** Maximum retry count. */
|
|
@@ -1,13 +1,84 @@
|
|
|
1
1
|
import type { AuthenticationState } from '../Types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pure classifier for directory-barrier failures: does this error mean
|
|
4
|
+
* "this platform cannot sync directory handles" (degrade to process-crash
|
|
5
|
+
* atomicity) or "the durability barrier did not hold" (propagate)?
|
|
6
|
+
*
|
|
7
|
+
* - `ENOSYS` / `ENOTSUP` on any platform: the operation is not
|
|
8
|
+
* implemented — genuinely unsupported, safe to degrade.
|
|
9
|
+
* - `EINVAL` / `EPERM` / `EISDIR` on `win32` only: Windows directory
|
|
10
|
+
* handles reject open-for-read and FlushFileBuffers with these codes,
|
|
11
|
+
* so they are the documented platform fallback there. On Linux/macOS
|
|
12
|
+
* the same codes from a freshly opened directory handle mean something
|
|
13
|
+
* is genuinely wrong and they propagate.
|
|
14
|
+
* - Everything else propagates everywhere: `EIO`, `ENOSPC`, `EROFS`,
|
|
15
|
+
* `EACCES`, `ENOENT`, and notably `EBADF` (a bad handle is a real bug,
|
|
16
|
+
* never evidence of an unsupported platform).
|
|
17
|
+
*
|
|
18
|
+
* `platform` defaults to the running platform; tests pass explicit values
|
|
19
|
+
* to cover the matrix deterministically on any OS.
|
|
20
|
+
*/
|
|
21
|
+
export declare const isUnsupportedDirSync: (e: unknown, platform?: NodeJS.Platform) => boolean;
|
|
22
|
+
type BridgeStoreFileHandle = {
|
|
23
|
+
writeFile(value: Uint8Array): Promise<void>;
|
|
24
|
+
sync(): Promise<void>;
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
};
|
|
27
|
+
type BridgeStoreFileIO = {
|
|
28
|
+
writeTmp?(tmpPath: string, value: Uint8Array): Promise<void>;
|
|
29
|
+
publishTmp?(tmpPath: string, finalPath: string): Promise<void>;
|
|
30
|
+
syncDir?(dir: string): Promise<void>;
|
|
31
|
+
openTmp?(tmpPath: string): Promise<BridgeStoreFileHandle>;
|
|
32
|
+
};
|
|
33
|
+
type BridgeStoreOptions = {
|
|
34
|
+
io?: BridgeStoreFileIO;
|
|
35
|
+
};
|
|
2
36
|
/**
|
|
3
37
|
* Creates a file-based store for the WASM bridge.
|
|
4
38
|
*
|
|
5
39
|
* Each (store, key) pair maps to a file: `<folder>/<store>-<key>.bin`
|
|
6
40
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
41
|
+
* Durability model:
|
|
42
|
+
* - Caller buffers are copied synchronously at admission (`set`/`setMany`
|
|
43
|
+
* copy before queueing), so mutating a buffer after the call — even
|
|
44
|
+
* before awaiting it — can never change what gets persisted.
|
|
45
|
+
* - Critical stores write through `durableWrite` before `set`/`setMany`
|
|
46
|
+
* resolve. The in-memory map only records bytes AFTER the full barrier
|
|
47
|
+
* (write + fsync + atomic rename + directory sync) succeeds, so an
|
|
48
|
+
* identical retry following a failure is never skipped and always
|
|
49
|
+
* re-attempts the write.
|
|
50
|
+
* - If the post-rename barrier fails, the key is marked uncertain: prior
|
|
51
|
+
* durable knowledge is discarded, reads serve best-available bytes
|
|
52
|
+
* without re-certifying them, and no identical set is skipped until a
|
|
53
|
+
* later operation completes the full barrier for that key.
|
|
54
|
+
* - Non-critical stores are debounced (50ms coalescing) and readable
|
|
55
|
+
* immediately (read-your-write), but such reads are NOT durable until
|
|
56
|
+
* `flush()` succeeds. A failed flush keeps the pending entry and throws,
|
|
57
|
+
* so the next `flush()` retries the same bytes.
|
|
58
|
+
* - `flush()` first waits for every operation admitted before it
|
|
59
|
+
* (barrier), then drains the pending writes those operations produced.
|
|
60
|
+
* Failures observed by the barrier propagate — a failed admitted write
|
|
61
|
+
* fails the flush — but only operations outstanding during that flush
|
|
62
|
+
* are reported, so history never poisons later flushes. A drain pass
|
|
63
|
+
* with any failure stops at that pass and leaves the failed entries
|
|
64
|
+
* for the next explicit flush. `flush()` never reports quiescence
|
|
65
|
+
* while prior admitted work is still running.
|
|
66
|
+
* - All operations on one key (set, delete, flush, concurrent batches) run
|
|
67
|
+
* through a per-key chain, so a stale failure can never erase newer
|
|
68
|
+
* state and an in-flight write can never resurrect a deleted key.
|
|
69
|
+
* - A failed delete restores the preceding pending/durable state, so an
|
|
70
|
+
* acknowledged value stays readable and flushable; only a successful
|
|
71
|
+
* delete (unlink + directory barrier) clears it. A delete retried while
|
|
72
|
+
* the key is uncertain re-runs the directory barrier instead of
|
|
73
|
+
* swallowing the uncertainty as idempotent absence — except that a
|
|
74
|
+
* directory removed externally surfaces ENOENT rather than success.
|
|
75
|
+
* - Every byte array handed back to callers is a copy.
|
|
9
76
|
*
|
|
10
77
|
* @param folder Directory to store bridge state files
|
|
78
|
+
* @param options Optional per-store file-I/O steps. Test/fault-injection
|
|
79
|
+
* seam only: the default implementation is the sole provider of the
|
|
80
|
+
* durability contract above.
|
|
11
81
|
*/
|
|
12
|
-
export declare function useBridgeStore(folder: string): Promise<NonNullable<AuthenticationState['store']>>;
|
|
82
|
+
export declare function useBridgeStore(folder: string, options?: BridgeStoreOptions): Promise<NonNullable<AuthenticationState['store']>>;
|
|
83
|
+
export {};
|
|
13
84
|
//# sourceMappingURL=use-bridge-store.d.ts.map
|