@oxidezap/baileyrs 0.0.35 → 0.1.1
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 +147 -24
- package/lib/Compatibility/legacy-store/namespaces.d.ts +20 -0
- package/lib/Compatibility/legacy-store/namespaces.js +27 -0
- package/lib/Compatibility/newsletter-results.d.ts +15 -0
- package/lib/Compatibility/newsletter-results.js +38 -0
- 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/business.d.ts +29 -0
- package/lib/Socket/business.js +104 -0
- package/lib/Socket/chat-actions.d.ts +20 -11
- package/lib/Socket/chat-actions.js +171 -83
- package/lib/Socket/events.d.ts +31 -0
- package/lib/Socket/events.js +144 -41
- package/lib/Socket/index.d.ts +113 -16
- package/lib/Socket/index.js +468 -157
- package/lib/Socket/internals.d.ts +88 -0
- package/lib/Socket/internals.js +145 -0
- package/lib/Socket/messages.d.ts +1 -12
- package/lib/Socket/messages.js +3 -20
- package/lib/Socket/newsletter.d.ts +61 -6
- package/lib/Socket/newsletter.js +125 -7
- package/lib/Socket/privacy.d.ts +25 -0
- package/lib/Socket/privacy.js +54 -0
- package/lib/Socket/server-queries.d.ts +38 -0
- package/lib/Socket/server-queries.js +121 -0
- 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/types.d.ts +6 -0
- package/lib/Types/Product.d.ts +9 -0
- package/lib/Utils/event-buffer.js +31 -0
- package/lib/Utils/index.d.ts +1 -0
- package/lib/Utils/index.js +3 -0
- package/lib/Utils/link-preview.d.ts +60 -0
- package/lib/Utils/link-preview.js +357 -0
- package/lib/Utils/messages.d.ts +31 -7
- package/lib/Utils/messages.js +49 -17
- package/lib/Utils/wrap-legacy-store.d.ts +1 -0
- package/lib/Utils/wrap-legacy-store.js +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Boom } from '../Utils/boom.js';
|
|
2
|
+
/**
|
|
3
|
+
* Every section, flattened and deduplicated, rather than only the section the
|
|
4
|
+
* server types `all`.
|
|
5
|
+
*
|
|
6
|
+
* Upstream reads that one section, and the core deliberately refused to: the
|
|
7
|
+
* real client walks every section and uses the type only for layout, so a bot
|
|
8
|
+
* that appears solely under a category is dropped by the narrower reading. A
|
|
9
|
+
* caller iterating this list handles the extra entries; one that silently lost
|
|
10
|
+
* a bot has no way to notice.
|
|
11
|
+
*/
|
|
12
|
+
const flattenBotList = (list) => {
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
const bots = [];
|
|
15
|
+
for (const section of list.sections) {
|
|
16
|
+
for (const bot of section.bots) {
|
|
17
|
+
if (seen.has(bot.jid))
|
|
18
|
+
continue;
|
|
19
|
+
seen.add(bot.jid);
|
|
20
|
+
bots.push({ jid: bot.jid, personaId: bot.personaId });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return bots;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Upstream names these in snake case and types the three timestamps as
|
|
27
|
+
* strings. Every field is optional because the server omits whatever does not
|
|
28
|
+
* apply to the account's tier, and an absent quota stays absent: `0` here means
|
|
29
|
+
* the quota is spent.
|
|
30
|
+
*/
|
|
31
|
+
const toCapInfo = (result) => ({
|
|
32
|
+
...(result.totalQuota !== undefined ? { total_quota: result.totalQuota } : {}),
|
|
33
|
+
...(result.usedQuota !== undefined ? { used_quota: result.usedQuota } : {}),
|
|
34
|
+
...(result.remainingQuota !== undefined ? { remaining_quota: result.remainingQuota } : {}),
|
|
35
|
+
...(result.cycleStartTimestamp !== undefined ? { cycle_start_timestamp: String(result.cycleStartTimestamp) } : {}),
|
|
36
|
+
...(result.cycleEndTimestamp !== undefined ? { cycle_end_timestamp: String(result.cycleEndTimestamp) } : {}),
|
|
37
|
+
...(result.serverSentTimestamp !== undefined ? { server_sent_timestamp: String(result.serverSentTimestamp) } : {}),
|
|
38
|
+
...(result.oteStatus !== undefined ? { ote_status: result.oteStatus } : {}),
|
|
39
|
+
...(result.mvStatus !== undefined ? { mv_status: result.mvStatus } : {}),
|
|
40
|
+
...(result.cappingStatus !== undefined
|
|
41
|
+
? { capping_status: result.cappingStatus }
|
|
42
|
+
: {})
|
|
43
|
+
});
|
|
44
|
+
export const makeServerQueryMethods = (ctx) => {
|
|
45
|
+
/** Last host seen, so the synchronous accessor upstream exposes can answer. */
|
|
46
|
+
let mediaHost = '';
|
|
47
|
+
/**
|
|
48
|
+
* When the credentials were obtained, not when they were last handed out.
|
|
49
|
+
* The engine serves a live connection from its cache and gives no signal
|
|
50
|
+
* for which calls were actual fetches, so the stamp is kept only while the
|
|
51
|
+
* connection it describes is still live: past its own ttl, on a forced
|
|
52
|
+
* call, or on rotated credentials, whatever comes back is a fetch.
|
|
53
|
+
*
|
|
54
|
+
* A caller renewing on `fetchDate + ttl` needs both halves of that. Always
|
|
55
|
+
* restamping would push its deadline forward forever; never restamping
|
|
56
|
+
* would leave it renewing against a moment that has already passed.
|
|
57
|
+
*/
|
|
58
|
+
let fetched;
|
|
59
|
+
const isLive = (held) => Date.now() - held.at.getTime() < held.ttl * 1000;
|
|
60
|
+
return {
|
|
61
|
+
/**
|
|
62
|
+
* `maxContentLengthBytes` is absent by design: the core's hosts carry
|
|
63
|
+
* nothing but a hostname, so the field upstream declares has no source
|
|
64
|
+
* and is not invented here.
|
|
65
|
+
*/
|
|
66
|
+
refreshMediaConn: async (forceGet = false) => {
|
|
67
|
+
// Forced on the first call, as upstream's first call is: the engine may
|
|
68
|
+
// already hold a connection acquired by an upload, and stamping that
|
|
69
|
+
// one as fetched now would report it fresher than it is.
|
|
70
|
+
const held = fetched;
|
|
71
|
+
const conn = await (await ctx.getClient()).getMediaConn(forceGet || !held);
|
|
72
|
+
mediaHost = conn.hosts[0]?.hostname ?? mediaHost;
|
|
73
|
+
const isFetch = forceGet || !held || held.auth !== conn.auth || !isLive(held);
|
|
74
|
+
fetched = isFetch ? { auth: conn.auth, ttl: conn.ttl, at: new Date() } : held;
|
|
75
|
+
// A copy: the stored instant decides when the next call restamps, and a
|
|
76
|
+
// consumer holding the same Date could move it.
|
|
77
|
+
return { auth: conn.auth, ttl: conn.ttl, hosts: conn.hosts, fetchDate: new Date(fetched.at) };
|
|
78
|
+
},
|
|
79
|
+
/** Synchronous, as upstream has it, so it reads what the last refresh saw. */
|
|
80
|
+
getMediaHost: () => mediaHost,
|
|
81
|
+
getBotListV2: async () => {
|
|
82
|
+
return flattenBotList(await (await ctx.getClient()).getBotList());
|
|
83
|
+
},
|
|
84
|
+
fetchNewChatMessageCap: async () => {
|
|
85
|
+
return toCapInfo(await (await ctx.getClient()).fetchNewChatMessageCappingInfo());
|
|
86
|
+
},
|
|
87
|
+
cleanDirtyBits: async (type, fromTimestamp) => {
|
|
88
|
+
let timestamp = null;
|
|
89
|
+
if (fromTimestamp !== undefined) {
|
|
90
|
+
// A blank string is not a timestamp, and `Number('')` is the epoch,
|
|
91
|
+
// so without this a caller meaning "no timestamp" would ask the
|
|
92
|
+
// server to clean from the beginning of time.
|
|
93
|
+
const blank = typeof fromTimestamp === 'string' && fromTimestamp.trim() === '';
|
|
94
|
+
timestamp = typeof fromTimestamp === 'string' ? Number(fromTimestamp) : fromTimestamp;
|
|
95
|
+
if (blank || !Number.isFinite(timestamp)) {
|
|
96
|
+
throw new Boom(`cleanDirtyBits: fromTimestamp '${fromTimestamp}' is not a number`, { statusCode: 400 });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
await (await ctx.getClient()).cleanDirtyBits(type, timestamp);
|
|
100
|
+
},
|
|
101
|
+
/**
|
|
102
|
+
* Refused rather than wired up. The core already fires a peer data
|
|
103
|
+
* request itself when a message fails to decrypt, with its own age
|
|
104
|
+
* policy, so a second one here would duplicate it. The request a
|
|
105
|
+
* consumer actually drives, asking for history, is `fetchMessageHistory`.
|
|
106
|
+
*/
|
|
107
|
+
sendPeerDataOperationMessage: async (_pdoMessage) => {
|
|
108
|
+
throw new Boom('sendPeerDataOperationMessage is not supported: use fetchMessageHistory to request history, and note the engine issues its own peer data request when a message fails to decrypt', { statusCode: 501 });
|
|
109
|
+
},
|
|
110
|
+
/**
|
|
111
|
+
* Refused one layer down, as a build decision. `create_call_link` exists
|
|
112
|
+
* in the core behind its voip feature, and the bridge pins the core with
|
|
113
|
+
* default features off, so it is not compiled into the wasm artifact at
|
|
114
|
+
* all. Reaching it would pull the webrtc stack into the bundle.
|
|
115
|
+
*/
|
|
116
|
+
createCallLink: async (_type, _event, _timeoutMs) => {
|
|
117
|
+
throw new Boom('createCallLink is not available: the call-link operation sits behind the core voip feature, which is not compiled into the wasm bridge', { statusCode: 501 });
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=server-queries.js.map
|
|
@@ -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
|
package/lib/Socket/types.d.ts
CHANGED
|
@@ -23,6 +23,12 @@ export interface SocketContext {
|
|
|
23
23
|
getClientSync: () => WasmWhatsAppClient;
|
|
24
24
|
/** Raw stanza EventEmitter for CB: pattern compat */
|
|
25
25
|
ws: EventEmitter;
|
|
26
|
+
/**
|
|
27
|
+
* Where a failure goes when it has nowhere else to go: a dispatcher that
|
|
28
|
+
* threw, a wire batch that would not decode. Also what the socket exposes
|
|
29
|
+
* as `onUnexpectedError`, so the two are one reporter rather than two.
|
|
30
|
+
*/
|
|
31
|
+
reportUnexpectedError: (err: unknown, msg: string) => void;
|
|
26
32
|
}
|
|
27
33
|
/** Convert a bridge Jid struct to a string */
|
|
28
34
|
export declare const jidStr: (jid: {
|
package/lib/Types/Product.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
import type { CatalogResult as CatalogPageResult, CollectionsResult } from '@oxidezap/whatsapp-rust-bridge';
|
|
1
2
|
import type { WAMediaUpload } from './Message.js';
|
|
3
|
+
/**
|
|
4
|
+
* What `getCatalog` returns, under a name of its own. The `CatalogResult`
|
|
5
|
+
* below is the raw catalog envelope and is a different shape, so a consumer
|
|
6
|
+
* typing the call has one name to reach for and it is this one.
|
|
7
|
+
*/
|
|
8
|
+
export type CatalogPage = CatalogPageResult;
|
|
9
|
+
/** As `CatalogPage`, for `getCollections`. */
|
|
10
|
+
export type CollectionsPage = CollectionsResult;
|
|
2
11
|
export type CatalogResult = {
|
|
3
12
|
data: {
|
|
4
13
|
paging: {
|
|
@@ -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/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './auth-utils.js';
|
|
|
3
3
|
export * from './crypto.js';
|
|
4
4
|
export * from './generics.js';
|
|
5
5
|
export * from './messages.js';
|
|
6
|
+
export { getUrlInfo, type URLGenerationOptions } from './link-preview.js';
|
|
6
7
|
export * from './messages-media.js';
|
|
7
8
|
export * from './process-history-message.js';
|
|
8
9
|
export * from './process-message.js';
|
package/lib/Utils/index.js
CHANGED
|
@@ -3,6 +3,9 @@ export * from './auth-utils.js';
|
|
|
3
3
|
export * from './crypto.js';
|
|
4
4
|
export * from './generics.js';
|
|
5
5
|
export * from './messages.js';
|
|
6
|
+
// Named rather than `*`: the underscore hooks in that file exist for the tests
|
|
7
|
+
// and would otherwise become released API.
|
|
8
|
+
export { getUrlInfo } from './link-preview.js';
|
|
6
9
|
export * from './messages-media.js';
|
|
7
10
|
export * from './process-history-message.js';
|
|
8
11
|
export * from './process-message.js';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { WAUrlInfo } from '../Types/Message.js';
|
|
2
|
+
import type { ILogger } from './logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* The first link in a piece of text, or undefined when there is none. Exported
|
|
5
|
+
* under an underscore so the extraction can be tested without a network.
|
|
6
|
+
*
|
|
7
|
+
* Trailing prose punctuation is dropped: a link at the end of a sentence
|
|
8
|
+
* carries the full stop with it, and `https://example.com.` is not what the
|
|
9
|
+
* writer meant. A closing bracket goes the same way, which costs the rare url
|
|
10
|
+
* that genuinely ends in one and saves the common case of a link in
|
|
11
|
+
* parentheses.
|
|
12
|
+
*/
|
|
13
|
+
export declare const _firstLink: (text: string) => string | undefined;
|
|
14
|
+
export type URLGenerationOptions = {
|
|
15
|
+
thumbnailWidth: number;
|
|
16
|
+
fetchOpts: {
|
|
17
|
+
/** Timeout in ms */
|
|
18
|
+
timeout: number;
|
|
19
|
+
proxyUrl?: string;
|
|
20
|
+
headers?: HeadersInit;
|
|
21
|
+
};
|
|
22
|
+
uploadImage?: (encFilePath: string, opts: {
|
|
23
|
+
fileEncSha256B64: string;
|
|
24
|
+
mediaType: string;
|
|
25
|
+
}) => Promise<unknown>;
|
|
26
|
+
logger?: ILogger;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Fetched here rather than through `getHttpStream`, which forwards neither the
|
|
30
|
+
* timeout nor the proxy and validates no destination. The credentials in
|
|
31
|
+
* `headers` are for the page, so they are sent to the thumbnail only when it
|
|
32
|
+
* is the same origin; another host advertised by that page must not receive
|
|
33
|
+
* them.
|
|
34
|
+
*/
|
|
35
|
+
/** Exported under an underscore so the destination guard can be driven directly. */
|
|
36
|
+
export declare const _getCompressedJpegThumbnail: (url: string, pageUrl: string, { thumbnailWidth, fetchOpts }: URLGenerationOptions) => Promise<{
|
|
37
|
+
buffer: any;
|
|
38
|
+
original: {
|
|
39
|
+
width: any;
|
|
40
|
+
height: any;
|
|
41
|
+
};
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Reads the first URL out of a piece of text and fetches what a link preview
|
|
45
|
+
* needs. Nothing here is protocol: it is an HTTP fetch, an OpenGraph parse and
|
|
46
|
+
* a thumbnail, which is why it belongs in this layer rather than the engine.
|
|
47
|
+
*
|
|
48
|
+
* Resolves to undefined for the two cases that mean "no preview": text with no
|
|
49
|
+
* link in it, and a page with no title. Everything else throws, including a
|
|
50
|
+
* timeout, because a swallowed failure is indistinguishable from a page that
|
|
51
|
+
* genuinely had nothing, and a caller retrying the first would give up on the
|
|
52
|
+
* second.
|
|
53
|
+
*
|
|
54
|
+
* The metadata parse comes from `link-preview-js`, an optional peer dependency
|
|
55
|
+
* this package already declares and had no reader for, so nothing new is
|
|
56
|
+
* pulled in: a consumer who does not want link previews does not install it
|
|
57
|
+
* and never calls this.
|
|
58
|
+
*/
|
|
59
|
+
export declare const getUrlInfo: (text: string, opts?: URLGenerationOptions) => Promise<WAUrlInfo | undefined>;
|
|
60
|
+
//# sourceMappingURL=link-preview.d.ts.map
|