@oxidezap/baileyrs 0.2.10 → 0.2.12
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 +36 -0
- package/lib/Bridge/index.d.ts +1 -1
- package/lib/Bridge/index.js +1 -1
- package/lib/Bridge/primitives.d.ts +18 -6
- package/lib/Bridge/primitives.js +26 -13
- package/lib/Bridge/schema.js +103 -16
- package/lib/Bridge/types.d.ts +11 -0
- package/lib/Compatibility/proto-runtime.js +18 -9
- package/lib/Socket/index.js +7 -1
- package/lib/Socket/terminal-close-reporter.d.ts +29 -8
- package/lib/Socket/terminal-close-reporter.js +31 -8
- package/lib/Socket/unsupported-config.d.ts +1 -1
- package/lib/Socket/unsupported-config.js +1 -0
- package/lib/Types/Socket.d.ts +8 -0
- package/lib/Utils/use-bridge-store.d.ts +74 -3
- package/lib/Utils/use-bridge-store.js +541 -201
- package/lib/WAProto/compatibility-schema.d.ts +1 -1
- package/lib/WAProto/compatibility-schema.js +1 -1
- package/package.json +9 -7
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reports the terminal `connection.update { close }` — exactly once,
|
|
3
|
-
*
|
|
2
|
+
* Reports the terminal `connection.update { close }` — exactly once, and
|
|
3
|
+
* never silently not at all.
|
|
4
|
+
*
|
|
5
|
+
* The accepted claim publishes after its teardown settles, so a consumer that
|
|
6
|
+
* answers the close with a replacement socket does not overlap the old one's
|
|
7
|
+
* release. The watchdog is the deliberate exception: past the timeout the
|
|
8
|
+
* close goes out with teardown still running (and logged), because losing the
|
|
9
|
+
* event entirely is the worse failure. Nothing here promises that every close
|
|
10
|
+
* lands after every resource is released — only that at most one close lands.
|
|
4
11
|
*
|
|
5
12
|
* That sentence is the whole contract this branch sells, and getting it wrong
|
|
6
13
|
* has two opposite failure modes, both bad:
|
|
@@ -8,9 +15,8 @@
|
|
|
8
15
|
* - **Not reported.** The consumer's handler never runs, so it never builds a
|
|
9
16
|
* replacement socket. A bot offline with nothing in its logs — the original
|
|
10
17
|
* bug this branch exists to fix.
|
|
11
|
-
* - **Reported
|
|
12
|
-
* one
|
|
13
|
-
* notifications for one socket and a handler that cleans up on close loops.
|
|
18
|
+
* - **Reported twice.** Every listener sees two terminal notifications for
|
|
19
|
+
* one socket and a handler that cleans up on close loops.
|
|
14
20
|
*
|
|
15
21
|
* Keeping both away used to be inline logic split across the dispatcher hook
|
|
16
22
|
* and `logout()`, sharing a counter and a promise. Nine separate bugs came out
|
|
@@ -35,9 +41,24 @@ export const TERMINAL_CLOSE_PUBLISH_TIMEOUT_MS = 60000;
|
|
|
35
41
|
export const makeTerminalCloseReporter = (opts) => {
|
|
36
42
|
const { logger } = opts;
|
|
37
43
|
const publishTimeoutMs = opts.publishTimeoutMs ?? TERMINAL_CLOSE_PUBLISH_TIMEOUT_MS;
|
|
38
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Claims, not deliveries — see `hasReported`. At most one: a socket has a
|
|
46
|
+
* single terminal generation, so the first claim wins and later ones are
|
|
47
|
+
* ignored rather than published.
|
|
48
|
+
*/
|
|
39
49
|
let claimed = 0;
|
|
40
50
|
let publishedPromise;
|
|
51
|
+
/**
|
|
52
|
+
* True once the single terminal generation has been claimed. Deliberately
|
|
53
|
+
* log-free: a duplicate arrives on a path whose logger is
|
|
54
|
+
* consumer-replaceable, and an ignored signal must never throw.
|
|
55
|
+
*/
|
|
56
|
+
const claim = () => {
|
|
57
|
+
if (claimed > 0)
|
|
58
|
+
return false;
|
|
59
|
+
claimed++;
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
41
62
|
/** One publish per claim, whatever gets there first, and never throwing. */
|
|
42
63
|
const makeOnce = (publish, settle) => {
|
|
43
64
|
let done = false;
|
|
@@ -59,7 +80,8 @@ export const makeTerminalCloseReporter = (opts) => {
|
|
|
59
80
|
};
|
|
60
81
|
return {
|
|
61
82
|
reportAfter: (teardown, publish) => {
|
|
62
|
-
|
|
83
|
+
if (!claim())
|
|
84
|
+
return;
|
|
63
85
|
let settle;
|
|
64
86
|
publishedPromise = new Promise(resolve => {
|
|
65
87
|
settle = resolve;
|
|
@@ -94,7 +116,8 @@ export const makeTerminalCloseReporter = (opts) => {
|
|
|
94
116
|
}
|
|
95
117
|
},
|
|
96
118
|
reportNow: publish => {
|
|
97
|
-
|
|
119
|
+
if (!claim())
|
|
120
|
+
return;
|
|
98
121
|
let settle;
|
|
99
122
|
publishedPromise = new Promise(resolve => {
|
|
100
123
|
settle = resolve;
|
|
@@ -33,7 +33,7 @@ export declare const UNSUPPORTED_CONFIG_KEYS: readonly ["keepAliveIntervalMs", "
|
|
|
33
33
|
* member of `SocketConfig` belongs to neither list — which is exactly how the
|
|
34
34
|
* first version of the catalog shipped twelve keys short.
|
|
35
35
|
*/
|
|
36
|
-
export declare const READ_CONFIG_KEYS: readonly ["waWebSocketUrl", "options", "logger", "version", "browser", "pushName", "auth", "cache", "deviceProps", "wantedPreKeyCount", "emitOwnEvents", "shouldIgnoreJid", "defaultQueryTimeoutMs", "transactionOpts", "makeSignalRepository"];
|
|
36
|
+
export declare const READ_CONFIG_KEYS: readonly ["waWebSocketUrl", "options", "logger", "version", "browser", "pushName", "auth", "cache", "deviceProps", "wantedPreKeyCount", "dangerSkipCertChainVerify", "emitOwnEvents", "shouldIgnoreJid", "defaultQueryTimeoutMs", "transactionOpts", "makeSignalRepository"];
|
|
37
37
|
/**
|
|
38
38
|
* Which unsupported options this caller actually passed.
|
|
39
39
|
*
|
package/lib/Types/Socket.d.ts
CHANGED
|
@@ -93,6 +93,14 @@ export type SocketConfig = {
|
|
|
93
93
|
* generated and encoded in one shot). Must be set before connecting.
|
|
94
94
|
*/
|
|
95
95
|
wantedPreKeyCount?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Testing-only bypass for the Noise server-cert chain check, for mock
|
|
98
|
+
* servers that cannot sign a chain rooted in WhatsApp's issuer. Strict by
|
|
99
|
+
* default: absent, null and false all verify, and only a literal `true`
|
|
100
|
+
* opts in — the bridge rejects any other truthy value at construction
|
|
101
|
+
* rather than treating it as opt-in. Never set this outside tests.
|
|
102
|
+
*/
|
|
103
|
+
dangerSkipCertChainVerify?: boolean;
|
|
96
104
|
/** @deprecated QR timeout is handled by the bridge connection state machine. */
|
|
97
105
|
qrTimeout?: number;
|
|
98
106
|
/** Maximum retry count. */
|
|
@@ -1,13 +1,84 @@
|
|
|
1
1
|
import type { AuthenticationState } from '../Types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pure classifier for directory-barrier failures: does this error mean
|
|
4
|
+
* "this platform cannot sync directory handles" (degrade to process-crash
|
|
5
|
+
* atomicity) or "the durability barrier did not hold" (propagate)?
|
|
6
|
+
*
|
|
7
|
+
* - `ENOSYS` / `ENOTSUP` on any platform: the operation is not
|
|
8
|
+
* implemented — genuinely unsupported, safe to degrade.
|
|
9
|
+
* - `EINVAL` / `EPERM` / `EISDIR` on `win32` only: Windows directory
|
|
10
|
+
* handles reject open-for-read and FlushFileBuffers with these codes,
|
|
11
|
+
* so they are the documented platform fallback there. On Linux/macOS
|
|
12
|
+
* the same codes from a freshly opened directory handle mean something
|
|
13
|
+
* is genuinely wrong and they propagate.
|
|
14
|
+
* - Everything else propagates everywhere: `EIO`, `ENOSPC`, `EROFS`,
|
|
15
|
+
* `EACCES`, `ENOENT`, and notably `EBADF` (a bad handle is a real bug,
|
|
16
|
+
* never evidence of an unsupported platform).
|
|
17
|
+
*
|
|
18
|
+
* `platform` defaults to the running platform; tests pass explicit values
|
|
19
|
+
* to cover the matrix deterministically on any OS.
|
|
20
|
+
*/
|
|
21
|
+
export declare const isUnsupportedDirSync: (e: unknown, platform?: NodeJS.Platform) => boolean;
|
|
22
|
+
type BridgeStoreFileHandle = {
|
|
23
|
+
writeFile(value: Uint8Array): Promise<void>;
|
|
24
|
+
sync(): Promise<void>;
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
};
|
|
27
|
+
type BridgeStoreFileIO = {
|
|
28
|
+
writeTmp?(tmpPath: string, value: Uint8Array): Promise<void>;
|
|
29
|
+
publishTmp?(tmpPath: string, finalPath: string): Promise<void>;
|
|
30
|
+
syncDir?(dir: string): Promise<void>;
|
|
31
|
+
openTmp?(tmpPath: string): Promise<BridgeStoreFileHandle>;
|
|
32
|
+
};
|
|
33
|
+
type BridgeStoreOptions = {
|
|
34
|
+
io?: BridgeStoreFileIO;
|
|
35
|
+
};
|
|
2
36
|
/**
|
|
3
37
|
* Creates a file-based store for the WASM bridge.
|
|
4
38
|
*
|
|
5
39
|
* Each (store, key) pair maps to a file: `<folder>/<store>-<key>.bin`
|
|
6
40
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
41
|
+
* Durability model:
|
|
42
|
+
* - Caller buffers are copied synchronously at admission (`set`/`setMany`
|
|
43
|
+
* copy before queueing), so mutating a buffer after the call — even
|
|
44
|
+
* before awaiting it — can never change what gets persisted.
|
|
45
|
+
* - Critical stores write through `durableWrite` before `set`/`setMany`
|
|
46
|
+
* resolve. The in-memory map only records bytes AFTER the full barrier
|
|
47
|
+
* (write + fsync + atomic rename + directory sync) succeeds, so an
|
|
48
|
+
* identical retry following a failure is never skipped and always
|
|
49
|
+
* re-attempts the write.
|
|
50
|
+
* - If the post-rename barrier fails, the key is marked uncertain: prior
|
|
51
|
+
* durable knowledge is discarded, reads serve best-available bytes
|
|
52
|
+
* without re-certifying them, and no identical set is skipped until a
|
|
53
|
+
* later operation completes the full barrier for that key.
|
|
54
|
+
* - Non-critical stores are debounced (50ms coalescing) and readable
|
|
55
|
+
* immediately (read-your-write), but such reads are NOT durable until
|
|
56
|
+
* `flush()` succeeds. A failed flush keeps the pending entry and throws,
|
|
57
|
+
* so the next `flush()` retries the same bytes.
|
|
58
|
+
* - `flush()` first waits for every operation admitted before it
|
|
59
|
+
* (barrier), then drains the pending writes those operations produced.
|
|
60
|
+
* Failures observed by the barrier propagate — a failed admitted write
|
|
61
|
+
* fails the flush — but only operations outstanding during that flush
|
|
62
|
+
* are reported, so history never poisons later flushes. A drain pass
|
|
63
|
+
* with any failure stops at that pass and leaves the failed entries
|
|
64
|
+
* for the next explicit flush. `flush()` never reports quiescence
|
|
65
|
+
* while prior admitted work is still running.
|
|
66
|
+
* - All operations on one key (set, delete, flush, concurrent batches) run
|
|
67
|
+
* through a per-key chain, so a stale failure can never erase newer
|
|
68
|
+
* state and an in-flight write can never resurrect a deleted key.
|
|
69
|
+
* - A failed delete restores the preceding pending/durable state, so an
|
|
70
|
+
* acknowledged value stays readable and flushable; only a successful
|
|
71
|
+
* delete (unlink + directory barrier) clears it. A delete retried while
|
|
72
|
+
* the key is uncertain re-runs the directory barrier instead of
|
|
73
|
+
* swallowing the uncertainty as idempotent absence — except that a
|
|
74
|
+
* directory removed externally surfaces ENOENT rather than success.
|
|
75
|
+
* - Every byte array handed back to callers is a copy.
|
|
9
76
|
*
|
|
10
77
|
* @param folder Directory to store bridge state files
|
|
78
|
+
* @param options Optional per-store file-I/O steps. Test/fault-injection
|
|
79
|
+
* seam only: the default implementation is the sole provider of the
|
|
80
|
+
* durability contract above.
|
|
11
81
|
*/
|
|
12
|
-
export declare function useBridgeStore(folder: string): Promise<NonNullable<AuthenticationState['store']>>;
|
|
82
|
+
export declare function useBridgeStore(folder: string, options?: BridgeStoreOptions): Promise<NonNullable<AuthenticationState['store']>>;
|
|
83
|
+
export {};
|
|
13
84
|
//# sourceMappingURL=use-bridge-store.d.ts.map
|