@oxidezap/baileyrs 0.0.32 → 0.1.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.
- package/README.md +77 -24
- package/lib/Bridge/adapt.d.ts +1 -1
- package/lib/Bridge/history-sync-wire.d.ts +1 -1
- package/lib/Bridge/history-sync-wire.js +2 -2
- package/lib/Bridge/schema.d.ts +1 -1
- package/lib/Bridge/schema.js +10 -10
- package/lib/Bridge/types.d.ts +1 -1
- package/lib/Compatibility/group-metadata.d.ts +1 -1
- package/lib/Compatibility/legacy-store/codecs/basic.js +1 -1
- package/lib/Compatibility/legacy-store/codecs/signal.js +17 -5
- package/lib/Compatibility/legacy-store/device.js +1 -1
- package/lib/Compatibility/legacy-store/multi-file.js +1 -1
- package/lib/Compatibility/legacy-store/native-projection.d.ts +1 -1
- package/lib/Compatibility/legacy-store/types.d.ts +1 -1
- package/lib/Compatibility/media-type.d.ts +1 -1
- package/lib/Compatibility/message-relay.d.ts +1 -1
- package/lib/Compatibility/proto-runtime.js +119 -45
- package/lib/Compatibility/socket-results.d.ts +1 -1
- package/lib/Compatibility/stanza-responses.d.ts +1 -1
- package/lib/Compatibility/usync/adapter.d.ts +1 -1
- package/lib/Compatibility/websocket-client.d.ts +24 -3
- package/lib/Compatibility/websocket-client.js +47 -18
- package/lib/Socket/bridge-client-owner.d.ts +89 -0
- package/lib/Socket/bridge-client-owner.js +135 -0
- package/lib/Socket/communities.d.ts +2 -2
- package/lib/Socket/contacts.d.ts +1 -1
- package/lib/Socket/events.d.ts +32 -1
- package/lib/Socket/events.js +140 -37
- package/lib/Socket/index.d.ts +32 -6
- package/lib/Socket/index.js +433 -132
- package/lib/Socket/messages.d.ts +4 -1
- package/lib/Socket/messages.js +6 -4
- package/lib/Socket/newsletter.d.ts +3 -3
- package/lib/Socket/terminal-close-reporter.d.ts +79 -0
- package/lib/Socket/terminal-close-reporter.js +108 -0
- package/lib/Socket/terminal-close.d.ts +39 -0
- package/lib/Socket/terminal-close.js +51 -0
- package/lib/Socket/transport.d.ts +1 -1
- package/lib/Socket/types.d.ts +1 -1
- package/lib/Types/Auth.d.ts +1 -1
- package/lib/Types/Message.d.ts +1 -1
- package/lib/Types/Socket.d.ts +2 -2
- package/lib/Utils/crypto.d.ts +2 -2
- package/lib/Utils/crypto.js +1 -1
- package/lib/Utils/event-buffer.js +31 -0
- package/lib/Utils/logger.d.ts +2 -2
- package/lib/Utils/logger.js +122 -25
- package/lib/Utils/messages.d.ts +12 -1
- package/lib/Utils/messages.js +39 -15
- package/lib/Utils/process-history-message.js +6 -10
- package/lib/Utils/process-message.js +1 -1
- package/lib/WAProto/runtime.js +5 -2
- package/package.json +3 -3
package/lib/Socket/messages.d.ts
CHANGED
|
@@ -10,8 +10,11 @@ import type { SocketContext } from './types.js';
|
|
|
10
10
|
* it across the delete and restore it on a fresh contextInfo.
|
|
11
11
|
*
|
|
12
12
|
* Mutates `msg` in place.
|
|
13
|
+
*
|
|
14
|
+
* `contentType` is a parameter so the send path can hand over the type it has
|
|
15
|
+
* already resolved rather than have it scanned out of `msg` a second time.
|
|
13
16
|
*/
|
|
14
|
-
export declare function stripContextInfoForBridge(msg: WAMessageContent): void;
|
|
17
|
+
export declare function stripContextInfoForBridge(msg: WAMessageContent, contentType?: keyof WAProto.IMessage | undefined): void;
|
|
15
18
|
export declare const makeMessageMethods: (ctx: SocketContext) => {
|
|
16
19
|
sendMessage: (jid: string, content: AnyMessageContent, options?: Omit<MessageGenerationOptions, 'waClient' | 'logger' | 'userJid' | 'mediaInNote'>) => Promise<WAMessage>;
|
|
17
20
|
updateMediaMessage: (message: WAMessage) => Promise<WAMessage>;
|
package/lib/Socket/messages.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { encodeProto } from 'whatsapp-rust-bridge';
|
|
1
|
+
import { encodeProto } from '@oxidezap/whatsapp-rust-bridge';
|
|
2
2
|
import { planMessageRelay } from '../Compatibility/message-relay.js';
|
|
3
3
|
import { receiptMessageKeys } from '../Compatibility/message-keys.js';
|
|
4
4
|
import { WAProto } from '../Types/index.js';
|
|
@@ -22,9 +22,11 @@ function getMediaContent(content) {
|
|
|
22
22
|
* it across the delete and restore it on a fresh contextInfo.
|
|
23
23
|
*
|
|
24
24
|
* Mutates `msg` in place.
|
|
25
|
+
*
|
|
26
|
+
* `contentType` is a parameter so the send path can hand over the type it has
|
|
27
|
+
* already resolved rather than have it scanned out of `msg` a second time.
|
|
25
28
|
*/
|
|
26
|
-
export function stripContextInfoForBridge(msg) {
|
|
27
|
-
const contentType = getContentType(msg);
|
|
29
|
+
export function stripContextInfoForBridge(msg, contentType = getContentType(msg)) {
|
|
28
30
|
const pinAddOnDuration = contentType === 'pinInChatMessage' ? msg.messageContextInfo?.messageAddOnDurationInSecs : undefined;
|
|
29
31
|
delete msg.messageContextInfo;
|
|
30
32
|
if (pinAddOnDuration !== undefined && pinAddOnDuration !== null) {
|
|
@@ -85,7 +87,7 @@ export const makeMessageMethods = (ctx) => ({
|
|
|
85
87
|
return fullMsg;
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
|
-
stripContextInfoForBridge(msg);
|
|
90
|
+
stripContextInfoForBridge(msg, contentType);
|
|
89
91
|
let msgId;
|
|
90
92
|
const msgBytes = encodeProto('Message', msg);
|
|
91
93
|
if (jid === 'status@broadcast' && options?.statusJidList?.length) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { SocketContext } from './types.js';
|
|
2
2
|
export declare const makeNewsletterMethods: (ctx: SocketContext) => {
|
|
3
|
-
newsletterCreate: (name: string, description?: string) => Promise<import("whatsapp-rust-bridge").NewsletterMetadataResult>;
|
|
4
|
-
newsletterMetadata: (jid: string) => Promise<import("whatsapp-rust-bridge").NewsletterMetadataResult>;
|
|
5
|
-
newsletterSubscribe: (jid: string) => Promise<import("whatsapp-rust-bridge").NewsletterMetadataResult>;
|
|
3
|
+
newsletterCreate: (name: string, description?: string) => Promise<import("@oxidezap/whatsapp-rust-bridge").NewsletterMetadataResult>;
|
|
4
|
+
newsletterMetadata: (jid: string) => Promise<import("@oxidezap/whatsapp-rust-bridge").NewsletterMetadataResult>;
|
|
5
|
+
newsletterSubscribe: (jid: string) => Promise<import("@oxidezap/whatsapp-rust-bridge").NewsletterMetadataResult>;
|
|
6
6
|
newsletterUnsubscribe: (jid: string) => Promise<void>;
|
|
7
7
|
newsletterReactMessage: (jid: string, serverId: string, reaction?: string) => Promise<void>;
|
|
8
8
|
/**
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports the terminal `connection.update { close }` — exactly once, after
|
|
3
|
+
* teardown, and never silently not at all.
|
|
4
|
+
*
|
|
5
|
+
* That sentence is the whole contract this branch sells, and getting it wrong
|
|
6
|
+
* has two opposite failure modes, both bad:
|
|
7
|
+
*
|
|
8
|
+
* - **Not reported.** The consumer's handler never runs, so it never builds a
|
|
9
|
+
* replacement socket. A bot offline with nothing in its logs — the original
|
|
10
|
+
* bug this branch exists to fix.
|
|
11
|
+
* - **Reported early, or twice.** The replacement socket overlaps the old
|
|
12
|
+
* one's auth-store flush and `free()`, or every listener sees two terminal
|
|
13
|
+
* notifications for one socket and a handler that cleans up on close loops.
|
|
14
|
+
*
|
|
15
|
+
* Keeping both away used to be inline logic split across the dispatcher hook
|
|
16
|
+
* and `logout()`, sharing a counter and a promise. Nine separate bugs came out
|
|
17
|
+
* of that in review — a publish that could never run, a throwing listener
|
|
18
|
+
* taking the process down, `logout()` resolving before the close it caused,
|
|
19
|
+
* the watchdog not releasing its waiters, a suppression check that also
|
|
20
|
+
* matched non-terminal teardown, a fallback that did not count itself. Every
|
|
21
|
+
* one was a different way of answering the same question, so it is one object
|
|
22
|
+
* now, with the answer in one place.
|
|
23
|
+
*/
|
|
24
|
+
import type { ILogger } from '../Utils/logger.js';
|
|
25
|
+
/**
|
|
26
|
+
* How long teardown may run before the close is reported anyway.
|
|
27
|
+
*
|
|
28
|
+
* A deliberate trade of one guarantee for the other: past this point the close
|
|
29
|
+
* goes out with teardown still running, because losing the event entirely is
|
|
30
|
+
* the worse of the two failures. Teardown takes milliseconds in practice, so
|
|
31
|
+
* reaching this means a consumer end handler or a store flush never settled —
|
|
32
|
+
* and the error logged alongside says exactly that. Generous rather than
|
|
33
|
+
* tight, since firing early is the harmful direction.
|
|
34
|
+
*/
|
|
35
|
+
export declare const TERMINAL_CLOSE_PUBLISH_TIMEOUT_MS = 60000;
|
|
36
|
+
export interface TerminalCloseReporter {
|
|
37
|
+
/**
|
|
38
|
+
* Run `teardown`, then publish. Used by the dispatcher, which learns about
|
|
39
|
+
* the close first and hands over the publishing so the socket can finish
|
|
40
|
+
* releasing its resources before a consumer can react.
|
|
41
|
+
*
|
|
42
|
+
* Never rejects and never leaves `publish` uncalled: teardown failures are
|
|
43
|
+
* logged, a throwing listener is contained, and a teardown that hangs is
|
|
44
|
+
* cut short by the watchdog.
|
|
45
|
+
*/
|
|
46
|
+
reportAfter: (teardown: () => Promise<void>, publish: () => void) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Publish immediately, for a close nothing else will announce — a `logout()`
|
|
49
|
+
* with no live client, or one whose `logout()` threw before the bridge
|
|
50
|
+
* dispatched anything.
|
|
51
|
+
*/
|
|
52
|
+
reportNow: (publish: () => void) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Whether a terminal close has been *claimed* — reported, or reported-and-
|
|
55
|
+
* still-tearing-down. Not "published": `reportAfter` publishes only once
|
|
56
|
+
* teardown finishes, so a check keyed on delivery would see `false` right
|
|
57
|
+
* after the bridge announced the logout and let `logout()` add a second
|
|
58
|
+
* close of its own.
|
|
59
|
+
*
|
|
60
|
+
* `logout()` uses this rather than "is the socket closing", which is also
|
|
61
|
+
* true for a plain `end()` — and those report nothing, so keying off them
|
|
62
|
+
* would let a logout racing one finish with no close at all.
|
|
63
|
+
*/
|
|
64
|
+
hasReported: () => boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Settles once the most recent report has been published — the moment the
|
|
67
|
+
* consumer sees it, not the moment teardown finishes, so a waiter is not
|
|
68
|
+
* left hanging when the watchdog is what released the event.
|
|
69
|
+
*
|
|
70
|
+
* Resolves immediately when nothing has been reported.
|
|
71
|
+
*/
|
|
72
|
+
published: () => Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
export declare const makeTerminalCloseReporter: (opts: {
|
|
75
|
+
logger: ILogger;
|
|
76
|
+
/** Overridable for tests; production uses the constant above. */
|
|
77
|
+
publishTimeoutMs?: number;
|
|
78
|
+
}) => TerminalCloseReporter;
|
|
79
|
+
//# sourceMappingURL=terminal-close-reporter.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports the terminal `connection.update { close }` — exactly once, after
|
|
3
|
+
* teardown, and never silently not at all.
|
|
4
|
+
*
|
|
5
|
+
* That sentence is the whole contract this branch sells, and getting it wrong
|
|
6
|
+
* has two opposite failure modes, both bad:
|
|
7
|
+
*
|
|
8
|
+
* - **Not reported.** The consumer's handler never runs, so it never builds a
|
|
9
|
+
* replacement socket. A bot offline with nothing in its logs — the original
|
|
10
|
+
* bug this branch exists to fix.
|
|
11
|
+
* - **Reported early, or twice.** The replacement socket overlaps the old
|
|
12
|
+
* one's auth-store flush and `free()`, or every listener sees two terminal
|
|
13
|
+
* notifications for one socket and a handler that cleans up on close loops.
|
|
14
|
+
*
|
|
15
|
+
* Keeping both away used to be inline logic split across the dispatcher hook
|
|
16
|
+
* and `logout()`, sharing a counter and a promise. Nine separate bugs came out
|
|
17
|
+
* of that in review — a publish that could never run, a throwing listener
|
|
18
|
+
* taking the process down, `logout()` resolving before the close it caused,
|
|
19
|
+
* the watchdog not releasing its waiters, a suppression check that also
|
|
20
|
+
* matched non-terminal teardown, a fallback that did not count itself. Every
|
|
21
|
+
* one was a different way of answering the same question, so it is one object
|
|
22
|
+
* now, with the answer in one place.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* How long teardown may run before the close is reported anyway.
|
|
26
|
+
*
|
|
27
|
+
* A deliberate trade of one guarantee for the other: past this point the close
|
|
28
|
+
* goes out with teardown still running, because losing the event entirely is
|
|
29
|
+
* the worse of the two failures. Teardown takes milliseconds in practice, so
|
|
30
|
+
* reaching this means a consumer end handler or a store flush never settled —
|
|
31
|
+
* and the error logged alongside says exactly that. Generous rather than
|
|
32
|
+
* tight, since firing early is the harmful direction.
|
|
33
|
+
*/
|
|
34
|
+
export const TERMINAL_CLOSE_PUBLISH_TIMEOUT_MS = 60000;
|
|
35
|
+
export const makeTerminalCloseReporter = (opts) => {
|
|
36
|
+
const { logger } = opts;
|
|
37
|
+
const publishTimeoutMs = opts.publishTimeoutMs ?? TERMINAL_CLOSE_PUBLISH_TIMEOUT_MS;
|
|
38
|
+
/** Claims, not deliveries — see `hasReported`. */
|
|
39
|
+
let claimed = 0;
|
|
40
|
+
let publishedPromise;
|
|
41
|
+
/** One publish per claim, whatever gets there first, and never throwing. */
|
|
42
|
+
const makeOnce = (publish, settle) => {
|
|
43
|
+
let done = false;
|
|
44
|
+
return () => {
|
|
45
|
+
if (done)
|
|
46
|
+
return;
|
|
47
|
+
done = true;
|
|
48
|
+
try {
|
|
49
|
+
publish();
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
// `publish` emits on the consumer's bus. Letting this escape
|
|
53
|
+
// would surface as an unhandled rejection on a detached chain —
|
|
54
|
+
// process exit under Node's default handler.
|
|
55
|
+
logger.error({ err }, 'connection.update listener threw on the terminal close');
|
|
56
|
+
}
|
|
57
|
+
settle();
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
reportAfter: (teardown, publish) => {
|
|
62
|
+
claimed++;
|
|
63
|
+
let settle;
|
|
64
|
+
publishedPromise = new Promise(resolve => {
|
|
65
|
+
settle = resolve;
|
|
66
|
+
});
|
|
67
|
+
const publishOnce = makeOnce(publish, settle);
|
|
68
|
+
// Deliberately referenced: a pending promise does not keep Node's
|
|
69
|
+
// event loop alive, so an unref'd timer lets a minimal bot exit
|
|
70
|
+
// before it can publish — and the close handler that would have
|
|
71
|
+
// built the replacement never runs, which is what this guards.
|
|
72
|
+
// Cleared the moment teardown settles, so it holds the loop only
|
|
73
|
+
// while one is in flight.
|
|
74
|
+
const watchdog = setTimeout(() => {
|
|
75
|
+
logger.error({ afterMs: publishTimeoutMs }, 'socket teardown is still running; reporting the close anyway');
|
|
76
|
+
publishOnce();
|
|
77
|
+
}, publishTimeoutMs);
|
|
78
|
+
const finish = (err) => {
|
|
79
|
+
clearTimeout(watchdog);
|
|
80
|
+
if (err)
|
|
81
|
+
logger.error({ err }, 'socket teardown failed after a terminal disconnect');
|
|
82
|
+
publishOnce();
|
|
83
|
+
};
|
|
84
|
+
// `teardown()` is called inside the try because it can throw
|
|
85
|
+
// *synchronously*, before returning a promise — `.then` would never
|
|
86
|
+
// run and the close would stay unpublished until the watchdog, or
|
|
87
|
+
// forever if the timer were ever removed. The no-silent-failure
|
|
88
|
+
// promise has to hold for that path too.
|
|
89
|
+
try {
|
|
90
|
+
void teardown().then(() => finish(), finish);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
finish(err);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
reportNow: publish => {
|
|
97
|
+
claimed++;
|
|
98
|
+
let settle;
|
|
99
|
+
publishedPromise = new Promise(resolve => {
|
|
100
|
+
settle = resolve;
|
|
101
|
+
});
|
|
102
|
+
makeOnce(publish, settle)();
|
|
103
|
+
},
|
|
104
|
+
hasReported: () => claimed > 0,
|
|
105
|
+
published: () => publishedPromise ?? Promise.resolve()
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=terminal-close-reporter.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
/** True when the engine will keep retrying after this `<failure>`. */
|
|
38
|
+
export declare const isReconnectableConnectFailure: (reason: number | undefined) => boolean;
|
|
39
|
+
//# sourceMappingURL=terminal-close.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
/**
|
|
38
|
+
* `ConnectFailureReason` wire codes the engine retries — the JS mirror of
|
|
39
|
+
* `ConnectFailureReason::should_reconnect()`, which matches only
|
|
40
|
+
* `InternalServerError | ServiceUnavailable`.
|
|
41
|
+
*
|
|
42
|
+
* A `<failure>` with no reason at all is NOT retried: the engine parses it as
|
|
43
|
+
* `Unknown(0)`, which fails `should_reconnect()`.
|
|
44
|
+
*/
|
|
45
|
+
const RECONNECTABLE_CONNECT_FAILURE_REASONS = new Set([
|
|
46
|
+
500, // InternalServerError
|
|
47
|
+
503 // ServiceUnavailable
|
|
48
|
+
]);
|
|
49
|
+
/** True when the engine will keep retrying after this `<failure>`. */
|
|
50
|
+
export const isReconnectableConnectFailure = (reason) => reason !== undefined && RECONNECTABLE_CONNECT_FAILURE_REASONS.has(reason);
|
|
51
|
+
//# sourceMappingURL=terminal-close.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { JsHttpClientConfig, JsTransportCallbacks } from 'whatsapp-rust-bridge';
|
|
1
|
+
import type { JsHttpClientConfig, JsTransportCallbacks } from '@oxidezap/whatsapp-rust-bridge';
|
|
2
2
|
import type { ILogger } from '../Utils/logger.js';
|
|
3
3
|
interface TransportConfig {
|
|
4
4
|
waWebSocketUrl: string | URL;
|
package/lib/Socket/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EventEmitter } from 'events';
|
|
2
|
-
import type { WasmWhatsAppClient } from 'whatsapp-rust-bridge';
|
|
2
|
+
import type { WasmWhatsAppClient } from '@oxidezap/whatsapp-rust-bridge';
|
|
3
3
|
import type { Contact, SocketConfig } from '../Types/index.js';
|
|
4
4
|
import type { makeEventBuffer } from '../Utils/event-buffer.js';
|
|
5
5
|
import type { ILogger } from '../Utils/logger.js';
|
package/lib/Types/Auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Buffer } from 'node:buffer';
|
|
2
|
-
import type { JsStoreCallbacks } from 'whatsapp-rust-bridge';
|
|
2
|
+
import type { JsStoreCallbacks } from '@oxidezap/whatsapp-rust-bridge';
|
|
3
3
|
import type { proto } from '../WAProto/runtime.js';
|
|
4
4
|
import type { Contact } from './Contact.js';
|
|
5
5
|
import type { MinimalMessage } from './Message.js';
|
package/lib/Types/Message.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Readable } from 'stream';
|
|
2
2
|
import type { URL } from 'url';
|
|
3
|
-
import type { MediaType as BridgeMediaType, UploadMediaResult, WasmWhatsAppClient } from 'whatsapp-rust-bridge';
|
|
3
|
+
import type { MediaType as BridgeMediaType, UploadMediaResult, WasmWhatsAppClient } from '@oxidezap/whatsapp-rust-bridge';
|
|
4
4
|
import type { WAMessageAddressingMode as WAMessageAddressingModeType } from '../Compatibility/public-api/enum-types.js';
|
|
5
5
|
import { proto } from '../WAProto/runtime.js';
|
|
6
6
|
import type { MediaType } from '../Defaults/index.js';
|
package/lib/Types/Socket.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Agent } from 'node:https';
|
|
2
2
|
import type { URL } from 'url';
|
|
3
|
-
import type { CacheConfig, DevicePropsInput } from 'whatsapp-rust-bridge';
|
|
3
|
+
import type { CacheConfig, DevicePropsInput } from '@oxidezap/whatsapp-rust-bridge';
|
|
4
4
|
import type { proto } from '../WAProto/runtime.js';
|
|
5
|
-
export type { DeviceAppVersion, DeviceHistorySyncConfig, DevicePlatformType, DevicePropsInput } from 'whatsapp-rust-bridge';
|
|
5
|
+
export type { DeviceAppVersion, DeviceHistorySyncConfig, DevicePlatformType, DevicePropsInput } from '@oxidezap/whatsapp-rust-bridge';
|
|
6
6
|
import type { ILogger } from '../Utils/logger.js';
|
|
7
7
|
import type { AuthenticationState, LIDMapping, SignalAuthState } from './Auth.js';
|
|
8
8
|
import type { GroupMetadata } from './GroupMetadata.js';
|
package/lib/Utils/crypto.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { hkdf, md5 } from 'whatsapp-rust-bridge';
|
|
1
|
+
import { hkdf, md5 } from '@oxidezap/whatsapp-rust-bridge';
|
|
2
2
|
import type { KeyPair } from '../Types/Auth.js';
|
|
3
3
|
export { hkdf, md5 };
|
|
4
|
-
export type { HkdfInfo } from 'whatsapp-rust-bridge';
|
|
4
|
+
export type { HkdfInfo } from '@oxidezap/whatsapp-rust-bridge';
|
|
5
5
|
/** Prefix Signal's version byte when the public key is in raw 32-byte form. */
|
|
6
6
|
export declare const generateSignalPubKey: (pubKey: Uint8Array | Buffer) => Buffer<ArrayBufferLike> | Uint8Array<ArrayBufferLike>;
|
|
7
7
|
export declare const Curve: {
|
package/lib/Utils/crypto.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto';
|
|
2
|
-
import { calculateAgreement, calculateSignature, generateKeyPair, hkdf, md5, verifySignature } from 'whatsapp-rust-bridge';
|
|
2
|
+
import { calculateAgreement, calculateSignature, generateKeyPair, hkdf, md5, verifySignature } from '@oxidezap/whatsapp-rust-bridge';
|
|
3
3
|
import { KEY_BUNDLE_TYPE } from '../Defaults/index.js';
|
|
4
4
|
import { aesCbc256Decrypt, aesCbc256Encrypt, aesGcm256Decrypt, aesGcm256Encrypt, hmacDigest } from '../Platform/crypto.js';
|
|
5
5
|
export { hkdf, md5 };
|
|
@@ -362,6 +362,37 @@ export const makeEventBuffer = (logger) => {
|
|
|
362
362
|
let flushPendingTimeout;
|
|
363
363
|
ev.on('event', (events) => {
|
|
364
364
|
for (const event of Object.keys(events)) {
|
|
365
|
+
if (event === 'connection.update') {
|
|
366
|
+
// Delivered listener by listener, unlike every other event.
|
|
367
|
+
// `EventEmitter.emit()` stops at the first listener that throws,
|
|
368
|
+
// so one bad handler would keep the rest — including the app's
|
|
369
|
+
// reconnect handler — from ever seeing a terminal `close`, and
|
|
370
|
+
// the bot would stay offline. This is the lifecycle channel;
|
|
371
|
+
// losing delivery here is the failure mode the socket's whole
|
|
372
|
+
// close contract exists to prevent.
|
|
373
|
+
// `rawListeners`, not `listeners`: the latter unwraps `once()`
|
|
374
|
+
// handlers to the function underneath, so calling that would
|
|
375
|
+
// never run the wrapper that unregisters them and the listener
|
|
376
|
+
// would stay subscribed for every later update. `once` is not on
|
|
377
|
+
// `BaileysEventEmitter` today, so this is unreachable rather than
|
|
378
|
+
// broken — but the two differ only in this respect and the raw
|
|
379
|
+
// one is the correct primitive for calling listeners by hand.
|
|
380
|
+
for (const listener of ev.rawListeners(event)) {
|
|
381
|
+
try {
|
|
382
|
+
// `.call(ev, …)` so `this` is still the emitter, as it
|
|
383
|
+
// would be under `emit()`. A listener declared as a
|
|
384
|
+
// normal function that does `this.off('connection.update', …)`
|
|
385
|
+
// — a common self-removing pattern — would otherwise
|
|
386
|
+
// throw on `undefined` and be swallowed by the catch.
|
|
387
|
+
;
|
|
388
|
+
listener.call(ev, events[event]);
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
logger.error({ err, event }, 'connection.update listener threw; continuing with the rest');
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
365
396
|
ev.emit(event, events[event]);
|
|
366
397
|
}
|
|
367
398
|
});
|
package/lib/Utils/logger.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { Logger } from 'pino';
|
|
2
2
|
export interface ILogger {
|
|
3
3
|
level: string;
|
|
4
4
|
child(obj: Record<string, unknown>): ILogger;
|
|
@@ -8,6 +8,6 @@ export interface ILogger {
|
|
|
8
8
|
warn(obj: unknown, msg?: string): void;
|
|
9
9
|
error(obj: unknown, msg?: string): void;
|
|
10
10
|
}
|
|
11
|
-
declare const _default:
|
|
11
|
+
declare const _default: Logger;
|
|
12
12
|
export default _default;
|
|
13
13
|
//# sourceMappingURL=logger.d.ts.map
|
package/lib/Utils/logger.js
CHANGED
|
@@ -1,27 +1,124 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
const DEFAULT_LEVEL = 'info';
|
|
3
|
+
const REDACTED_PATHS = [
|
|
4
|
+
'creds',
|
|
5
|
+
'authState',
|
|
6
|
+
'noiseKey',
|
|
7
|
+
'signedIdentityKey',
|
|
8
|
+
'advSecretKey',
|
|
9
|
+
'privateKey',
|
|
10
|
+
'secretKey',
|
|
11
|
+
'preKey.privateKey',
|
|
12
|
+
'*.creds',
|
|
13
|
+
'*.authState',
|
|
14
|
+
'*.noiseKey',
|
|
15
|
+
'*.signedIdentityKey',
|
|
16
|
+
'*.advSecretKey',
|
|
17
|
+
'*.privateKey',
|
|
18
|
+
'*.secretKey'
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Deferred because building pino at module evaluation cost ~10 MB of RSS in every
|
|
22
|
+
* importing process: ~4.5 MB for pino's module graph, the rest for the Date/ICU
|
|
23
|
+
* machinery V8 materializes when the configured `timestamp` runs — which pino
|
|
24
|
+
* calls while *constructing* the logger. Applications that pass their own
|
|
25
|
+
* `SocketConfig.logger` never needed any of it.
|
|
26
|
+
*
|
|
27
|
+
* `require` rather than a dynamic `import`, because ILogger's methods are
|
|
28
|
+
* synchronous: an async resolution would have to drop or queue the first lines.
|
|
29
|
+
*/
|
|
30
|
+
let rootLogger;
|
|
31
|
+
const resolveRootLogger = () => {
|
|
32
|
+
if (!rootLogger) {
|
|
33
|
+
const requireFrom = createRequire(import.meta.url);
|
|
34
|
+
const pino = requireFrom('pino');
|
|
35
|
+
rootLogger = pino({
|
|
36
|
+
name: 'baileyrs',
|
|
37
|
+
level: process.env.BAILEYRS_LOG_LEVEL || DEFAULT_LEVEL,
|
|
38
|
+
timestamp: () => `,"time":"${new Date().toJSON()}"`,
|
|
39
|
+
redact: { paths: REDACTED_PATHS, censor: '[REDACTED]' }
|
|
40
|
+
});
|
|
25
41
|
}
|
|
26
|
-
|
|
42
|
+
return rootLogger;
|
|
43
|
+
};
|
|
44
|
+
const createDeferredLogger = (bindings, parent, childOptions) => {
|
|
45
|
+
let resolved;
|
|
46
|
+
let pendingLevel;
|
|
47
|
+
const members = new Map();
|
|
48
|
+
const resolve = () => {
|
|
49
|
+
if (!resolved) {
|
|
50
|
+
const source = parent ? parent.resolve() : resolveRootLogger();
|
|
51
|
+
resolved = bindings ? source.child(bindings, childOptions) : source;
|
|
52
|
+
if (pendingLevel !== undefined)
|
|
53
|
+
resolved.level = pendingLevel;
|
|
54
|
+
}
|
|
55
|
+
return resolved;
|
|
56
|
+
};
|
|
57
|
+
const peekLevel = () => resolved?.level ??
|
|
58
|
+
pendingLevel ??
|
|
59
|
+
childOptions?.level ??
|
|
60
|
+
parent?.level() ??
|
|
61
|
+
process.env.BAILEYRS_LOG_LEVEL ??
|
|
62
|
+
DEFAULT_LEVEL;
|
|
63
|
+
const self = { level: peekLevel, resolve };
|
|
64
|
+
// Hoisted so `logger.child` keeps the stable identity a plain property has.
|
|
65
|
+
const child = (extra, options) => createDeferredLogger(extra, self, options);
|
|
66
|
+
// Typed as pino's Logger, not ILogger: narrowing would reject `logger.fatal(...)`
|
|
67
|
+
// at compile time even though the proxy forwards it.
|
|
68
|
+
const target = {};
|
|
69
|
+
return new Proxy(target, {
|
|
70
|
+
get(_target, property) {
|
|
71
|
+
if (property === 'child')
|
|
72
|
+
return child;
|
|
73
|
+
if (property === 'level' && !resolved)
|
|
74
|
+
return peekLevel();
|
|
75
|
+
const cached = members.get(property);
|
|
76
|
+
if (cached !== undefined)
|
|
77
|
+
return cached;
|
|
78
|
+
const logger = resolve();
|
|
79
|
+
const value = logger[property];
|
|
80
|
+
if (typeof value !== 'function')
|
|
81
|
+
return value;
|
|
82
|
+
const bound = value.bind(logger);
|
|
83
|
+
members.set(property, bound);
|
|
84
|
+
return bound;
|
|
85
|
+
},
|
|
86
|
+
set(_target, property, value) {
|
|
87
|
+
if (property === 'level' && !resolved) {
|
|
88
|
+
pendingLevel = value;
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
;
|
|
92
|
+
resolve()[property] = value;
|
|
93
|
+
// Whole cache, not just this key: pino swaps its log methods for noops
|
|
94
|
+
// when `level` changes, so a cached `info` would stay silent forever.
|
|
95
|
+
members.clear();
|
|
96
|
+
return true;
|
|
97
|
+
},
|
|
98
|
+
has(_target, property) {
|
|
99
|
+
return property === 'child' || property === 'level' || property in resolve();
|
|
100
|
+
},
|
|
101
|
+
deleteProperty(_target, property) {
|
|
102
|
+
members.delete(property);
|
|
103
|
+
delete resolve()[property];
|
|
104
|
+
return true;
|
|
105
|
+
},
|
|
106
|
+
defineProperty(_target, property, descriptor) {
|
|
107
|
+
// Forwarded, or the definition would land on the empty target while
|
|
108
|
+
// every other trap kept answering from the resolved logger.
|
|
109
|
+
members.clear();
|
|
110
|
+
return Reflect.defineProperty(resolve(), property, descriptor);
|
|
111
|
+
},
|
|
112
|
+
ownKeys() {
|
|
113
|
+
return Reflect.ownKeys(resolve());
|
|
114
|
+
},
|
|
115
|
+
getOwnPropertyDescriptor(_target, property) {
|
|
116
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(resolve(), property);
|
|
117
|
+
// Forced configurable: the proxy target is an empty extensible object,
|
|
118
|
+
// and reporting a non-configurable key it does not own is a TypeError.
|
|
119
|
+
return descriptor && { ...descriptor, configurable: true };
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
export default createDeferredLogger();
|
|
27
124
|
//# sourceMappingURL=logger.js.map
|
package/lib/Utils/messages.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import { Readable } from 'node:stream';
|
|
3
|
-
import type { WasmWhatsAppClient } from 'whatsapp-rust-bridge';
|
|
3
|
+
import type { WasmWhatsAppClient } from '@oxidezap/whatsapp-rust-bridge';
|
|
4
4
|
import { type MediaType } from '../Defaults/index.js';
|
|
5
5
|
import type { AnyMediaMessageContent, AnyMessageContent, MessageContentGenerationOptions, MessageGenerationOptions, MessageGenerationOptionsFromContent, MessageUserReceipt, WAMessage, WAMessageContent, WAMessageKey } from '../Types/index.js';
|
|
6
6
|
import { WAProto } from '../Types/index.js';
|
|
@@ -57,6 +57,17 @@ export type DownloadMediaMessageContext = {
|
|
|
57
57
|
*/
|
|
58
58
|
export declare const downloadMediaMessage: <Type extends 'buffer' | 'stream'>(message: WAMessage, type: Type, options: MediaDownloadOptions, ctx: DownloadMediaMessageContext) => Promise<Type extends "buffer" ? Buffer<ArrayBufferLike> : Readable>;
|
|
59
59
|
export declare const _registerActiveBridgeClient: (client: WasmWhatsAppClient, logger?: ILogger) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Drop the module-level pointer when `sock.end()` frees the client it points
|
|
62
|
+
* at. Without this the global keeps a strong reference to a freed
|
|
63
|
+
* `WasmWhatsAppClient`, so the next standalone `downloadContentFromMessage()`
|
|
64
|
+
* call reaches into a null wasm pointer and fails with an opaque
|
|
65
|
+
* wasm-bindgen error instead of the actionable "no bridge client" Boom.
|
|
66
|
+
*
|
|
67
|
+
* Identity-checked on purpose: a multi-account host that ends socket A after
|
|
68
|
+
* creating socket B must not clear B's registration.
|
|
69
|
+
*/
|
|
70
|
+
export declare const _unregisterActiveBridgeClient: (client: WasmWhatsAppClient) => void;
|
|
60
71
|
/**
|
|
61
72
|
* Upstream-Baileys-compatible standalone media download. Builds a synthetic
|
|
62
73
|
* `WAMessage` from the supplied media subcontent (image/video/audio/etc fields)
|