@oxidezap/baileyrs 0.2.13 → 0.3.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/lib/Compatibility/signal-repository.d.ts +1 -1
- package/lib/Compatibility/signal-repository.js +61 -57
- package/lib/Compatibility/stanza-responses.d.ts +3 -3
- package/lib/Compatibility/stanza-responses.js +16 -13
- package/lib/Socket/blocking.js +11 -9
- package/lib/Socket/bridge-error-boundary.d.ts +2 -2
- package/lib/Socket/bridge-error-boundary.js +2 -2
- package/lib/Socket/business.js +63 -61
- package/lib/Socket/chat-actions.js +102 -98
- package/lib/Socket/client-operations.d.ts +6 -0
- package/lib/Socket/client-operations.js +2 -0
- package/lib/Socket/communities.js +8 -8
- package/lib/Socket/contacts.js +29 -26
- package/lib/Socket/events.js +1 -1
- package/lib/Socket/groups.js +20 -52
- package/lib/Socket/index.d.ts +2 -2
- package/lib/Socket/index.js +68 -90
- package/lib/Socket/internals.js +1 -1
- package/lib/Socket/messages.js +224 -218
- package/lib/Socket/newsletter.js +137 -133
- package/lib/Socket/prekeys.d.ts +1 -1
- package/lib/Socket/prekeys.js +20 -18
- package/lib/Socket/presence.js +15 -13
- package/lib/Socket/privacy.js +10 -10
- package/lib/Socket/profile.js +20 -18
- package/lib/Socket/server-queries.js +4 -4
- package/lib/Socket/types.d.ts +2 -6
- package/package.json +4 -4
|
@@ -6,7 +6,7 @@ import { GROUP_REQUEST_ACTIONS, GROUP_SETTINGS, JOIN_APPROVAL_MODES, makeGroupMe
|
|
|
6
6
|
export const makeCommunityMethods = (ctx, groups = makeGroupMethods(ctx)) => {
|
|
7
7
|
const communityMetadata = async (jid) => groups.groupMetadata(jid);
|
|
8
8
|
const communityFetchAllParticipating = async () => {
|
|
9
|
-
const bridgeCommunities = await
|
|
9
|
+
const bridgeCommunities = await ctx.withClient(client => client.communityFetchAllParticipating());
|
|
10
10
|
const result = {};
|
|
11
11
|
for (const [communityJid, metadata] of Object.entries(bridgeCommunities)) {
|
|
12
12
|
result[communityJid] = bridgeGroupMetadataToBaileys(metadata);
|
|
@@ -17,30 +17,30 @@ export const makeCommunityMethods = (ctx, groups = makeGroupMethods(ctx)) => {
|
|
|
17
17
|
return {
|
|
18
18
|
communityMetadata,
|
|
19
19
|
communityCreate: async (subject, body) => {
|
|
20
|
-
const metadata = await
|
|
20
|
+
const metadata = await ctx.withClient(client => client.createCommunity(subject, body || undefined, true, true, true));
|
|
21
21
|
return bridgeGroupMetadataToBaileys(metadata);
|
|
22
22
|
},
|
|
23
23
|
communityCreateGroup: async (subject, participants, parentCommunityJid) => {
|
|
24
|
-
const metadata = await
|
|
24
|
+
const metadata = await ctx.withClient(client => client.createCommunitySubgroup(subject, participants, parentCommunityJid));
|
|
25
25
|
return bridgeGroupMetadataToBaileys(metadata);
|
|
26
26
|
},
|
|
27
27
|
communityLeave: async (id) => {
|
|
28
|
-
await
|
|
28
|
+
await ctx.withClient(client => client.deactivateCommunity(id));
|
|
29
29
|
},
|
|
30
30
|
communityUpdateSubject: async (jid, subject) => {
|
|
31
31
|
await groups.groupUpdateSubject(jid, subject);
|
|
32
32
|
},
|
|
33
33
|
communityLinkGroup: async (groupJid, parentCommunityJid) => {
|
|
34
|
-
await
|
|
34
|
+
await ctx.withClient(client => client.linkCommunitySubgroups(parentCommunityJid, [groupJid]));
|
|
35
35
|
},
|
|
36
36
|
communityUnlinkGroup: async (groupJid, parentCommunityJid) => {
|
|
37
|
-
await
|
|
37
|
+
await ctx.withClient(client => client.unlinkCommunitySubgroups(parentCommunityJid, [groupJid], false));
|
|
38
38
|
},
|
|
39
39
|
communityFetchLinkedGroups: async (jid) => {
|
|
40
40
|
const metadata = await groups.groupMetadata(jid);
|
|
41
41
|
const communityJid = metadata.linkedParent || jid;
|
|
42
42
|
const isCommunity = !metadata.linkedParent;
|
|
43
|
-
const subgroups = await
|
|
43
|
+
const subgroups = await ctx.withClient(client => client.getCommunitySubgroups(communityJid));
|
|
44
44
|
const linkedGroups = subgroups.map(group => ({
|
|
45
45
|
id: group.id,
|
|
46
46
|
subject: group.subject,
|
|
@@ -61,7 +61,7 @@ export const makeCommunityMethods = (ctx, groups = makeGroupMethods(ctx)) => {
|
|
|
61
61
|
},
|
|
62
62
|
communityParticipantsUpdate: async (jid, participants, action) => {
|
|
63
63
|
assertArgumentDomain('communityParticipantsUpdate', 'action', action, PARTICIPANT_ACTIONS);
|
|
64
|
-
return bridgeParticipantChangesToBaileys(await
|
|
64
|
+
return bridgeParticipantChangesToBaileys(await ctx.withClient(client => client.communityParticipantsUpdate(jid, participants, action)));
|
|
65
65
|
},
|
|
66
66
|
communityUpdateDescription: groups.groupUpdateDescription,
|
|
67
67
|
communityInviteCode: groups.groupInviteCode,
|
package/lib/Socket/contacts.js
CHANGED
|
@@ -1,29 +1,32 @@
|
|
|
1
1
|
import { assertArgumentDomain } from '../Utils/argument-domain.js';
|
|
2
2
|
export const PROFILE_PICTURE_TYPES = ['preview', 'image'];
|
|
3
|
-
export const makeContactMethods = (ctx) =>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
3
|
+
export const makeContactMethods = (ctx) => {
|
|
4
|
+
return {
|
|
5
|
+
onWhatsApp: async (...phoneNumber) => {
|
|
6
|
+
return ctx.withClient(async (client) => {
|
|
7
|
+
// Single batched usync — the bridge splits PN/LID inputs internally and
|
|
8
|
+
// returns lid/pnJid/isBusiness in the same payload, so no secondary IQ.
|
|
9
|
+
const results = await client.isOnWhatsApp(phoneNumber);
|
|
10
|
+
return results.map(r => {
|
|
11
|
+
const out = { exists: r.isRegistered, jid: r.jid, isBusiness: r.isBusiness };
|
|
12
|
+
if (r.lid)
|
|
13
|
+
out.lid = r.lid;
|
|
14
|
+
if (r.pnJid)
|
|
15
|
+
out.pnJid = r.pnJid;
|
|
16
|
+
if (r.verifiedName)
|
|
17
|
+
out.verifiedName = r.verifiedName;
|
|
18
|
+
return out;
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
profilePictureUrl: async (jid, type = 'preview', timeoutMs) => {
|
|
23
|
+
assertArgumentDomain('profilePictureUrl', 'type', type, PROFILE_PICTURE_TYPES);
|
|
24
|
+
const result = await ctx.withClient(client => client.profilePictureUrl(jid, type, timeoutMs));
|
|
25
|
+
return result?.url;
|
|
26
|
+
},
|
|
27
|
+
fetchUserInfo: async (...jids) => {
|
|
28
|
+
return await ctx.withClient(client => client.fetchUserInfo(jids));
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
};
|
|
29
32
|
//# sourceMappingURL=contacts.js.map
|
package/lib/Socket/events.js
CHANGED
|
@@ -546,7 +546,7 @@ const DISPATCHERS = {
|
|
|
546
546
|
// the same complete lifecycle and ordering as upstream.
|
|
547
547
|
void (async () => {
|
|
548
548
|
try {
|
|
549
|
-
const bridgeMetadata = await
|
|
549
|
+
const bridgeMetadata = await ctx.withClient(client => client.getGroupMetadata(evt.groupJid));
|
|
550
550
|
const metadata = bridgeGroupMetadataToBaileys(bridgeMetadata);
|
|
551
551
|
ctx.ev.emit('chats.upsert', [
|
|
552
552
|
{ id: metadata.id, name: metadata.subject, conversationTimestamp: metadata.creation }
|
package/lib/Socket/groups.js
CHANGED
|
@@ -20,36 +20,15 @@ export const JOIN_APPROVAL_MODES = ['on', 'off'];
|
|
|
20
20
|
// exact as a double, and that shape carries no methods. The helper reads both
|
|
21
21
|
// forms, and reconstructs the high word instead of dropping it.
|
|
22
22
|
const inviteExpirationNumber = (value) => toNumber(value);
|
|
23
|
-
// The core's V4 join parser only accepts a `<group>`, `<community>` or
|
|
24
|
-
// `<membership_approval_request>` child, but the server also answers a
|
|
25
|
-
// successful join with a bare `<iq type="result">` (WA Web's own
|
|
26
|
-
// `AcceptGroupAddResponseSuccess` variant requires no child at all — only the
|
|
27
|
-
// result envelope whose `from` echoes the request's `to`). The core reports
|
|
28
|
-
// that shape as an `IqError::ParseError`, which the bridge surfaces as
|
|
29
|
-
// `kind: 'internal'`. Error stanzas never reach the parser (they become
|
|
30
|
-
// `kind: 'server'` one layer below), so this substring can only mean the join
|
|
31
|
-
// was accepted and the JID carrier is missing — never a rejection.
|
|
32
|
-
const BARE_JOIN_SUCCESS_FRAGMENT = 'expected <group>, <community>, or <membership_approval_request> in join response';
|
|
33
|
-
// A bare `<iq type="result">` join success, as described above. Anything else
|
|
34
|
-
// (server rejections, timeouts, transport loss, protocol violations) must keep
|
|
35
|
-
// propagating.
|
|
36
|
-
const isBareJoinSuccess = (error) => {
|
|
37
|
-
if (!(error instanceof Error) || error.name !== 'WhatsAppError')
|
|
38
|
-
return false;
|
|
39
|
-
const kind = error.kind;
|
|
40
|
-
if (kind !== 'internal')
|
|
41
|
-
return false;
|
|
42
|
-
return typeof error.message === 'string' && error.message.includes(BARE_JOIN_SUCCESS_FRAGMENT);
|
|
43
|
-
};
|
|
44
23
|
export const makeGroupMethods = (ctx) => {
|
|
45
24
|
const groupMetadata = async (jid) => {
|
|
46
|
-
const metadata = await
|
|
25
|
+
const metadata = await ctx.withClient(client => client.getGroupMetadata(jid));
|
|
47
26
|
return bridgeGroupMetadataToBaileys(metadata);
|
|
48
27
|
};
|
|
49
28
|
const groupSettingUpdate = async (jid, setting) => {
|
|
50
29
|
const checked = assertArgumentDomain('groupSettingUpdate', 'setting', setting, GROUP_SETTINGS);
|
|
51
30
|
const mapped = GROUP_SETTING_ALIASES[checked];
|
|
52
|
-
await
|
|
31
|
+
await ctx.withClient(client => client.groupSettingUpdate(jid, mapped.setting, mapped.value));
|
|
53
32
|
};
|
|
54
33
|
const groupAcceptInviteV4 = ctx.ev.createBufferedFunction(async (key, inviteMessage
|
|
55
34
|
// oxlint-disable-next-line typescript/no-explicit-any -- the established public contract returns Promise<any>.
|
|
@@ -59,18 +38,7 @@ export const makeGroupMethods = (ctx) => {
|
|
|
59
38
|
if (!groupJid || !inviteMessage.inviteCode || !messageKey.remoteJid) {
|
|
60
39
|
throw new TypeError('groupAcceptInviteV4 requires groupJid, inviteCode and inviter JID');
|
|
61
40
|
}
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
joinedJid = await (await ctx.getClient()).groupAcceptInviteV4(groupJid, inviteMessage.inviteCode, inviteExpirationNumber(inviteMessage.inviteExpiration), messageKey.remoteJid);
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
// The join was accepted but the response carried no JID node.
|
|
68
|
-
// Baileys returns the envelope's `from` here, which echoes the
|
|
69
|
-
// request's `to` — the group JID we already hold.
|
|
70
|
-
if (!isBareJoinSuccess(error))
|
|
71
|
-
throw error;
|
|
72
|
-
joinedJid = groupJid;
|
|
73
|
-
}
|
|
41
|
+
const joinedJid = await ctx.withClient(client => client.groupAcceptInviteV4(groupJid, inviteMessage.inviteCode, inviteExpirationNumber(inviteMessage.inviteExpiration), messageKey.remoteJid));
|
|
74
42
|
if (messageKey.id) {
|
|
75
43
|
const expiredInvite = proto.Message.GroupInviteMessage.fromObject(inviteMessage);
|
|
76
44
|
expiredInvite.inviteExpiration = 0;
|
|
@@ -101,61 +69,61 @@ export const makeGroupMethods = (ctx) => {
|
|
|
101
69
|
return {
|
|
102
70
|
groupMetadata,
|
|
103
71
|
groupCreate: async (subject, participants) => {
|
|
104
|
-
const metadata = await
|
|
72
|
+
const metadata = await ctx.withClient(client => client.createGroup(subject, participants));
|
|
105
73
|
return bridgeGroupMetadataToBaileys(metadata);
|
|
106
74
|
},
|
|
107
75
|
groupLeave: async (id) => {
|
|
108
|
-
await
|
|
76
|
+
await ctx.withClient(client => client.groupLeave(id));
|
|
109
77
|
},
|
|
110
78
|
groupUpdateSubject: async (jid, subject) => {
|
|
111
|
-
await
|
|
79
|
+
await ctx.withClient(client => client.groupUpdateSubject(jid, subject));
|
|
112
80
|
},
|
|
113
81
|
groupRequestParticipantsList: async (jid) => {
|
|
114
|
-
return bridgeMembershipRequestsToBaileys(await
|
|
82
|
+
return bridgeMembershipRequestsToBaileys(await ctx.withClient(client => client.groupRequestParticipantsList(jid)));
|
|
115
83
|
},
|
|
116
84
|
groupRequestParticipantsUpdate: async (jid, participants, action) => {
|
|
117
85
|
assertArgumentDomain('groupRequestParticipantsUpdate', 'action', action, GROUP_REQUEST_ACTIONS);
|
|
118
|
-
return bridgeMembershipRequestUpdatesToBaileys(await
|
|
86
|
+
return bridgeMembershipRequestUpdatesToBaileys(await ctx.withClient(client => client.groupRequestParticipantsUpdate(jid, participants, action)));
|
|
119
87
|
},
|
|
120
88
|
groupParticipantsUpdate: async (jid, participants, action) => {
|
|
121
89
|
assertArgumentDomain('groupParticipantsUpdate', 'action', action, PARTICIPANT_ACTIONS);
|
|
122
|
-
return bridgeParticipantChangesToBaileys(await
|
|
90
|
+
return bridgeParticipantChangesToBaileys(await ctx.withClient(client => client.groupParticipantsUpdate(jid, participants, action)));
|
|
123
91
|
},
|
|
124
92
|
groupUpdateDescription: async (jid, description) => {
|
|
125
|
-
await
|
|
93
|
+
await ctx.withClient(client => client.groupUpdateDescription(jid, description));
|
|
126
94
|
},
|
|
127
95
|
groupInviteCode: async (jid) => {
|
|
128
|
-
return bridgeInviteLinkToCode(await
|
|
96
|
+
return bridgeInviteLinkToCode(await ctx.withClient(client => client.groupInviteCode(jid)));
|
|
129
97
|
},
|
|
130
98
|
groupRevokeInvite: async (jid) => {
|
|
131
|
-
return bridgeInviteLinkToCode(await
|
|
99
|
+
return bridgeInviteLinkToCode(await ctx.withClient(client => client.groupRevokeInvite(jid)));
|
|
132
100
|
},
|
|
133
101
|
groupAcceptInvite: async (code) => {
|
|
134
|
-
return
|
|
102
|
+
return ctx.withClient(client => client.groupAcceptInvite(code));
|
|
135
103
|
},
|
|
136
104
|
groupRevokeInviteV4: async (groupJid, invitedJid) => {
|
|
137
|
-
return
|
|
105
|
+
return ctx.withClient(client => client.groupRevokeInviteV4(groupJid, invitedJid));
|
|
138
106
|
},
|
|
139
107
|
groupAcceptInviteV4,
|
|
140
108
|
groupGetInviteInfo: async (code) => {
|
|
141
|
-
return bridgeGroupMetadataToBaileys(await
|
|
109
|
+
return bridgeGroupMetadataToBaileys(await ctx.withClient(client => client.groupGetInviteInfo(code)));
|
|
142
110
|
},
|
|
143
111
|
groupToggleEphemeral: async (jid, ephemeralExpiration) => {
|
|
144
|
-
await
|
|
112
|
+
await ctx.withClient(client => client.groupToggleEphemeral(jid, ephemeralExpiration));
|
|
145
113
|
},
|
|
146
114
|
groupSettingUpdate,
|
|
147
115
|
groupMemberAddMode: async (jid, mode) => {
|
|
148
116
|
assertArgumentDomain('groupMemberAddMode', 'mode', mode, MEMBER_ADD_MODES);
|
|
149
|
-
await
|
|
117
|
+
await ctx.withClient(client => client.groupMemberAddMode(jid, mode));
|
|
150
118
|
},
|
|
151
119
|
groupJoinApprovalMode: async (jid, mode) => {
|
|
152
120
|
// Anything but 'on' used to mean off, so a typo turned approvals off
|
|
153
121
|
// and reported success.
|
|
154
122
|
assertArgumentDomain('groupJoinApprovalMode', 'mode', mode, JOIN_APPROVAL_MODES);
|
|
155
|
-
await
|
|
123
|
+
await ctx.withClient(client => client.groupSettingUpdate(jid, 'membership_approval', mode === 'on'));
|
|
156
124
|
},
|
|
157
125
|
groupFetchAllParticipating: async () => {
|
|
158
|
-
const bridgeGroups = await
|
|
126
|
+
const bridgeGroups = await ctx.withClient(client => client.groupFetchAllParticipating());
|
|
159
127
|
const result = {};
|
|
160
128
|
for (const [groupJid, metadata] of Object.entries(bridgeGroups)) {
|
|
161
129
|
result[groupJid] = bridgeGroupMetadataToBaileys(metadata);
|
|
@@ -164,7 +132,7 @@ export const makeGroupMethods = (ctx) => {
|
|
|
164
132
|
return result;
|
|
165
133
|
},
|
|
166
134
|
updateMemberLabel: async (jid, memberLabel) => {
|
|
167
|
-
return
|
|
135
|
+
return ctx.withClient(client => client.updateMemberLabel(jid, memberLabel.slice(0, 30)));
|
|
168
136
|
}
|
|
169
137
|
};
|
|
170
138
|
};
|
package/lib/Socket/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
-
import { type UploadMediaResult } from '@oxidezap/whatsapp-rust-bridge';
|
|
2
|
+
import { type WasmWhatsAppClient, type UploadMediaResult } from '@oxidezap/whatsapp-rust-bridge';
|
|
3
3
|
import { WebSocketClient } from '../Compatibility/websocket-client.js';
|
|
4
4
|
import { type MediaType } from '../Defaults/index.js';
|
|
5
5
|
import type { BinaryNode, AuthenticationCreds, ConnectionState, Contact, ReachoutTimelockState, SignalKeyStoreWithTransaction, UserFacingSocketConfig, WABusinessProfile, WAMessage, WAMessageKey, WAPresence } from '../Types/index.js';
|
|
@@ -220,7 +220,7 @@ declare const makeWASocket: (config: UserFacingSocketConfig) => {
|
|
|
220
220
|
*/
|
|
221
221
|
[Symbol.asyncDispose](): Promise<void>;
|
|
222
222
|
user: Contact | undefined;
|
|
223
|
-
waClient:
|
|
223
|
+
waClient: WasmWhatsAppClient | undefined;
|
|
224
224
|
isConnected: boolean;
|
|
225
225
|
isLoggedIn: boolean;
|
|
226
226
|
authState: {
|
package/lib/Socket/index.js
CHANGED
|
@@ -44,6 +44,7 @@ import { makeServerQueryMethods } from './server-queries.js';
|
|
|
44
44
|
import { makeProfileMethods } from './profile.js';
|
|
45
45
|
import { mapReachoutTimelock } from './reachout.js';
|
|
46
46
|
import { makeHttpClient, makeTransport } from './transport.js';
|
|
47
|
+
import { makeWithClient } from './client-operations.js';
|
|
47
48
|
import { makeUSyncMethods } from './usync.js';
|
|
48
49
|
let wasmInitialized = false;
|
|
49
50
|
/**
|
|
@@ -216,24 +217,19 @@ const makeWASocket = (config) => {
|
|
|
216
217
|
throw firstFlushError;
|
|
217
218
|
},
|
|
218
219
|
release: async (client) => {
|
|
219
|
-
// `disconnect()` before `free()`
|
|
220
|
-
//
|
|
220
|
+
// `disconnect()` before `free()` drains exactly one shape: a
|
|
221
|
+
// `disconnect()` still in flight. Since bridge 0.21.1, freeing
|
|
222
|
+
// with ordinary calls pending (`fetchBlocklist()`, `logout()`) is
|
|
223
|
+
// safe — its `Drop` signals shutdown and aborts the background
|
|
224
|
+
// tasks — but freeing mid-`disconnect()` still aborts the process
|
|
225
|
+
// (`async-lock` panicking while panicking). See
|
|
226
|
+
// `__tests__/bridge-free-safety.test.ts`.
|
|
221
227
|
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
// here
|
|
227
|
-
// `logout()`, `disconnect()` and a plain `fetchBlocklist()` all
|
|
228
|
-
// reproduce it — see `__tests__/bridge-free-safety.test.ts`.
|
|
229
|
-
//
|
|
230
|
-
// What keeps teardown off that path is the `ws.close()` above, which
|
|
231
|
-
// is itself a `client.disconnect()` (`Compatibility/websocket-client.ts`).
|
|
232
|
-
// This is the belt to that braces, and the gap it closes is
|
|
233
|
-
// `WebSocketClient.close()`'s early return when `closing`/`closed` is
|
|
234
|
-
// already set: that path does NOT await the disconnect it skipped, so
|
|
235
|
-
// `void sock.ws.close(); await sock.end()` could otherwise reach
|
|
236
|
-
// `free()` with the first disconnect still running.
|
|
228
|
+
// That shape is reachable: `WebSocketClient.close()` early-returns
|
|
229
|
+
// when `closing`/`closed` is already set without awaiting the
|
|
230
|
+
// disconnect it skipped, so `void sock.ws.close(); await sock.end()`
|
|
231
|
+
// could otherwise reach `free()` with the first disconnect still
|
|
232
|
+
// running. Awaiting it here is the belt to `ws.close()`'s braces.
|
|
237
233
|
let disconnected = true;
|
|
238
234
|
try {
|
|
239
235
|
await client.disconnect();
|
|
@@ -339,61 +335,42 @@ const makeWASocket = (config) => {
|
|
|
339
335
|
setUser: u => {
|
|
340
336
|
user = u;
|
|
341
337
|
},
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
// still running would otherwise be handed the client anyway.
|
|
367
|
-
//
|
|
368
|
-
// The window between handing a client back and the call reaching
|
|
369
|
-
// wasm cannot be closed here — that needs in-flight call
|
|
370
|
-
// tracking. What covers it is `release`, which awaits
|
|
371
|
-
// `client.disconnect()` before `free()`; the corruption comes
|
|
372
|
-
// from freeing with a call pending, and the disconnect drains
|
|
373
|
-
// those first (`__tests__/bridge-free-safety.test.ts`).
|
|
374
|
-
if (owner.isClosing()) {
|
|
375
|
-
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed });
|
|
376
|
-
}
|
|
377
|
-
if (initError) {
|
|
378
|
-
throw new Boom('Bridge client failed to initialize: ' + initError.message, { statusCode: 500 });
|
|
379
|
-
}
|
|
380
|
-
const built = owner.peek();
|
|
381
|
-
if (!built)
|
|
382
|
-
throw new Boom('Client not initialized', { statusCode: 500 });
|
|
383
|
-
return wrapBridgeClient(built);
|
|
384
|
-
});
|
|
385
|
-
},
|
|
386
|
-
getClientSync: () => {
|
|
387
|
-
// Same rule as `getClient` — see there.
|
|
338
|
+
withClient: makeWithClient(getClient)
|
|
339
|
+
};
|
|
340
|
+
function getClient() {
|
|
341
|
+
// Teardown retains the client through closing to disconnect the transport.
|
|
342
|
+
// Ordinary operations must stop being admitted when close() is called.
|
|
343
|
+
if (owner.isClosing()) {
|
|
344
|
+
return Promise.reject(new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed }));
|
|
345
|
+
}
|
|
346
|
+
// Otherwise gated on `initialized`, not merely on the client
|
|
347
|
+
// existing. `adopt()` publishes it several awaits before
|
|
348
|
+
// `setDeviceProps`, the account lookups and `run()`, so keying off
|
|
349
|
+
// `peek()` alone would hand ordinary calls like `sendMessage()` a
|
|
350
|
+
// half-built client whose read loop has not started — and skip the
|
|
351
|
+
// `initError` check when startup later fails.
|
|
352
|
+
if (initialized) {
|
|
353
|
+
const ready = owner.peek();
|
|
354
|
+
if (ready)
|
|
355
|
+
return Promise.resolve(wrapBridgeClient(ready));
|
|
356
|
+
}
|
|
357
|
+
return initPromise.then(() => {
|
|
358
|
+
// Closing may have started while initialization was pending.
|
|
359
|
+
// This gate does not track admitted operations. Bridge 0.21.1 tolerates
|
|
360
|
+
// free() during ordinary calls, but not during disconnect(); release
|
|
361
|
+
// still awaits that drain before freeing the client.
|
|
388
362
|
if (owner.isClosing()) {
|
|
389
363
|
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed });
|
|
390
364
|
}
|
|
365
|
+
if (initError) {
|
|
366
|
+
throw new Boom('Bridge client failed to initialize: ' + initError.message, { statusCode: 500 });
|
|
367
|
+
}
|
|
391
368
|
const built = owner.peek();
|
|
392
369
|
if (!built)
|
|
393
370
|
throw new Boom('Client not initialized', { statusCode: 500 });
|
|
394
371
|
return wrapBridgeClient(built);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
397
374
|
// The native repository delegates Signal state directly to the core and does
|
|
398
375
|
// not need the standalone transaction facade. Keep that facade lazy for the
|
|
399
376
|
// public authState and custom repository contracts that can observe it.
|
|
@@ -776,7 +753,7 @@ const makeWASocket = (config) => {
|
|
|
776
753
|
});
|
|
777
754
|
};
|
|
778
755
|
const fetchReachoutTimelock = async () => {
|
|
779
|
-
const payload = await
|
|
756
|
+
const payload = await ctx.withClient(client => client.fetchReachoutTimelock());
|
|
780
757
|
const state = mapReachoutTimelock(payload) ?? { isActive: false };
|
|
781
758
|
ev.emit('connection.update', { reachoutTimeLock: state });
|
|
782
759
|
return state;
|
|
@@ -784,14 +761,14 @@ const makeWASocket = (config) => {
|
|
|
784
761
|
const query = async (node, timeoutMs) => {
|
|
785
762
|
if (!node.attrs.id)
|
|
786
763
|
node.attrs.id = generateMessageTag();
|
|
787
|
-
const result = (await
|
|
764
|
+
const result = (await ctx.withClient(client => client.queryNode(node, timeoutMs)));
|
|
788
765
|
assertNodeErrorFree(result);
|
|
789
766
|
return result;
|
|
790
767
|
};
|
|
791
768
|
const waitForMessage = makeTaggedMessageWaiter(ws, logger, fullConfig.defaultQueryTimeoutMs);
|
|
792
769
|
const usyncMethods = makeUSyncMethods({
|
|
793
770
|
queryNode: query,
|
|
794
|
-
queryUsync: async (typedQuery) =>
|
|
771
|
+
queryUsync: async (typedQuery) => ctx.withClient(client => client.queryUsync(typedQuery))
|
|
795
772
|
});
|
|
796
773
|
const sock = {
|
|
797
774
|
ev,
|
|
@@ -876,18 +853,18 @@ const makeWASocket = (config) => {
|
|
|
876
853
|
notificationMutex,
|
|
877
854
|
generateMessageTag,
|
|
878
855
|
sendNode: async (frame) => {
|
|
879
|
-
return
|
|
856
|
+
return ctx.withClient(client => client.sendNode(frame));
|
|
880
857
|
},
|
|
881
858
|
assertSessions: async (jids, force) => {
|
|
882
|
-
return
|
|
859
|
+
return ctx.withClient(client => client.assertSessions(jids, force ?? false));
|
|
883
860
|
},
|
|
884
861
|
getUSyncDevices: async (jids, useCache, ignoreZeroDevices) => {
|
|
885
|
-
return
|
|
862
|
+
return ctx.withClient(client => client.getUSyncDevices(jids, useCache, ignoreZeroDevices));
|
|
886
863
|
},
|
|
887
864
|
waitForMessage,
|
|
888
865
|
query,
|
|
889
866
|
sendRawMessage: async (data) => {
|
|
890
|
-
return
|
|
867
|
+
return ctx.withClient(client => client.sendRawMessage(data instanceof Uint8Array ? data : new Uint8Array(data)));
|
|
891
868
|
},
|
|
892
869
|
/**
|
|
893
870
|
* `dsmMessage` is accepted so the signature matches upstream, and
|
|
@@ -901,7 +878,7 @@ const makeWASocket = (config) => {
|
|
|
901
878
|
throw new Boom('createParticipantNodes: dsmMessage is not supported, the engine encrypts one payload for every recipient and cannot substitute a different one for your own devices', { statusCode: 501 });
|
|
902
879
|
}
|
|
903
880
|
const bytes = encodeProtoCompat('Message', message);
|
|
904
|
-
return
|
|
881
|
+
return ctx.withClient(client => client.createParticipantNodesBytes(jids, bytes, extraAttrs ?? {}));
|
|
905
882
|
},
|
|
906
883
|
signalRepository,
|
|
907
884
|
...makePreKeyMethods(ctx),
|
|
@@ -926,14 +903,15 @@ const makeWASocket = (config) => {
|
|
|
926
903
|
// Ahead of the client: an off-union value used to fall through to the
|
|
927
904
|
// chat-state branch and be reported as a missing jid.
|
|
928
905
|
assertArgumentDomain('sendPresenceUpdate', 'type', type, WA_PRESENCES);
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
906
|
+
return ctx.withClient(async (c) => {
|
|
907
|
+
if (type === 'available' || type === 'unavailable') {
|
|
908
|
+
return c.sendPresence(type);
|
|
909
|
+
}
|
|
910
|
+
if (!toJid) {
|
|
911
|
+
throw new Boom(`sendPresenceUpdate('${type}') requires a target jid`, { statusCode: 400 });
|
|
912
|
+
}
|
|
913
|
+
return c.sendChatState(toJid, type);
|
|
914
|
+
});
|
|
937
915
|
},
|
|
938
916
|
/**
|
|
939
917
|
* Plaintext media upload helper, source-compatible with the upstream
|
|
@@ -946,15 +924,15 @@ const makeWASocket = (config) => {
|
|
|
946
924
|
// `toBridgeMediaType` below still refuses the ones it cannot map.
|
|
947
925
|
assertArgumentDomain('waUploadToServer', 'mediaType', opts?.mediaType, MEDIA_TYPES);
|
|
948
926
|
const bytes = data instanceof Uint8Array && !Buffer.isBuffer(data) ? data : new Uint8Array(data);
|
|
949
|
-
return
|
|
927
|
+
return ctx.withClient(client => client.uploadMedia(bytes, toBridgeMediaType(opts.mediaType)));
|
|
950
928
|
},
|
|
951
929
|
...makePrivacyMethods(ctx),
|
|
952
930
|
updateDefaultDisappearingMode: async (duration) => {
|
|
953
|
-
await
|
|
931
|
+
await ctx.withClient(client => client.updateDefaultDisappearingMode(duration));
|
|
954
932
|
},
|
|
955
933
|
rejectCall: async (callId, callFrom) => {
|
|
956
934
|
const context = activeCallContexts.get(callId);
|
|
957
|
-
await
|
|
935
|
+
await ctx.withClient(client => client.rejectCall(callId, context?.peer ?? callFrom, context?.callCreator ?? callFrom));
|
|
958
936
|
activeCallContexts.delete(callId);
|
|
959
937
|
},
|
|
960
938
|
/**
|
|
@@ -974,14 +952,14 @@ const makeWASocket = (config) => {
|
|
|
974
952
|
/** Upstream Baileys-compatible name. */
|
|
975
953
|
fetchAccountReachoutTimelock: fetchReachoutTimelock,
|
|
976
954
|
getBusinessProfile: async (jid) => {
|
|
977
|
-
return bridgeBusinessProfileToBaileys(await
|
|
955
|
+
return bridgeBusinessProfileToBaileys(await ctx.withClient(client => client.getBusinessProfile(jid)));
|
|
978
956
|
},
|
|
979
957
|
fetchMessageHistory: async (count, oldestMsgKey, oldestMsgTimestamp) => {
|
|
980
|
-
return
|
|
958
|
+
return ctx.withClient(client => client.fetchMessageHistory(count, oldestMsgKey.remoteJid || '', oldestMsgKey.id || '', oldestMsgKey.fromMe || false, typeof oldestMsgTimestamp === 'number' ? oldestMsgTimestamp : oldestMsgTimestamp.toNumber()));
|
|
981
959
|
},
|
|
982
960
|
sendStatusMessage: async (message, recipients) => {
|
|
983
961
|
const bytes = encodeProtoCompat('Message', message);
|
|
984
|
-
return
|
|
962
|
+
return ctx.withClient(client => client.sendStatusMessageBytes(bytes, recipients));
|
|
985
963
|
},
|
|
986
964
|
...makeMessageMethods(ctx),
|
|
987
965
|
...groupMethods,
|
|
@@ -1002,11 +980,11 @@ const makeWASocket = (config) => {
|
|
|
1002
980
|
// the context below is built, and a check past that await reports a
|
|
1003
981
|
// stack without the caller in it.
|
|
1004
982
|
assertArgumentDomain('downloadMedia', 'type', type, MEDIA_DOWNLOAD_TYPES);
|
|
1005
|
-
return downloadMediaMessage(message, type, options, {
|
|
983
|
+
return ctx.withClient(client => downloadMediaMessage(message, type, options, {
|
|
1006
984
|
logger,
|
|
1007
985
|
reuploadRequest: (m) => sock.updateMediaMessage(m),
|
|
1008
|
-
waClient:
|
|
1009
|
-
});
|
|
986
|
+
waClient: client
|
|
987
|
+
}));
|
|
1010
988
|
}
|
|
1011
989
|
};
|
|
1012
990
|
// Assigning replaces the handler the socket itself reports through, rather
|
package/lib/Socket/internals.js
CHANGED
|
@@ -65,7 +65,7 @@ export const makeInternalMethods = (ctx) => {
|
|
|
65
65
|
* that wants the next attempt waits again.
|
|
66
66
|
*/
|
|
67
67
|
waitForSocketOpen: async () => {
|
|
68
|
-
await
|
|
68
|
+
await ctx.withClient(client => client.waitForSocket(TRANSPORT_CONNECT_TIMEOUT_MS));
|
|
69
69
|
},
|
|
70
70
|
/**
|
|
71
71
|
* Publishes a message onto the event bus, which is this layer's own job.
|