zapo-js 1.4.0 → 1.5.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.
Files changed (94) hide show
  1. package/dist/appstate/types.d.ts +1 -2
  2. package/dist/auth/WaAuthClient.d.ts +10 -0
  3. package/dist/auth/WaAuthClient.js +17 -8
  4. package/dist/auth/credentials-flow.d.ts +8 -0
  5. package/dist/auth/credentials-flow.js +35 -2
  6. package/dist/auth/pairing/companion-host.d.ts +105 -0
  7. package/dist/auth/pairing/companion-host.js +148 -0
  8. package/dist/auth/types.d.ts +20 -9
  9. package/dist/client/WaClient.d.ts +15 -5
  10. package/dist/client/WaClient.js +46 -12
  11. package/dist/client/WaClientFactory.d.ts +3 -0
  12. package/dist/client/WaClientFactory.js +24 -6
  13. package/dist/client/coordinators/WaAppStateMutationCoordinator.d.ts +8 -0
  14. package/dist/client/coordinators/WaAppStateMutationCoordinator.js +37 -1
  15. package/dist/client/coordinators/WaGroupCoordinator.js +6 -1
  16. package/dist/client/coordinators/WaMessageDispatchCoordinator.d.ts +3 -1
  17. package/dist/client/coordinators/WaMessageDispatchCoordinator.js +13 -3
  18. package/dist/client/coordinators/WaMobileCoordinator.d.ts +192 -0
  19. package/dist/client/coordinators/WaMobileCoordinator.js +645 -0
  20. package/dist/client/messaging/companion-host.d.ts +43 -0
  21. package/dist/client/messaging/companion-host.js +61 -0
  22. package/dist/client/messaging/key-protocol.js +25 -1
  23. package/dist/client/persistence/companion-host.d.ts +36 -0
  24. package/dist/client/persistence/companion-host.js +57 -0
  25. package/dist/client/plugins/install.d.ts +2 -1
  26. package/dist/client/plugins/install.js +44 -11
  27. package/dist/client/types.d.ts +71 -7
  28. package/dist/esm/auth/WaAuthClient.js +18 -9
  29. package/dist/esm/auth/credentials-flow.js +36 -3
  30. package/dist/esm/auth/pairing/companion-host.js +140 -0
  31. package/dist/esm/client/WaClient.js +46 -12
  32. package/dist/esm/client/WaClientFactory.js +25 -7
  33. package/dist/esm/client/coordinators/WaAppStateMutationCoordinator.js +37 -1
  34. package/dist/esm/client/coordinators/WaGroupCoordinator.js +6 -1
  35. package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +14 -4
  36. package/dist/esm/client/coordinators/WaMobileCoordinator.js +641 -0
  37. package/dist/esm/client/messaging/companion-host.js +56 -0
  38. package/dist/esm/client/messaging/key-protocol.js +25 -1
  39. package/dist/esm/client/persistence/companion-host.js +54 -0
  40. package/dist/esm/client/plugins/install.js +44 -11
  41. package/dist/esm/index.js +5 -2
  42. package/dist/esm/message/WaMessageClient.js +3 -2
  43. package/dist/esm/message/context-info.js +4 -0
  44. package/dist/esm/protocol/constants.js +4 -3
  45. package/dist/esm/protocol/device-identity.js +18 -0
  46. package/dist/esm/protocol/group.js +6 -0
  47. package/dist/esm/protocol/message.js +12 -0
  48. package/dist/esm/protocol/nodes.js +15 -1
  49. package/dist/esm/retry/replay.js +3 -3
  50. package/dist/esm/signal/attestation/WaAdvSignature.js +67 -2
  51. package/dist/esm/signal/attestation/constants.js +2 -0
  52. package/dist/esm/signal/index.js +1 -0
  53. package/dist/esm/transport/index.js +1 -1
  54. package/dist/esm/transport/node/builders/group.js +4 -3
  55. package/dist/esm/transport/node/builders/mobile.js +113 -0
  56. package/dist/esm/transport/noise/WaClientPayload.js +16 -7
  57. package/dist/esm/transport/wa-version-fetcher.js +152 -0
  58. package/dist/esm/util/index.js +1 -0
  59. package/dist/index.d.ts +10 -4
  60. package/dist/index.js +13 -4
  61. package/dist/message/WaMessageClient.js +2 -1
  62. package/dist/message/context-info.d.ts +2 -0
  63. package/dist/message/context-info.js +5 -0
  64. package/dist/protocol/constants.d.ts +6 -4
  65. package/dist/protocol/constants.js +8 -2
  66. package/dist/protocol/device-identity.d.ts +25 -0
  67. package/dist/protocol/device-identity.js +21 -0
  68. package/dist/protocol/group.d.ts +6 -0
  69. package/dist/protocol/group.js +7 -1
  70. package/dist/protocol/message.d.ts +14 -0
  71. package/dist/protocol/message.js +13 -1
  72. package/dist/protocol/nodes.d.ts +14 -0
  73. package/dist/protocol/nodes.js +15 -1
  74. package/dist/retry/replay.js +2 -2
  75. package/dist/signal/attestation/WaAdvSignature.d.ts +50 -1
  76. package/dist/signal/attestation/WaAdvSignature.js +71 -1
  77. package/dist/signal/attestation/constants.d.ts +2 -0
  78. package/dist/signal/attestation/constants.js +3 -1
  79. package/dist/signal/index.d.ts +1 -0
  80. package/dist/signal/index.js +13 -1
  81. package/dist/transport/index.d.ts +2 -2
  82. package/dist/transport/index.js +4 -3
  83. package/dist/transport/node/builders/group.js +4 -3
  84. package/dist/transport/node/builders/mobile.d.ts +68 -0
  85. package/dist/transport/node/builders/mobile.js +120 -0
  86. package/dist/transport/noise/WaClientPayload.js +16 -7
  87. package/dist/transport/wa-version-fetcher.d.ts +92 -0
  88. package/dist/transport/wa-version-fetcher.js +156 -0
  89. package/dist/util/index.d.ts +1 -0
  90. package/dist/util/index.js +3 -1
  91. package/package.json +1 -1
  92. package/dist/esm/transport/wa-web-version-fetcher.js +0 -91
  93. package/dist/transport/wa-web-version-fetcher.d.ts +0 -44
  94. package/dist/transport/wa-web-version-fetcher.js +0 -94
