@oxidezap/baileyrs 0.0.35 → 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/Compatibility/proto-runtime.js +30 -20
- package/lib/Compatibility/websocket-client.d.ts +23 -2
- 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/events.d.ts +31 -0
- package/lib/Socket/events.js +139 -36
- package/lib/Socket/index.d.ts +28 -2
- package/lib/Socket/index.js +432 -131
- package/lib/Socket/messages.d.ts +4 -1
- package/lib/Socket/messages.js +5 -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/Utils/event-buffer.js +31 -0
- package/lib/Utils/messages.d.ts +11 -0
- package/lib/Utils/messages.js +35 -14
- package/package.json +2 -2
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
|
@@ -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) {
|
|
@@ -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
|
|
@@ -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/messages.d.ts
CHANGED
|
@@ -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)
|
package/lib/Utils/messages.js
CHANGED
|
@@ -505,10 +505,14 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
505
505
|
};
|
|
506
506
|
export const generateWAMessageFromContent = (jid, message, options) => {
|
|
507
507
|
const innerMessage = normalizeMessageContent(message);
|
|
508
|
-
const key = getContentType(innerMessage);
|
|
509
508
|
const timestamp = unixTimestampSeconds(options.timestamp);
|
|
510
509
|
const { quoted, userJid } = options;
|
|
510
|
+
// Only the quote and ephemeral branches read the content key, and a plain
|
|
511
|
+
// send takes neither, so resolve it on demand instead of on every send.
|
|
512
|
+
let key;
|
|
511
513
|
if (quoted && !isJidNewsletter(jid)) {
|
|
514
|
+
const contentKey = getContentType(innerMessage);
|
|
515
|
+
key = contentKey;
|
|
512
516
|
const participant = quoted.key.fromMe
|
|
513
517
|
? userJid // TODO: Add support for LIDs
|
|
514
518
|
: quoted.participant || quoted.key.participant || quoted.key.remoteJid;
|
|
@@ -520,7 +524,7 @@ export const generateWAMessageFromContent = (jid, message, options) => {
|
|
|
520
524
|
if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) {
|
|
521
525
|
delete quotedContent.contextInfo;
|
|
522
526
|
}
|
|
523
|
-
const contextInfo = ('contextInfo' in innerMessage[
|
|
527
|
+
const contextInfo = ('contextInfo' in innerMessage[contentKey] && innerMessage[contentKey]?.contextInfo) || {};
|
|
524
528
|
contextInfo.participant = jidNormalizedUser(participant);
|
|
525
529
|
contextInfo.stanzaId = quoted.key.id;
|
|
526
530
|
contextInfo.quotedMessage = quotedMsg;
|
|
@@ -529,26 +533,27 @@ export const generateWAMessageFromContent = (jid, message, options) => {
|
|
|
529
533
|
if (jid !== quoted.key.remoteJid) {
|
|
530
534
|
contextInfo.remoteJid = quoted.key.remoteJid;
|
|
531
535
|
}
|
|
532
|
-
if (contextInfo && innerMessage[
|
|
536
|
+
if (contextInfo && innerMessage[contentKey]) {
|
|
533
537
|
/* @ts-ignore */
|
|
534
|
-
innerMessage[
|
|
538
|
+
innerMessage[contentKey].contextInfo = contextInfo;
|
|
535
539
|
}
|
|
536
540
|
}
|
|
537
541
|
if (
|
|
538
542
|
// if we want to send a disappearing message
|
|
539
543
|
!!options?.ephemeralExpiration &&
|
|
540
|
-
// and it's not a protocol message -- delete, toggle disappear message
|
|
541
|
-
key !== 'protocolMessage' &&
|
|
542
|
-
// already not converted to disappearing message
|
|
543
|
-
key !== 'ephemeralMessage' &&
|
|
544
544
|
// newsletters don't support ephemeral messages
|
|
545
545
|
!isJidNewsletter(jid)) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
546
|
+
const contentKey = key ?? getContentType(innerMessage);
|
|
547
|
+
// skip protocol messages -- delete, toggle disappear message -- and
|
|
548
|
+
// content already converted to a disappearing message
|
|
549
|
+
if (contentKey !== 'protocolMessage' && contentKey !== 'ephemeralMessage') {
|
|
550
|
+
/* @ts-ignore */
|
|
551
|
+
innerMessage[contentKey].contextInfo = {
|
|
552
|
+
...innerMessage[contentKey].contextInfo,
|
|
553
|
+
expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL
|
|
554
|
+
//ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
|
|
555
|
+
};
|
|
556
|
+
}
|
|
552
557
|
}
|
|
553
558
|
// `create` copies into a fresh instance; content built by
|
|
554
559
|
// `generateWAMessageContent` already is one, and the branches above mutate
|
|
@@ -720,6 +725,22 @@ export const _registerActiveBridgeClient = (client, logger) => {
|
|
|
720
725
|
activeBridgeClient = client;
|
|
721
726
|
activeBridgeLogger = logger;
|
|
722
727
|
};
|
|
728
|
+
/**
|
|
729
|
+
* Drop the module-level pointer when `sock.end()` frees the client it points
|
|
730
|
+
* at. Without this the global keeps a strong reference to a freed
|
|
731
|
+
* `WasmWhatsAppClient`, so the next standalone `downloadContentFromMessage()`
|
|
732
|
+
* call reaches into a null wasm pointer and fails with an opaque
|
|
733
|
+
* wasm-bindgen error instead of the actionable "no bridge client" Boom.
|
|
734
|
+
*
|
|
735
|
+
* Identity-checked on purpose: a multi-account host that ends socket A after
|
|
736
|
+
* creating socket B must not clear B's registration.
|
|
737
|
+
*/
|
|
738
|
+
export const _unregisterActiveBridgeClient = (client) => {
|
|
739
|
+
if (activeBridgeClient !== client)
|
|
740
|
+
return;
|
|
741
|
+
activeBridgeClient = undefined;
|
|
742
|
+
activeBridgeLogger = undefined;
|
|
743
|
+
};
|
|
723
744
|
const noopLogger = {
|
|
724
745
|
level: 'silent',
|
|
725
746
|
child: () => noopLogger,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxidezap/baileyrs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"description": "A Rust-powered WhatsApp Web library for JavaScript, with a Baileys-compatible API",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"whatsapp",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
77
|
"@hapi/boom": "^9.1.4",
|
|
78
|
-
"@oxidezap/whatsapp-rust-bridge": "0.6.
|
|
78
|
+
"@oxidezap/whatsapp-rust-bridge": "0.6.5",
|
|
79
79
|
"long": "^5.3.2",
|
|
80
80
|
"pino": "^10.3.1",
|
|
81
81
|
"protobufjs": "^7.6.5"
|