@@ -14,6 +14,7 @@ import { WaIncomingNodeCoordinator } from './coordinators/WaIncomingNodeCoordina
14
14
  import { type WaLowLevelCoordinator } from './coordinators/WaLowLevelCoordinator';
15
15
  import { WaMessageCoordinator } from './coordinators/WaMessageCoordinator';
16
16
  import { WaMessageDispatchCoordinator } from './coordinators/WaMessageDispatchCoordinator';
17
+ import { WaMobileCoordinator } from './coordinators/WaMobileCoordinator';
17
18
  import { type WaNewsletterCoordinator } from './coordinators/WaNewsletterCoordinator';
18
19
  import { WaPassiveTasksCoordinator } from './coordinators/WaPassiveTasksCoordinator';
19
20
  import { type WaPresenceCoordinator } from './coordinators/WaPresenceCoordinator';
@@ -119,6 +120,8 @@ export interface WaClientDependencies {
119
120
  readonly trustedContactToken: WaTrustedContactTokenCoordinator;
120
121
  readonly abPropsCoordinator: WaAbPropsCoordinator;
121
122
  readonly peerDataOperation: PeerDataOperationRequester;
123
+ readonly mobileCoordinator: WaMobileCoordinator;
124
+ readonly isMobilePrimary: () => boolean;
122
125
  }
123
126
  export declare function resolveWaClientBase(options: WaClientOptions, logger: Logger): WaClientBase;
124
127
  export declare function buildWaClientDependencies(input: {
@@ -17,6 +17,7 @@ const WaIncomingNodeCoordinator_1 = require("./coordinators/WaIncomingNodeCoordi
17
17
  const WaLowLevelCoordinator_1 = require("./coordinators/WaLowLevelCoordinator");
18
18
  const WaMessageCoordinator_1 = require("./coordinators/WaMessageCoordinator");
19
19
  const WaMessageDispatchCoordinator_1 = require("./coordinators/WaMessageDispatchCoordinator");
20
+ const WaMobileCoordinator_1 = require("./coordinators/WaMobileCoordinator");
20
21
  const WaNewsletterCoordinator_1 = require("./coordinators/WaNewsletterCoordinator");
21
22
  const WaOfflineResumeCoordinator_1 = require("./coordinators/WaOfflineResumeCoordinator");
22
23
  const WaPassiveTasksCoordinator_1 = require("./coordinators/WaPassiveTasksCoordinator");
@@ -65,7 +66,6 @@ const WaNodeTransport_1 = require("../transport/node/WaNodeTransport");
65
66
  const proxy_1 = require("../transport/proxy");
66
67
  const clock_1 = require("../util/clock");
67
68
  const primitives_1 = require("../util/primitives");
68
- const runtime_1 = require("../util/runtime");
69
69
  function assertProxyTransport(value, path) {
70
70
  if (value === undefined) {
71
71
  return;
@@ -89,7 +89,7 @@ function validateProxyOptions(options) {
89
89
  }
90
90
  function resolveWaClientBase(options, logger) {
91
91
  validateProxyOptions(options);
92
- const deviceBrowser = options.deviceBrowser ?? constants_1.WA_DEFAULTS.DEVICE_BROWSER;
92
+ const device = (0, constants_1.resolveWaDeviceIdentity)(options);
93
93
  const sessionId = options.sessionId.trim();
94
94
  if (sessionId.length === 0) {
95
95
  throw new Error('sessionId must be a non-empty string');
@@ -98,9 +98,9 @@ function resolveWaClientBase(options, logger) {
98
98
  const normalizedOptions = Object.freeze({
99
99
  ...options,
100
100
  sessionId,
101
- deviceBrowser,
102
- deviceOsDisplayName: options.deviceOsDisplayName ?? (0, runtime_1.getRuntimeOsDisplayName)(),
103
- devicePlatform: options.devicePlatform ?? (0, constants_1.getWaCompanionPlatformId)(deviceBrowser),
101
+ deviceBrowser: device.browser,
102
+ deviceOsDisplayName: device.osDisplayName,
103
+ devicePlatform: device.platform,
104
104
  urls: options.urls ?? options.chatSocketUrls ?? constants_1.WA_DEFAULTS.CHAT_SOCKET_URLS,
105
105
  iqTimeoutMs: options.iqTimeoutMs ?? constants_1.WA_DEFAULTS.IQ_TIMEOUT_MS,
106
106
  nodeQueryTimeoutMs: options.nodeQueryTimeoutMs ?? constants_1.WA_DEFAULTS.NODE_QUERY_TIMEOUT_MS,
@@ -455,6 +455,7 @@ function buildWaClientDependencies(input) {
455
455
  });
456
456
  messageDispatch = new WaMessageDispatchCoordinator_1.WaMessageDispatchCoordinator({
457
457
  logger,
458
+ emitMessageSend: (event) => runtime.emitEvent('message_send', event),
458
459
  messageClient,
459
460
  retryTracker,
460
461
  sessionResolver,
@@ -591,6 +592,7 @@ function buildWaClientDependencies(input) {
591
592
  serverClock,
592
593
  emitSnapshotMutations: options.chatEvents?.emitSnapshotMutations === true,
593
594
  emitMutation: (event) => runtime.emitEvent('mutation', event),
595
+ emitMutationSend: (event) => runtime.emitEvent('mutation_send', event),
594
596
  nctSaltSink: (salt) => trustedContactToken.handleNctSaltSync(salt),
595
597
  contactSink: runtime.persistContact,
596
598
  pushNameSink: (name) => {
@@ -1017,6 +1019,20 @@ function buildWaClientDependencies(input) {
1017
1019
  isConnected: () => connectionManager?.isConnected() ?? false,
1018
1020
  defaultIqTimeoutMs: options.iqTimeoutMs
1019
1021
  });
1022
+ const mobileCoordinator = new WaMobileCoordinator_1.WaMobileCoordinator({
1023
+ logger,
1024
+ authClient,
1025
+ messageDispatch,
1026
+ chatCoordinator: appStateMutations,
1027
+ deviceSync: signalDeviceSync,
1028
+ appStateStore: sessionStore.appState,
1029
+ queryWithContext: runtime.queryWithContext,
1030
+ emitEvent: runtime.emitEvent,
1031
+ registerIncomingHandler: (registration) => incomingNode.registerIncomingHandler(registration),
1032
+ isMobilePrimary,
1033
+ persistence: options.companionHost?.persistence,
1034
+ includePem: options.companionHost?.includePem
1035
+ });
1020
1036
  return {
1021
1037
  nodeTransport,
1022
1038
  nodeOrchestrator,
@@ -1057,6 +1073,8 @@ function buildWaClientDependencies(input) {
1057
1073
  connectionManager,
1058
1074
  trustedContactToken,
1059
1075
  abPropsCoordinator,
1060
- peerDataOperation
1076
+ peerDataOperation,
1077
+ mobileCoordinator,
1078
+ isMobilePrimary
1061
1079
  };
1062
1080
  }
@@ -33,6 +33,7 @@ interface WaAppStateMutationCoordinatorOptions {
33
33
  readonly serverClock: ServerClock;
34
34
  readonly archiveRangeLimit?: number;
35
35
  readonly emitMutation?: (event: WaAppStateMutationEvent) => void;
36
+ readonly emitMutationSend?: (event: WaAppStateMutationEvent) => void;
36
37
  readonly emitSnapshotMutations?: boolean;
37
38
  readonly nctSaltSink?: (salt: Uint8Array | null) => Promise<void>;
38
39
  /**
@@ -79,6 +80,7 @@ export declare class WaAppStateMutationCoordinator {
79
80
  private readonly serverClock;
80
81
  private readonly archiveRangeLimit;
81
82
  private readonly emitMutation?;
83
+ private readonly emitMutationSend?;
82
84
  private readonly emitSnapshotMutations;
83
85
  private readonly nctSaltSink?;
84
86
  private readonly contactSink?;
@@ -160,6 +162,12 @@ export declare class WaAppStateMutationCoordinator {
160
162
  */
161
163
  flushMutations(): Promise<void>;
162
164
  private enqueueAndFlush;
165
+ /**
166
+ * Surfaces an action this client is sending via the outbound `mutation_send`
167
+ * event, so consumers can observe their own change at action time. Parse
168
+ * failures are swallowed (best-effort).
169
+ */
170
+ private emitLocalMutation;
163
171
  private enqueueMutation;
164
172
  private takePendingMutationsBatch;
165
173
  private flushPendingMutationsLoop;
@@ -14,6 +14,8 @@ const WA_APP_STATE_MUTATION_FLUSH_SUCCESS_STATES = new Set([
14
14
  constants_1.WA_APP_STATE_COLLECTION_STATES.SUCCESS_HAS_MORE
15
15
  ]);
16
16
  const WA_APP_STATE_ARCHIVE_RANGE_DEFAULT_LIMIT = 256;
17
+ /** Placeholder MACs for locally-emitted mutation events (the parser ignores them). */
18
+ const WA_EMPTY_MUTATION_MAC = new Uint8Array(0);
17
19
  function buildMutationIndexFromSchema(schema, indexArgs) {
18
20
  const parts = new Array(schema.indexParts.length);
19
21
  const args = indexArgs;
@@ -160,7 +162,6 @@ function buildRemoveMutationFromSchema(input) {
160
162
  collection: input.schema.collection,
161
163
  operation: 'remove',
162
164
  index: buildMutationIndexFromSchema(input.schema, input.indexArgs),
163
- previousValue: { timestamp: input.timestamp },
164
165
  version: input.schema.version,
165
166
  timestamp: input.timestamp
166
167
  };
@@ -180,6 +181,7 @@ class WaAppStateMutationCoordinator {
180
181
  this.serverClock = options.serverClock;
181
182
  this.archiveRangeLimit = (0, coercion_1.resolvePositive)(options.archiveRangeLimit, WA_APP_STATE_ARCHIVE_RANGE_DEFAULT_LIMIT, 'WaAppStateMutationCoordinatorOptions.archiveRangeLimit');
182
183
  this.emitMutation = options.emitMutation;
184
+ this.emitMutationSend = options.emitMutationSend;
183
185
  this.emitSnapshotMutations = options.emitSnapshotMutations === true;
184
186
  this.nctSaltSink = options.nctSaltSink;
185
187
  this.contactSink = options.contactSink;
@@ -617,9 +619,43 @@ class WaAppStateMutationCoordinator {
617
619
  async enqueueAndFlush(mutations) {
618
620
  for (const mutation of mutations) {
619
621
  this.enqueueMutation(mutation);
622
+ this.emitLocalMutation(mutation);
620
623
  }
621
624
  await this.flushMutations();
622
625
  }
626
+ /**
627
+ * Surfaces an action this client is sending via the outbound `mutation_send`
628
+ * event, so consumers can observe their own change at action time. Parse
629
+ * failures are swallowed (best-effort).
630
+ */
631
+ emitLocalMutation(input) {
632
+ if (!this.emitMutationSend)
633
+ return;
634
+ const value = input.operation === 'set' ? input.value : null;
635
+ try {
636
+ const event = (0, appstate_mutation_1.parseAppStateMutationEvent)({
637
+ collection: input.collection,
638
+ operation: input.operation,
639
+ source: 'local',
640
+ index: input.index,
641
+ value,
642
+ version: input.version,
643
+ indexMac: WA_EMPTY_MUTATION_MAC,
644
+ valueMac: WA_EMPTY_MUTATION_MAC,
645
+ keyId: WA_EMPTY_MUTATION_MAC,
646
+ timestamp: input.timestamp
647
+ });
648
+ if (event)
649
+ this.emitMutationSend(event);
650
+ }
651
+ catch (error) {
652
+ this.logger.debug('failed to emit local app-state mutation event', {
653
+ collection: input.collection,
654
+ index: input.index,
655
+ message: (0, primitives_1.toError)(error).message
656
+ });
657
+ }
658
+ }
623
659
  enqueueMutation(mutation) {
624
660
  const key = `${mutation.collection}\u0001${mutation.index}`;
625
661
  if (this.pendingMutations.has(key)) {
@@ -5,6 +5,7 @@ const group_1 = require("../events/group");
5
5
  const defaults_1 = require("../../protocol/defaults");
6
6
  const group_2 = require("../../protocol/group");
7
7
  const jid_1 = require("../../protocol/jid");
8
+ const message_1 = require("../../protocol/message");
8
9
  const nodes_1 = require("../../protocol/nodes");
9
10
  const notification_1 = require("../../protocol/notification");
10
11
  const account_sync_1 = require("../../transport/node/builders/account-sync");
@@ -90,7 +91,11 @@ function parseGroupCommons(target) {
90
91
  ? (0, helpers_1.findNodeChild)(membershipApprovalNode, nodes_1.WA_NODE_TAGS.GROUP_JOIN)
91
92
  : undefined;
92
93
  const addressingModeRaw = attrs.addressing_mode;
93
- const addressingMode = addressingModeRaw === 'lid' ? 'lid' : addressingModeRaw === 'pn' ? 'pn' : undefined;
94
+ const addressingMode = addressingModeRaw === message_1.WA_ADDRESSING_MODES.LID
95
+ ? message_1.WA_ADDRESSING_MODES.LID
96
+ : addressingModeRaw === message_1.WA_ADDRESSING_MODES.PN
97
+ ? message_1.WA_ADDRESSING_MODES.PN
98
+ : undefined;
94
99
  const rawJid = attrs.id ?? attrs.jid ?? '';
95
100
  const jid = rawJid && !rawJid.includes('@') ? `${rawJid}@${defaults_1.WA_DEFAULTS.GROUP_SERVER}` : rawJid;
96
101
  return {
@@ -3,7 +3,7 @@ import type { WaAuthCredentials } from '../../auth/types';
3
3
  import type { DeviceFanoutResolver } from '../messaging/fanout';
4
4
  import type { GroupMetadataCache } from '../messaging/group-metadata';
5
5
  import type { AppStateSyncKeyProtocol } from '../messaging/key-protocol';
6
- import type { WaGroupEvent, WaSendMessageOptions, WaSignalMessagePublishInput } from '../types';
6
+ import type { WaGroupEvent, WaOutgoingMessageEvent, WaSendMessageOptions, WaSignalMessagePublishInput } from '../types';
7
7
  import type { Logger } from '../../infra/log/types';
8
8
  import { type WaSendContextInfo } from '../../message/context-info';
9
9
  import { type IcdcMeta } from '../../message/crypto/icdc';
@@ -26,6 +26,8 @@ import type { BinaryNode } from '../../transport/types';
26
26
  import type { ServerClock } from '../../util/clock';
27
27
  interface WaMessageDispatchCoordinatorOptions {
28
28
  readonly logger: Logger;
29
+ /** Emits the outbound decrypted message (`message_send` event); best-effort. */
30
+ readonly emitMessageSend?: (event: WaOutgoingMessageEvent) => void;
29
31
  readonly messageClient: WaMessageClient;
30
32
  readonly retryTracker: OutboundRetryTracker;
31
33
  readonly sessionResolver: SignalSessionResolver;
@@ -203,6 +203,16 @@ class WaMessageDispatchCoordinator {
203
203
  }
204
204
  : withViewOnce;
205
205
  const upload = built.upload;
206
+ if (this.deps.emitMessageSend) {
207
+ try {
208
+ this.deps.emitMessageSend({ to: recipientJid, id: sendOptions.id, message });
209
+ }
210
+ catch (error) {
211
+ this.deps.logger.debug('message_send emit failed', {
212
+ message: (0, primitives_2.toError)(error).message
213
+ });
214
+ }
215
+ }
206
216
  const messageWithOverride = options.messageSecret
207
217
  ? {
208
218
  ...message,
@@ -894,17 +904,17 @@ class WaMessageDispatchCoordinator {
894
904
  resolveGroupAddressingMode(participantUserJids, groupJid) {
895
905
  for (let index = 0; index < participantUserJids.length; index += 1) {
896
906
  if ((0, jid_1.isLidJid)(participantUserJids[index])) {
897
- return 'lid';
907
+ return constants_1.WA_ADDRESSING_MODES.LID;
898
908
  }
899
909
  }
900
910
  this.deps.logger.trace('group addressing mode resolved to pn (default)', {
901
911
  groupJid,
902
912
  participants: participantUserJids.length
903
913
  });
904
- return 'pn';
914
+ return constants_1.WA_ADDRESSING_MODES.PN;
905
915
  }
906
916
  resolveSenderForAddressingMode(addressingMode, meJid) {
907
- if (addressingMode === 'lid') {
917
+ if (addressingMode === constants_1.WA_ADDRESSING_MODES.LID) {
908
918
  const meLid = this.deps.getCurrentCredentials()?.meLid;
909
919
  if (meLid && meLid.includes('@')) {
910
920
  try {
@@ -0,0 +1,192 @@
1
+ import { type PhoneNumberToLidMapping } from '../messaging/companion-host';
2
+ import type { CompanionHostPersistence, CompanionRecord } from '../persistence/companion-host';
3
+ import type { WaClientPluginContext } from '../plugins/types';
4
+ import type { WaClientEventMap } from '../types';
5
+ import type { WaClientDependencies } from '../WaClientFactory';
6
+ import type { Logger } from '../../infra/log/types';
7
+ type WaMobileEmit = <K extends keyof WaClientEventMap>(event: K, ...args: Parameters<WaClientEventMap[K]>) => void;
8
+ /** Result of a successful {@link WaMobileCoordinator.linkCompanion}. */
9
+ export interface LinkCompanionResult {
10
+ readonly deviceJid: string;
11
+ readonly keyIndex: number;
12
+ }
13
+ /** Dependencies for {@link WaMobileCoordinator}, injected by the client factory. */
14
+ export interface WaMobileCoordinatorDeps {
15
+ readonly logger: Logger;
16
+ readonly authClient: WaClientDependencies['authClient'];
17
+ readonly messageDispatch: WaClientDependencies['messageDispatch'];
18
+ readonly chatCoordinator: WaClientDependencies['chatCoordinator'];
19
+ readonly deviceSync: WaClientDependencies['signalDeviceSync'];
20
+ readonly appStateStore: WaClientPluginContext['stores']['appState'];
21
+ readonly queryWithContext: WaClientPluginContext['queryWithContext'];
22
+ readonly emitEvent: WaMobileEmit;
23
+ readonly registerIncomingHandler: WaClientPluginContext['registerIncomingHandler'];
24
+ readonly isMobilePrimary: () => boolean;
25
+ readonly persistence?: CompanionHostPersistence;
26
+ readonly includePem?: boolean;
27
+ }
28
+ /**
29
+ * Hosts companion devices from a mobile-primary session: signs a companion's
30
+ * device identity, uploads `pair-device`, tracks the linked set, and can revoke
31
+ * a device. Exposed at `client.mobile`; requires a registered primary session.
32
+ * Supports the QR ({@link linkCompanion}) and pairing-code
33
+ * ({@link linkCompanionByCode}) flows.
34
+ */
35
+ export declare class WaMobileCoordinator {
36
+ private readonly authClient;
37
+ private readonly messageDispatch;
38
+ private readonly chatCoordinator;
39
+ private readonly deviceSync;
40
+ private readonly appStateStore;
41
+ private readonly queryWithContext;
42
+ private readonly emitEvent;
43
+ private readonly isMobilePrimary;
44
+ private readonly logger;
45
+ private readonly persistence?;
46
+ private readonly includePem;
47
+ private epoch;
48
+ private pendingCompanionHello;
49
+ private pendingFinish;
50
+ private accountKeyIndexes;
51
+ private pushNameSeeded;
52
+ constructor(deps: WaMobileCoordinatorDeps);
53
+ /**
54
+ * Links the companion from its pairing QR: signs the device identity, uploads
55
+ * `pair-device`, and returns the server-assigned device jid.
56
+ *
57
+ * @throws when there is no primary session or the server rejects the upload
58
+ * (e.g. the linked-device cap is reached).
59
+ */
60
+ linkCompanion(qr: string): Promise<LinkCompanionResult>;
61
+ /**
62
+ * Links a companion via its 8-character pairing code (the "link with phone
63
+ * number" flow). The companion must have requested a code for this account
64
+ * first (its `companion_hello` is recorded); this drives `primary_hello`,
65
+ * awaits `companion_finish`, and completes the same `pair-device` upload as
66
+ * the QR path.
67
+ *
68
+ * @throws when there is no primary session, no companion is pending, the code
69
+ * is wrong, or the handshake times out.
70
+ */
71
+ linkCompanionByCode(pairingCode: string): Promise<LinkCompanionResult>;
72
+ /** Re-signs and republishes the key-index list for the current device set. */
73
+ publishKeyIndexList(): Promise<void>;
74
+ /**
75
+ * Unlinks a companion this primary linked: sends the `remove-companion-device`
76
+ * IQ, drops it from the tracked set, and republishes the key-index list for
77
+ * the reduced device set.
78
+ *
79
+ * @throws when there is no registered primary session, the companion is not
80
+ * tracked, or the server rejects the removal.
81
+ */
82
+ revokeCompanion(companionDeviceJid: string, reason?: string): Promise<void>;
83
+ /**
84
+ * Unlinks EVERY companion from the account in a single `remove-companion-device`
85
+ * `all="true"` stanza (the phone's "log out all companion devices"), clears the
86
+ * tracked set, and republishes the key-index list for the primary alone.
87
+ * `excludeHostedCompanion` spares companions this account itself hosts.
88
+ *
89
+ * @throws when there is no registered primary session or the server rejects
90
+ * the removal.
91
+ */
92
+ revokeAllCompanions(reason?: string, options?: {
93
+ readonly excludeHostedCompanion?: boolean;
94
+ }): Promise<void>;
95
+ /** Returns the companions this primary has linked in the current epoch. */
96
+ listCompanions(): Promise<readonly CompanionRecord[]>;
97
+ /**
98
+ * Reconciles the tracked companion set against the account's live device list
99
+ * on the server (`usync`). Drops companions the server no longer lists - one
100
+ * the user unlinked, or one that self-removed while the primary was offline -
101
+ * then persists and emits `companion_host_revoked` for each. Runs on connect
102
+ * and on `account_sync`; safe to call manually. A no-op (no server query, no
103
+ * epoch created) when no companions are tracked. Returns the removed jids.
104
+ */
105
+ reconcileCompanions(): Promise<readonly string[]>;
106
+ /**
107
+ * Prunes tracked companions to those whose device slot is still in
108
+ * `serverDeviceIndexes`, persisting + emitting `companion_host_revoked` for
109
+ * each drop. Shared by the connect-time (`usync`) and `account_sync` paths.
110
+ */
111
+ private pruneCompanionsToDevices;
112
+ /**
113
+ * Reconciles from an `account_sync` notification's `<devices>` payload - the
114
+ * fresh, authoritative device set - so a companion the user just unlinked is
115
+ * pruned immediately (no `usync` round-trip, which would read a stale cache).
116
+ */
117
+ private reconcileFromAccountSync;
118
+ /**
119
+ * Shares the primary's active app-state sync key with a linked companion via
120
+ * an `APP_STATE_SYNC_KEY_SHARE` peer message so it can decrypt app-state.
121
+ * Establishes the outbound Signal session on demand, so call it once the
122
+ * companion is online (prekeys uploaded), not at link time.
123
+ *
124
+ * @throws when there is no primary session or no active sync key.
125
+ */
126
+ shareAppStateSyncKeys(companionDeviceJid: string): Promise<void>;
127
+ /**
128
+ * Pushes the `INITIAL_BOOTSTRAP` history sync to a linked companion as a
129
+ * `HISTORY_SYNC_NOTIFICATION` peer message. Flips the companion's
130
+ * `initialChatHistory` bootstrap flag; without it the companion self-removes
131
+ * with `HistorySyncTimeout`. Requires the companion online with prekeys.
132
+ *
133
+ * @throws when there is no registered primary session.
134
+ */
135
+ sendHistorySyncBootstrap(companionDeviceJid: string, options?: {
136
+ readonly phoneNumberToLidMappings?: readonly PhoneNumberToLidMapping[];
137
+ }): Promise<void>;
138
+ /**
139
+ * `ClientPairingProps` for the `<client-props>` pair-device element. A
140
+ * LID-native primary declares the chat DB LID-migrated + pure-LID session so
141
+ * the companion runs `setIsLidMigrated` at pair time and does not self-remove
142
+ * on its LID-addressed blocklist.
143
+ */
144
+ private buildClientPairingProps;
145
+ /**
146
+ * Seeds the primary's own `setting_pushName` into the `critical_block`
147
+ * app-state collection, once per session. The companion's critical bootstrap
148
+ * completes only when it finds an applied `SettingPushName` action; without
149
+ * one it self-removes with `syncd_timeout`. Falls back to the account phone
150
+ * number when no push name is set. Idempotent and best-effort.
151
+ */
152
+ private ensureAccountPushNameAppState;
153
+ private completeLink;
154
+ /**
155
+ * Best-effort background provisioning after a companion links: pushes the
156
+ * history-sync bootstrap and shares the app-state sync key. Fire-and-forget
157
+ * and retried, because the companion needs a moment to upload prekeys before
158
+ * the outbound Signal session these peer messages ride on can be established.
159
+ */
160
+ private provisionLinkedCompanion;
161
+ private provisionLinkedCompanionWithRetry;
162
+ private handleNotification;
163
+ /**
164
+ * Tracks the account's live device key-indexes from an `account_sync`
165
+ * notification so a new companion gets a non-colliding key index and the
166
+ * published key-index list keeps every existing device valid.
167
+ */
168
+ private updateAccountKeyIndexes;
169
+ private forgetAccountKeyIndex;
170
+ /**
171
+ * The key indexes currently valid for this account - index 0 (the primary),
172
+ * the account's own live devices, and every tracked companion. Sorted
173
+ * ascending. Deliberately excludes `epoch.currentKeyIndex`, which is an
174
+ * allocation high-water mark, not proof a device still exists: after revoking
175
+ * the last-linked companion its index must drop out of the published list.
176
+ */
177
+ private validKeyIndexes;
178
+ /**
179
+ * Allocates the next companion key index, advancing strictly past both the
180
+ * epoch's monotonic high-water mark and every currently-valid index so a
181
+ * revoked index is never reused (reuse breaks previously linked devices).
182
+ */
183
+ private nextKeyIndex;
184
+ private storeCompanionHello;
185
+ private resolveCompanionFinish;
186
+ private nodeBytes;
187
+ private requirePrimaryIdentityKeyPair;
188
+ private ensureEpoch;
189
+ private generateRawId;
190
+ private persist;
191
+ }
192
+ export {};