@xbibzlibrary/whatsbibz 1.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.
Files changed (260) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/LICENSE +22 -0
  3. package/LICENSE.upstream +21 -0
  4. package/README.md +280 -0
  5. package/VERIFIKASI-IDENTITAS-2026-09-03.md +138 -0
  6. package/WAProto/GenerateStatics.sh +3 -0
  7. package/WAProto/WAProto.proto +6035 -0
  8. package/WAProto/fix-imports.js +81 -0
  9. package/WAProto/index.d.ts +15314 -0
  10. package/WAProto/index.js +132326 -0
  11. package/assets/logo/README.md +21 -0
  12. package/assets/logo/png/banner-1200x360.png +0 -0
  13. package/assets/logo/png/icon-1024.png +0 -0
  14. package/assets/logo/png/icon-128.png +0 -0
  15. package/assets/logo/png/icon-16.png +0 -0
  16. package/assets/logo/png/icon-24.png +0 -0
  17. package/assets/logo/png/icon-256.png +0 -0
  18. package/assets/logo/png/icon-32.png +0 -0
  19. package/assets/logo/png/icon-48.png +0 -0
  20. package/assets/logo/png/icon-512.png +0 -0
  21. package/assets/logo/png/icon-64.png +0 -0
  22. package/assets/logo/png/mark-512.png +0 -0
  23. package/assets/logo/preview.png +0 -0
  24. package/assets/logo/whatsbibz.ico +0 -0
  25. package/lib/BibzWhats/client.d.ts +95 -0
  26. package/lib/BibzWhats/client.js +501 -0
  27. package/lib/BibzWhats/device-identity.d.ts +60 -0
  28. package/lib/BibzWhats/device-identity.js +234 -0
  29. package/lib/BibzWhats/extract.d.ts +29 -0
  30. package/lib/BibzWhats/extract.js +115 -0
  31. package/lib/BibzWhats/index.d.ts +7 -0
  32. package/lib/BibzWhats/index.js +8 -0
  33. package/lib/BibzWhats/jid.d.ts +22 -0
  34. package/lib/BibzWhats/jid.js +98 -0
  35. package/lib/BibzWhats/pairing.d.ts +35 -0
  36. package/lib/BibzWhats/pairing.js +186 -0
  37. package/lib/BibzWhats/send.d.ts +9 -0
  38. package/lib/BibzWhats/send.js +107 -0
  39. package/lib/BibzWhats/version.d.ts +6 -0
  40. package/lib/BibzWhats/version.js +7 -0
  41. package/lib/Defaults/index.d.ts +73 -0
  42. package/lib/Defaults/index.js +141 -0
  43. package/lib/Modded/message_builder.d.ts +15 -0
  44. package/lib/Modded/message_builder.js +2330 -0
  45. package/lib/Signal/Group/ciphertext-message.d.ts +9 -0
  46. package/lib/Signal/Group/ciphertext-message.js +11 -0
  47. package/lib/Signal/Group/group-session-builder.d.ts +14 -0
  48. package/lib/Signal/Group/group-session-builder.js +29 -0
  49. package/lib/Signal/Group/group_cipher.d.ts +16 -0
  50. package/lib/Signal/Group/group_cipher.js +81 -0
  51. package/lib/Signal/Group/index.d.ts +11 -0
  52. package/lib/Signal/Group/index.js +11 -0
  53. package/lib/Signal/Group/keyhelper.d.ts +10 -0
  54. package/lib/Signal/Group/keyhelper.js +17 -0
  55. package/lib/Signal/Group/sender-chain-key.d.ts +13 -0
  56. package/lib/Signal/Group/sender-chain-key.js +25 -0
  57. package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -0
  58. package/lib/Signal/Group/sender-key-distribution-message.js +62 -0
  59. package/lib/Signal/Group/sender-key-message.d.ts +18 -0
  60. package/lib/Signal/Group/sender-key-message.js +65 -0
  61. package/lib/Signal/Group/sender-key-name.d.ts +17 -0
  62. package/lib/Signal/Group/sender-key-name.js +47 -0
  63. package/lib/Signal/Group/sender-key-record.d.ts +30 -0
  64. package/lib/Signal/Group/sender-key-record.js +40 -0
  65. package/lib/Signal/Group/sender-key-state.d.ts +38 -0
  66. package/lib/Signal/Group/sender-key-state.js +83 -0
  67. package/lib/Signal/Group/sender-message-key.d.ts +11 -0
  68. package/lib/Signal/Group/sender-message-key.js +25 -0
  69. package/lib/Signal/libsignal.d.ts +4 -0
  70. package/lib/Signal/libsignal.js +406 -0
  71. package/lib/Signal/lid-mapping.d.ts +18 -0
  72. package/lib/Signal/lid-mapping.js +276 -0
  73. package/lib/Socket/Client/index.d.ts +2 -0
  74. package/lib/Socket/Client/index.js +2 -0
  75. package/lib/Socket/Client/types.d.ts +15 -0
  76. package/lib/Socket/Client/types.js +10 -0
  77. package/lib/Socket/Client/websocket.d.ts +12 -0
  78. package/lib/Socket/Client/websocket.js +53 -0
  79. package/lib/Socket/business.d.ts +537 -0
  80. package/lib/Socket/business.js +421 -0
  81. package/lib/Socket/chats.d.ts +110 -0
  82. package/lib/Socket/chats.js +1006 -0
  83. package/lib/Socket/communities.d.ts +646 -0
  84. package/lib/Socket/communities.js +430 -0
  85. package/lib/Socket/engine.d.ts +129 -0
  86. package/lib/Socket/engine.js +767 -0
  87. package/lib/Socket/groups.d.ts +149 -0
  88. package/lib/Socket/groups.js +326 -0
  89. package/lib/Socket/index.d.ts +637 -0
  90. package/lib/Socket/index.js +14 -0
  91. package/lib/Socket/messages-recv.d.ts +239 -0
  92. package/lib/Socket/messages-recv.js +1307 -0
  93. package/lib/Socket/messages-send.d.ts +580 -0
  94. package/lib/Socket/messages-send.js +1639 -0
  95. package/lib/Socket/mex.d.ts +2 -0
  96. package/lib/Socket/mex.js +47 -0
  97. package/lib/Socket/newsletter.d.ts +176 -0
  98. package/lib/Socket/newsletter.js +456 -0
  99. package/lib/Socket/socket.d.ts +56 -0
  100. package/lib/Socket/socket.js +998 -0
  101. package/lib/Types/Auth.d.ts +115 -0
  102. package/lib/Types/Auth.js +1 -0
  103. package/lib/Types/Bussines.d.ts +36 -0
  104. package/lib/Types/Bussines.js +1 -0
  105. package/lib/Types/Call.d.ts +14 -0
  106. package/lib/Types/Call.js +1 -0
  107. package/lib/Types/Chat.d.ts +124 -0
  108. package/lib/Types/Chat.js +7 -0
  109. package/lib/Types/Contact.d.ts +23 -0
  110. package/lib/Types/Contact.js +1 -0
  111. package/lib/Types/Events.d.ts +241 -0
  112. package/lib/Types/Events.js +1 -0
  113. package/lib/Types/GroupMetadata.d.ts +67 -0
  114. package/lib/Types/GroupMetadata.js +1 -0
  115. package/lib/Types/Label.d.ts +46 -0
  116. package/lib/Types/Label.js +24 -0
  117. package/lib/Types/LabelAssociation.d.ts +29 -0
  118. package/lib/Types/LabelAssociation.js +6 -0
  119. package/lib/Types/Message.d.ts +427 -0
  120. package/lib/Types/Message.js +10 -0
  121. package/lib/Types/Newsletter.d.ts +134 -0
  122. package/lib/Types/Newsletter.js +30 -0
  123. package/lib/Types/Product.d.ts +78 -0
  124. package/lib/Types/Product.js +1 -0
  125. package/lib/Types/Signal.d.ts +77 -0
  126. package/lib/Types/Signal.js +1 -0
  127. package/lib/Types/Socket.d.ts +173 -0
  128. package/lib/Types/Socket.js +2 -0
  129. package/lib/Types/State.d.ts +38 -0
  130. package/lib/Types/State.js +12 -0
  131. package/lib/Types/USync.d.ts +25 -0
  132. package/lib/Types/USync.js +1 -0
  133. package/lib/Types/index.d.ts +75 -0
  134. package/lib/Types/index.js +32 -0
  135. package/lib/Utils/auth-utils.d.ts +18 -0
  136. package/lib/Utils/auth-utils.js +289 -0
  137. package/lib/Utils/browser-utils.d.ts +3 -0
  138. package/lib/Utils/browser-utils.js +47 -0
  139. package/lib/Utils/business.d.ts +22 -0
  140. package/lib/Utils/business.js +230 -0
  141. package/lib/Utils/chat-utils.d.ts +69 -0
  142. package/lib/Utils/chat-utils.js +811 -0
  143. package/lib/Utils/companion-reg-client-utils.d.ts +20 -0
  144. package/lib/Utils/companion-reg-client-utils.js +66 -0
  145. package/lib/Utils/crypto.d.ts +41 -0
  146. package/lib/Utils/crypto.js +117 -0
  147. package/lib/Utils/decode-wa-message.d.ts +47 -0
  148. package/lib/Utils/decode-wa-message.js +282 -0
  149. package/lib/Utils/event-buffer.d.ts +35 -0
  150. package/lib/Utils/event-buffer.js +590 -0
  151. package/lib/Utils/generics.d.ts +90 -0
  152. package/lib/Utils/generics.js +384 -0
  153. package/lib/Utils/history.d.ts +21 -0
  154. package/lib/Utils/history.js +130 -0
  155. package/lib/Utils/identity-change-handler.d.ts +36 -0
  156. package/lib/Utils/identity-change-handler.js +48 -0
  157. package/lib/Utils/index.d.ts +25 -0
  158. package/lib/Utils/index.js +24 -0
  159. package/lib/Utils/link-preview.d.ts +20 -0
  160. package/lib/Utils/link-preview.js +84 -0
  161. package/lib/Utils/logger.d.ts +12 -0
  162. package/lib/Utils/logger.js +2 -0
  163. package/lib/Utils/lt-hash.d.ts +10 -0
  164. package/lib/Utils/lt-hash.js +7 -0
  165. package/lib/Utils/make-mutex.d.ts +8 -0
  166. package/lib/Utils/make-mutex.js +32 -0
  167. package/lib/Utils/message-retry-manager.d.ts +109 -0
  168. package/lib/Utils/message-retry-manager.js +224 -0
  169. package/lib/Utils/messages-media.d.ts +129 -0
  170. package/lib/Utils/messages-media.js +784 -0
  171. package/lib/Utils/messages.d.ts +90 -0
  172. package/lib/Utils/messages.js +1240 -0
  173. package/lib/Utils/noise-handler.d.ts +19 -0
  174. package/lib/Utils/noise-handler.js +200 -0
  175. package/lib/Utils/offline-node-processor.d.ts +16 -0
  176. package/lib/Utils/offline-node-processor.js +39 -0
  177. package/lib/Utils/platform-identity.d.ts +14 -0
  178. package/lib/Utils/platform-identity.js +148 -0
  179. package/lib/Utils/pre-key-manager.d.ts +27 -0
  180. package/lib/Utils/pre-key-manager.js +105 -0
  181. package/lib/Utils/process-message.d.ts +59 -0
  182. package/lib/Utils/process-message.js +544 -0
  183. package/lib/Utils/reporting-utils.d.ts +10 -0
  184. package/lib/Utils/reporting-utils.js +257 -0
  185. package/lib/Utils/rich-messages.d.ts +276 -0
  186. package/lib/Utils/rich-messages.js +1143 -0
  187. package/lib/Utils/signal.d.ts +33 -0
  188. package/lib/Utils/signal.js +158 -0
  189. package/lib/Utils/stanza-ack.d.ts +10 -0
  190. package/lib/Utils/stanza-ack.js +37 -0
  191. package/lib/Utils/sticker-pack.d.ts +10 -0
  192. package/lib/Utils/sticker-pack.js +111 -0
  193. package/lib/Utils/sync-action-utils.d.ts +18 -0
  194. package/lib/Utils/sync-action-utils.js +47 -0
  195. package/lib/Utils/tc-token-utils.d.ts +8 -0
  196. package/lib/Utils/tc-token-utils.js +45 -0
  197. package/lib/Utils/use-multi-file-auth-state.d.ts +12 -0
  198. package/lib/Utils/use-multi-file-auth-state.js +120 -0
  199. package/lib/Utils/validate-connection.d.ts +10 -0
  200. package/lib/Utils/validate-connection.js +222 -0
  201. package/lib/VoIP/audio-feeder.d.ts +15 -0
  202. package/lib/VoIP/audio-feeder.js +136 -0
  203. package/lib/VoIP/index.d.ts +12 -0
  204. package/lib/VoIP/index.js +282 -0
  205. package/lib/VoIP/relay-transport.d.ts +43 -0
  206. package/lib/VoIP/relay-transport.js +559 -0
  207. package/lib/VoIP/signaling.js +603 -0
  208. package/lib/VoIP/types.d.ts +69 -0
  209. package/lib/VoIP/types.js +17 -0
  210. package/lib/VoIP/wasm-engine.d.ts +103 -0
  211. package/lib/VoIP/wasm-engine.js +1214 -0
  212. package/lib/VoIP/worker-bootstrap.js +1042 -0
  213. package/lib/WABinary/constants.d.ts +27 -0
  214. package/lib/WABinary/constants.js +1300 -0
  215. package/lib/WABinary/decode.d.ts +6 -0
  216. package/lib/WABinary/decode.js +261 -0
  217. package/lib/WABinary/encode.d.ts +2 -0
  218. package/lib/WABinary/encode.js +219 -0
  219. package/lib/WABinary/generic-utils.d.ts +16 -0
  220. package/lib/WABinary/generic-utils.js +190 -0
  221. package/lib/WABinary/index.d.ts +5 -0
  222. package/lib/WABinary/index.js +5 -0
  223. package/lib/WABinary/jid-utils.d.ts +47 -0
  224. package/lib/WABinary/jid-utils.js +95 -0
  225. package/lib/WABinary/types.d.ts +18 -0
  226. package/lib/WABinary/types.js +1 -0
  227. package/lib/WAM/BinaryInfo.d.ts +8 -0
  228. package/lib/WAM/BinaryInfo.js +9 -0
  229. package/lib/WAM/constants.d.ts +39 -0
  230. package/lib/WAM/constants.js +22852 -0
  231. package/lib/WAM/encode.d.ts +2 -0
  232. package/lib/WAM/encode.js +149 -0
  233. package/lib/WAM/index.d.ts +3 -0
  234. package/lib/WAM/index.js +3 -0
  235. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -0
  236. package/lib/WAUSync/Protocols/USyncContactProtocol.js +28 -0
  237. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -0
  238. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -0
  239. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -0
  240. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -0
  241. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -0
  242. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -0
  243. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +25 -0
  244. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -0
  245. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +9 -0
  246. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -0
  247. package/lib/WAUSync/Protocols/index.d.ts +4 -0
  248. package/lib/WAUSync/Protocols/index.js +4 -0
  249. package/lib/WAUSync/USyncQuery.d.ts +28 -0
  250. package/lib/WAUSync/USyncQuery.js +93 -0
  251. package/lib/WAUSync/USyncUser.d.ts +12 -0
  252. package/lib/WAUSync/USyncUser.js +22 -0
  253. package/lib/WAUSync/index.d.ts +3 -0
  254. package/lib/WAUSync/index.js +3 -0
  255. package/lib/assets/wasm/loader.js +5 -0
  256. package/lib/assets/wasm/whatsapp.wasm +0 -0
  257. package/lib/assets/wasm/worker-modules.js +273 -0
  258. package/lib/index.d.ts +21 -0
  259. package/lib/index.js +22 -0
  260. package/package.json +111 -0
@@ -0,0 +1,222 @@
1
+ import { Boom } from '@hapi/boom';
2
+ import { createHash } from 'crypto';
3
+ import { proto } from '../../WAProto/index.js';
4
+ import { KEY_BUNDLE_TYPE, WA_ADV_ACCOUNT_SIG_PREFIX, WA_ADV_DEVICE_SIG_PREFIX, WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX } from '../Defaults/index.js';
5
+ import { getBinaryNodeChild, jidDecode, S_WHATSAPP_NET } from '../WABinary/index.js';
6
+ import { Curve, hmacSign } from './crypto.js';
7
+ import { encodeBigEndian } from './generics.js';
8
+ import { createSignalIdentity } from './signal.js';
9
+ const getUserAgent = (config) => {
10
+ return {
11
+ appVersion: {
12
+ primary: config.version[0],
13
+ secondary: config.version[1],
14
+ tertiary: config.version[2]
15
+ },
16
+ platform: config.browser[1].toLocaleLowerCase().includes('android')
17
+ ? proto.ClientPayload.UserAgent.Platform.ANDROID
18
+ : proto.ClientPayload.UserAgent.Platform.WEB,
19
+ releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
20
+ osVersion: '0.1',
21
+ device: 'Desktop',
22
+ osBuildNumber: '0.1',
23
+ localeLanguageIso6391: 'en',
24
+ mnc: '000',
25
+ mcc: '000',
26
+ localeCountryIso31661Alpha2: config.countryCode
27
+ };
28
+ };
29
+ const PLATFORM_MAP = {
30
+ // BibzWhats: DARWIN (Mac OS Desktop lama) DITUTUP server — handshake diputus
31
+ // 428 sebelum QR (diukur 2026-09-03; upstream masih mengirim DARWIN dan gagal).
32
+ // WEB_BROWSER diterima untuk 'Mac OS'+'Desktop'+syncFullHistory dan riwayat
33
+ // penuh tetap diminta lewat DeviceProps.requireFullSync.
34
+ 'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.WEB_BROWSER,
35
+ // Sejak ~2026-06-30 server menolak WIN32 (Electron lama) → WIN_HYBRID (5).
36
+ // Upstream #2741. Diverifikasi ulang 2026-09-03: QR OK.
37
+ Windows: proto.ClientPayload.WebInfo.WebSubPlatform.WIN_HYBRID
38
+ };
39
+ const getWebInfo = (config) => {
40
+ let webSubPlatform = proto.ClientPayload.WebInfo.WebSubPlatform.WEB_BROWSER;
41
+ if (config.syncFullHistory &&
42
+ PLATFORM_MAP[config.browser[0]] &&
43
+ config.browser[1] === 'Desktop') {
44
+ webSubPlatform = PLATFORM_MAP[config.browser[0]];
45
+ }
46
+ // BibzWhats: override eksplisit (angka enum atau nama, mis. 'WEB_BROWSER', 'WIN_HYBRID')
47
+ if (config.webSubPlatform !== undefined && config.webSubPlatform !== null) {
48
+ const v = typeof config.webSubPlatform === 'string'
49
+ ? proto.ClientPayload.WebInfo.WebSubPlatform[config.webSubPlatform.toUpperCase()]
50
+ : config.webSubPlatform;
51
+ if (typeof v === 'number') webSubPlatform = v;
52
+ }
53
+ return { webSubPlatform };
54
+ };
55
+ const getClientPayload = (config) => {
56
+ const payload = {
57
+ connectType: proto.ClientPayload.ConnectType.WIFI_UNKNOWN,
58
+ connectReason: proto.ClientPayload.ConnectReason.USER_ACTIVATED,
59
+ userAgent: getUserAgent(config)
60
+ };
61
+ if (!config.browser[1].toLocaleLowerCase().includes('android')) {
62
+ payload.webInfo = getWebInfo(config);
63
+ }
64
+ if (config.pushName) {
65
+ payload.pushName = config.pushName;
66
+ }
67
+ return payload;
68
+ };
69
+ export const generateLoginNode = (userJid, config) => {
70
+ const { user, device } = jidDecode(userJid);
71
+ const payload = {
72
+ ...getClientPayload(config),
73
+ passive: true,
74
+ pull: true,
75
+ username: +user,
76
+ device: device,
77
+ // TODO: investigate (hard set as false atm)
78
+ lidDbMigrated: false
79
+ };
80
+ return proto.ClientPayload.fromObject(payload);
81
+ };
82
+ const getPlatformType = (platform) => {
83
+ const platformType = platform.toUpperCase();
84
+ if (platformType === 'ANDROID') {
85
+ return proto.DeviceProps.PlatformType.ANDROID_PHONE;
86
+ }
87
+ return (proto.DeviceProps.PlatformType[platformType] ||
88
+ proto.DeviceProps.PlatformType.CHROME);
89
+ };
90
+ export const generateRegistrationNode = ({ registrationId, signedPreKey, signedIdentityKey }, config) => {
91
+ // the app version needs to be md5 hashed
92
+ // and passed in
93
+ const appVersionBuf = createHash('md5')
94
+ .update(config.version.join('.')) // join as string
95
+ .digest();
96
+ const companion = {
97
+ os: config.browser[0],
98
+ platformType: getPlatformType(config.browser[1]),
99
+ requireFullSync: config.syncFullHistory,
100
+ historySyncConfig: {
101
+ storageQuotaMb: 10240,
102
+ inlineInitialPayloadInE2EeMsg: true,
103
+ recentSyncDaysLimit: undefined,
104
+ supportCallLogHistory: false,
105
+ supportBotUserAgentChatHistory: true,
106
+ supportCagReactionsAndPolls: true,
107
+ supportBizHostedMsg: true,
108
+ supportRecentSyncChunkMessageCountTuning: true,
109
+ supportHostedGroupMsg: true,
110
+ supportFbidBotChatHistory: true,
111
+ supportAddOnHistorySyncMigration: undefined,
112
+ supportMessageAssociation: true,
113
+ supportGroupHistory: false,
114
+ onDemandReady: undefined,
115
+ supportGuestChat: undefined
116
+ },
117
+ version: {
118
+ primary: 10,
119
+ secondary: 15,
120
+ tertiary: 7
121
+ }
122
+ };
123
+ const companionProto = proto.DeviceProps.encode(companion).finish();
124
+ const registerPayload = {
125
+ ...getClientPayload(config),
126
+ passive: false,
127
+ pull: false,
128
+ devicePairingData: {
129
+ buildHash: appVersionBuf,
130
+ deviceProps: companionProto,
131
+ eRegid: encodeBigEndian(registrationId),
132
+ eKeytype: KEY_BUNDLE_TYPE,
133
+ eIdent: signedIdentityKey.public,
134
+ eSkeyId: encodeBigEndian(signedPreKey.keyId, 3),
135
+ eSkeyVal: signedPreKey.keyPair.public,
136
+ eSkeySig: signedPreKey.signature
137
+ }
138
+ };
139
+ return proto.ClientPayload.fromObject(registerPayload);
140
+ };
141
+ export const configureSuccessfulPairing = (stanza, { advSecretKey, signedIdentityKey, signalIdentities }) => {
142
+ const msgId = stanza.attrs.id;
143
+ const pairSuccessNode = getBinaryNodeChild(stanza, 'pair-success');
144
+ const deviceIdentityNode = getBinaryNodeChild(pairSuccessNode, 'device-identity');
145
+ const platformNode = getBinaryNodeChild(pairSuccessNode, 'platform');
146
+ const deviceNode = getBinaryNodeChild(pairSuccessNode, 'device');
147
+ const businessNode = getBinaryNodeChild(pairSuccessNode, 'biz');
148
+ if (!deviceIdentityNode || !deviceNode) {
149
+ throw new Boom('Missing device-identity or device in pair success node', { data: stanza });
150
+ }
151
+ const bizName = businessNode?.attrs.name;
152
+ const jid = deviceNode.attrs.jid;
153
+ const lid = deviceNode.attrs.lid;
154
+ const { details, hmac, accountType } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content);
155
+ let hmacPrefix = Buffer.from([]);
156
+ if (accountType !== undefined && accountType === proto.ADVEncryptionType.HOSTED) {
157
+ hmacPrefix = WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX;
158
+ }
159
+ const advSign = hmacSign(Buffer.concat([hmacPrefix, details]), Buffer.from(advSecretKey, 'base64'));
160
+ if (Buffer.compare(hmac, advSign) !== 0) {
161
+ throw new Boom('Invalid account signature');
162
+ }
163
+ const account = proto.ADVSignedDeviceIdentity.decode(details);
164
+ const { accountSignatureKey, accountSignature, details: deviceDetails } = account;
165
+ const deviceIdentity = proto.ADVDeviceIdentity.decode(deviceDetails);
166
+ const accountSignaturePrefix = deviceIdentity.deviceType === proto.ADVEncryptionType.HOSTED
167
+ ? WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
168
+ : WA_ADV_ACCOUNT_SIG_PREFIX;
169
+ const accountMsg = Buffer.concat([accountSignaturePrefix, deviceDetails, signedIdentityKey.public]);
170
+ if (!Curve.verify(accountSignatureKey, accountMsg, accountSignature)) {
171
+ throw new Boom('Failed to verify account signature');
172
+ }
173
+ const deviceMsg = Buffer.concat([
174
+ WA_ADV_DEVICE_SIG_PREFIX,
175
+ deviceDetails,
176
+ signedIdentityKey.public,
177
+ accountSignatureKey
178
+ ]);
179
+ account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg);
180
+ const identity = createSignalIdentity(lid, accountSignatureKey);
181
+ const accountEnc = encodeSignedDeviceIdentity(account, false);
182
+ const reply = {
183
+ tag: 'iq',
184
+ attrs: {
185
+ to: S_WHATSAPP_NET,
186
+ type: 'result',
187
+ id: msgId
188
+ },
189
+ content: [
190
+ {
191
+ tag: 'pair-device-sign',
192
+ attrs: {},
193
+ content: [
194
+ {
195
+ tag: 'device-identity',
196
+ attrs: { 'key-index': deviceIdentity.keyIndex.toString() },
197
+ content: accountEnc
198
+ }
199
+ ]
200
+ }
201
+ ]
202
+ };
203
+ const authUpdate = {
204
+ account,
205
+ me: { id: jid, name: bizName, lid },
206
+ signalIdentities: [...(signalIdentities || []), identity],
207
+ platform: platformNode?.attrs.name
208
+ };
209
+ return {
210
+ creds: authUpdate,
211
+ reply
212
+ };
213
+ };
214
+ export const encodeSignedDeviceIdentity = (account, includeSignatureKey) => {
215
+ account = { ...account };
216
+ // set to null if we are not to include the signature key
217
+ // or if we are including the signature key but it is empty
218
+ if (!includeSignatureKey || !account.accountSignatureKey?.length) {
219
+ account.accountSignatureKey = null;
220
+ }
221
+ return proto.ADVSignedDeviceIdentity.encode(account).finish();
222
+ };
@@ -0,0 +1,15 @@
1
+ export declare class AudioFeeder {
2
+ #private;
3
+ private readonly sampleRate;
4
+ private readonly channels;
5
+ private readonly framesPerChunk;
6
+ private readonly onChunk;
7
+ private readonly source;
8
+ droppedChunks: number;
9
+ underflowChunks: number;
10
+ bytesProduced: number;
11
+ chunksEmitted: number;
12
+ constructor(sampleRate: number, channels: number, framesPerChunk: number, onChunk: (chunk: Float32Array) => void, source?: string);
13
+ start: () => void;
14
+ stop: () => void;
15
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Audio feeder.
3
+ *
4
+ * Spawns ffmpeg to decode `source` into f32le PCM at the requested rate, then
5
+ * meters frames out at chunk-cadence to the WASM uplink.
6
+ *
7
+ * @author ShellTear
8
+ */
9
+ import { spawn } from "node:child_process";
10
+ const LOW_WATERMARK_CHUNKS = 16;
11
+ const MAX_QUEUED_CHUNKS = 1024;
12
+ const DEFAULT_WARMUP_MS = 500;
13
+ export class AudioFeeder {
14
+ sampleRate;
15
+ channels;
16
+ framesPerChunk;
17
+ onChunk;
18
+ source;
19
+ #proc = null;
20
+ #pending = Buffer.alloc(0);
21
+ #queue = [];
22
+ #emitTimer = null;
23
+ #nextEmitAtMs = 0;
24
+ #warmupUntilMs = 0;
25
+ droppedChunks = 0;
26
+ underflowChunks = 0;
27
+ bytesProduced = 0;
28
+ chunksEmitted = 0;
29
+ constructor(sampleRate, channels, framesPerChunk, onChunk, source = "silence") {
30
+ this.sampleRate = sampleRate;
31
+ this.channels = channels;
32
+ this.framesPerChunk = framesPerChunk;
33
+ this.onChunk = onChunk;
34
+ this.source = source;
35
+ }
36
+ start = () => {
37
+ if (this.#proc)
38
+ return;
39
+ const chunkSamples = this.framesPerChunk * this.channels;
40
+ const chunkBytes = chunkSamples * Float32Array.BYTES_PER_ELEMENT;
41
+ const chunkIntervalMs = (this.framesPerChunk / this.sampleRate) * 1000;
42
+ const inputArgs = this.#resolveInputArgs();
43
+ this.#proc = spawn("ffmpeg", [
44
+ "-hide_banner",
45
+ "-loglevel", "error",
46
+ "-thread_queue_size", "512",
47
+ ...inputArgs,
48
+ "-f", "f32le",
49
+ "-ac", String(this.channels),
50
+ "-ar", String(this.sampleRate),
51
+ "pipe:1",
52
+ ]);
53
+ this.#proc.stdout.on("data", (chunk) => {
54
+ this.#pending = Buffer.concat([this.#pending, chunk]);
55
+ while (this.#pending.length >= chunkBytes) {
56
+ if (this.#queue.length >= MAX_QUEUED_CHUNKS) {
57
+ this.#proc?.stdout.pause();
58
+ break;
59
+ }
60
+ const frame = this.#pending.subarray(0, chunkBytes);
61
+ this.#pending = this.#pending.subarray(chunkBytes);
62
+ const out = new Float32Array(chunkSamples);
63
+ out.set(new Float32Array(frame.buffer, frame.byteOffset, chunkSamples));
64
+ this.bytesProduced += chunkBytes;
65
+ this.#queue.push(out);
66
+ }
67
+ });
68
+ this.#proc.stderr.on("data", (chunk) => {
69
+ process.stderr.write(`[AudioFeeder] ${chunk.toString().trim()}\n`);
70
+ });
71
+ this.#proc.on("error", (err) => {
72
+ process.stderr.write(`[AudioFeeder] ffmpeg process error: ${err.message}\n`);
73
+ this.#proc = null;
74
+ });
75
+ this.#proc.on("exit", (code) => {
76
+ if (code !== 0 && code !== null) {
77
+ process.stderr.write(`[AudioFeeder] ffmpeg exited with code=${code}\n`);
78
+ }
79
+ this.#proc = null;
80
+ });
81
+ this.#nextEmitAtMs = 0;
82
+ this.#warmupUntilMs = Date.now() + DEFAULT_WARMUP_MS;
83
+ this.#scheduleNext(chunkSamples, chunkIntervalMs);
84
+ };
85
+ stop = () => {
86
+ if (this.#emitTimer) {
87
+ clearTimeout(this.#emitTimer);
88
+ this.#emitTimer = null;
89
+ }
90
+ this.#proc?.kill("SIGTERM");
91
+ this.#proc = null;
92
+ this.#pending = Buffer.alloc(0);
93
+ this.#queue = [];
94
+ this.#warmupUntilMs = 0;
95
+ };
96
+ #resolveInputArgs = () => {
97
+ if (!this.source || this.source === "silence") {
98
+ return ["-f", "lavfi", "-i", `aevalsrc=0:d=3600:s=${this.sampleRate}`];
99
+ }
100
+ if (this.source.startsWith("lavfi:")) {
101
+ return ["-f", "lavfi", "-i", this.source.slice("lavfi:".length)];
102
+ }
103
+ return ["-i", this.source];
104
+ };
105
+ #scheduleNext = (chunkSamples, chunkIntervalMs) => {
106
+ if (!this.#proc)
107
+ return;
108
+ const now = Date.now();
109
+ if (this.#nextEmitAtMs === 0)
110
+ this.#nextEmitAtMs = now;
111
+ const delayMs = Math.max(0, this.#nextEmitAtMs - now);
112
+ this.#emitTimer = setTimeout(() => {
113
+ this.#emitTimer = null;
114
+ if (this.#queue.length < LOW_WATERMARK_CHUNKS && Date.now() < this.#warmupUntilMs) {
115
+ this.#nextEmitAtMs = Date.now() + 10;
116
+ this.#scheduleNext(chunkSamples, chunkIntervalMs);
117
+ return;
118
+ }
119
+ this.#flushOne(chunkSamples);
120
+ this.#nextEmitAtMs += chunkIntervalMs;
121
+ this.#scheduleNext(chunkSamples, chunkIntervalMs);
122
+ }, delayMs);
123
+ };
124
+ #flushOne = (chunkSamples) => {
125
+ let nextChunk = this.#queue.shift();
126
+ if (!nextChunk) {
127
+ nextChunk = new Float32Array(chunkSamples);
128
+ this.underflowChunks += 1;
129
+ }
130
+ this.chunksEmitted += 1;
131
+ this.onChunk(nextChunk);
132
+ if (this.#proc?.stdout.isPaused() && this.#queue.length <= MAX_QUEUED_CHUNKS / 4) {
133
+ this.#proc.stdout.resume();
134
+ }
135
+ };
136
+ }
@@ -0,0 +1,12 @@
1
+ // Deklarasi minimal (upstream tidak menyertakan tipe untuk modul ini).
2
+ import { EventEmitter } from 'events';
3
+ export { CallState } from './types.js';
4
+ export declare class ActiveCall extends EventEmitter {
5
+ constructor(callId: string, engine: any, durationMs?: number);
6
+ readonly callId: string;
7
+ [key: string]: any;
8
+ }
9
+ export declare class VoipClient {
10
+ constructor(config?: Record<string, any>);
11
+ [key: string]: any;
12
+ }
@@ -0,0 +1,282 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { randomBytes, createHmac } from "node:crypto";
3
+
4
+ import { WasmEngine } from "./wasm-engine.js";
5
+ import { RelayRtcTransport } from "./relay-transport.js";
6
+ import { SignalingBridge } from "./signaling.js";
7
+ import { AudioFeeder } from "./audio-feeder.js";
8
+ import { CallState } from "./types.js";
9
+
10
+ export { CallState } from "./types.js";
11
+
12
+ const SHA256_LEN = 32;
13
+
14
+ const toBareJid = (jid) => {
15
+ if (!jid) return jid;
16
+ const at = jid.indexOf("@");
17
+ if (at < 0) return jid;
18
+ const user = jid.slice(0, at).split(":")[0];
19
+ return `${user}@${jid.slice(at + 1)}`;
20
+ };
21
+
22
+ const computeHkdf = (key, salt, info, length) => {
23
+ const effectiveSalt = salt && salt.length > 0 ? Buffer.from(salt) : Buffer.alloc(SHA256_LEN, 0);
24
+ const prk = createHmac("sha256", effectiveSalt).update(key).digest();
25
+ const blocks = Math.ceil(length / SHA256_LEN);
26
+ const okm = Buffer.alloc(blocks * SHA256_LEN);
27
+ let prev = Buffer.alloc(0);
28
+ for (let i = 1; i <= blocks; i += 1) {
29
+ prev = createHmac("sha256", prk)
30
+ .update(prev)
31
+ .update(info)
32
+ .update(Buffer.from([i]))
33
+ .digest();
34
+ prev.copy(okm, (i - 1) * SHA256_LEN);
35
+ }
36
+ return new Uint8Array(okm.buffer, okm.byteOffset, length);
37
+ };
38
+
39
+ const computeHmacSha256 = (data, key) => {
40
+ const result = createHmac("sha256", Buffer.from(key)).update(data).digest();
41
+ return new Uint8Array(result.buffer, result.byteOffset, result.byteLength);
42
+ };
43
+
44
+ const isCallReceiptNode = (node) => {
45
+ if (node?.tag !== "receipt") return false;
46
+ const child = Array.isArray(node.content) ? node.content[0] : null;
47
+ return !!(child?.attrs?.["call-id"] || child?.attrs?.call_id);
48
+ };
49
+
50
+ export class ActiveCall extends EventEmitter {
51
+ #state = CallState.Idle;
52
+ #endResolver;
53
+ #endPromise;
54
+ #endTimer = null;
55
+ #ended = false;
56
+ #hasBeenActive = false;
57
+ _audioSource = "silence";
58
+
59
+ constructor(callId, engine, durationMs) {
60
+ super();
61
+ this.callId = callId;
62
+ this.engine = engine;
63
+ this.#endPromise = new Promise((res) => { this.#endResolver = res; });
64
+ if (durationMs > 0) {
65
+ this.#endTimer = setTimeout(() => this.end(), durationMs);
66
+ }
67
+ }
68
+
69
+ get state() { return this.#state; }
70
+
71
+ end = () => {
72
+ if (this.#ended) return;
73
+ this.#ended = true;
74
+ if (this.#endTimer) { clearTimeout(this.#endTimer); this.#endTimer = null; }
75
+ try { this.engine.endCall(0, true); } catch {}
76
+ };
77
+
78
+ mute = (muted) => {
79
+ try { this.engine.setMute(muted); } catch {}
80
+ };
81
+
82
+ waitForEnd = () => this.#endPromise;
83
+
84
+ _updateState = (state) => {
85
+ this.#state = state;
86
+ if (state === CallState.Calling || state === CallState.PreacceptReceived || state === CallState.Active) {
87
+ this.#hasBeenActive = true;
88
+ }
89
+ if (state === CallState.PreacceptReceived) this.emit("ringing");
90
+ else if (state === CallState.Active) this.emit("connected");
91
+ else if (this.#hasBeenActive && (state === CallState.Idle || state === CallState.Ending)) {
92
+ this._forceEnd("ended");
93
+ }
94
+ };
95
+
96
+ _emitAudio = (pcm) => { this.emit("audio", pcm); };
97
+
98
+ _forceEnd = (reason) => {
99
+ if (this.#ended) return;
100
+ this.#ended = true;
101
+ if (this.#endTimer) { clearTimeout(this.#endTimer); this.#endTimer = null; }
102
+ this.emit("ended", reason);
103
+ this.#endResolver(reason);
104
+ };
105
+ }
106
+
107
+ export class VoipClient {
108
+ #config;
109
+ #engine = null;
110
+ #relay = null;
111
+ #signaling = null;
112
+ #sock = null;
113
+ #activeCall = null;
114
+ #capturePtr = 0;
115
+ #captureChunkBytes = 0;
116
+ #captureSampleRate = 16000;
117
+ #captureChannels = 1;
118
+ #captureFramesPerChunk = 320;
119
+ #feeder = null;
120
+
121
+ constructor(config = {}) {
122
+ this.#config = config;
123
+ }
124
+
125
+ connectWithSocket = async (existingSock) => {
126
+ this.#sock = existingSock;
127
+ await this.#initVoipStack();
128
+ };
129
+
130
+ #initVoipStack = async () => {
131
+ this.#signaling = new SignalingBridge({ sock: this.#sock });
132
+ await this.#signaling.init();
133
+
134
+ this.#relay = new RelayRtcTransport({
135
+ onTransportMessage: (data, ip, port) => this.#engine?.handleOnTransportMessage(data, ip, port),
136
+ onIceRtt: (rttMs, ip, port) => this.#engine?.updateIceRtt(rttMs, ip, port),
137
+ });
138
+
139
+ this.#engine = new WasmEngine({
140
+ resourcesPath: this.#config.resourcesPath,
141
+ callbacks: {
142
+ onSignalingXmpp: (peerJid, callId, xmlPayload) =>
143
+ this.#signaling.sendSignaling(peerJid, callId, xmlPayload),
144
+ onCallEvent: (eventType, eventData) => this.#handleCallEvent(eventType, eventData),
145
+ sendDataToRelay: (data, ip, port) => this.#relay.send(data, ip, port),
146
+ onAudioCaptureInit: (config) => this.#handleAudioCaptureInit(config),
147
+ onAudioCaptureStart: () => this.#handleAudioCaptureStart(),
148
+ onAudioCaptureStop: () => this.#handleAudioCaptureStop(),
149
+ onAudioPlaybackData: (audioData) => this.#activeCall?._emitAudio(audioData),
150
+ cryptoHkdf: computeHkdf,
151
+ hmacSha256: computeHmacSha256,
152
+ },
153
+ });
154
+
155
+ await this.#engine.initialize();
156
+ this.#signaling.attachEngine(this.#engine);
157
+
158
+ const selfPnJid = this.#sock.authState.creds.me?.id;
159
+ const selfLidJid = this.#sock.authState.creds.me?.lid;
160
+ this.#engine.initVoipStack(selfPnJid, toBareJid(selfPnJid), selfLidJid);
161
+ await this.#engine.waitForVoipStackReady();
162
+ try { this.#engine.updateNetworkMedium(2, 0); } catch {}
163
+
164
+ this.#sock.ws.on("CB:call", (node) => {
165
+ this.#signaling.processIncomingCall(node, this.#engine, this.#activeCall?.callId ?? "");
166
+ });
167
+ this.#sock.ws.on("CB:receipt", (node) => {
168
+ if (!isCallReceiptNode(node)) return;
169
+ this.#signaling.processIncomingReceipt(node, this.#engine, this.#activeCall?.callId ?? "");
170
+ });
171
+ };
172
+
173
+ call = async (phoneNumber, opts = {}) => {
174
+ if (!this.#engine || !this.#signaling) throw new Error("Not connected. Call connectWithSocket() first.");
175
+ if (this.#activeCall) throw new Error("A call is already active.");
176
+
177
+ const targetNumber = phoneNumber.replace(/\D/g, "");
178
+ const targetPnJid = `${targetNumber}@s.whatsapp.net`;
179
+ const durationMs = opts.durationMs ?? 120_000;
180
+ const audioSource = opts.audioSource ?? "silence";
181
+
182
+ const peerLid = await this.#signaling.resolveLid(targetPnJid);
183
+ if (!peerLid) throw new Error(`Could not resolve LID for ${targetPnJid}`);
184
+
185
+ for (const jid of [targetPnJid, peerLid]) {
186
+ try { await this.#sock.presenceSubscribe(jid); } catch {}
187
+ }
188
+ await new Promise((r) => setTimeout(r, 750));
189
+
190
+ const peerDeviceJids = await this.#signaling.discoverPeerDevices(peerLid);
191
+ const deviceList = peerDeviceJids.length ? peerDeviceJids : [toBareJid(peerLid)];
192
+
193
+ const allSessionTargets = [...new Set([...deviceList, peerLid, toBareJid(peerLid), targetPnJid, toBareJid(targetPnJid)])];
194
+ await this.#signaling.ensureSessionsForPeers(allSessionTargets);
195
+
196
+ await new Promise((r) => setTimeout(r, 500));
197
+ await this.#signaling.issueTcToken(peerLid);
198
+ const tcToken = await this.#signaling.ensureTcToken(peerLid, targetPnJid);
199
+
200
+ const callId = ("00" + randomBytes(16).toString("hex").slice(2)).toUpperCase();
201
+
202
+ const call = new ActiveCall(callId, this.#engine, durationMs);
203
+ call._audioSource = audioSource;
204
+ this.#activeCall = call;
205
+
206
+ this.#engine.startCall({
207
+ peerJid: peerLid,
208
+ peerPn: targetPnJid,
209
+ peerList: deviceList,
210
+ callId,
211
+ isVideo: false,
212
+ isLidCall: true,
213
+ isFromDialer: false,
214
+ extraData: tcToken,
215
+ });
216
+
217
+ return call;
218
+ };
219
+
220
+ disconnect = () => {
221
+ this.#activeCall?._forceEnd("disconnect");
222
+ this.#activeCall = null;
223
+ this.#relay?.closeAll();
224
+ this.#engine?.destroy();
225
+ this.#engine = null;
226
+ this.#relay = null;
227
+ this.#signaling = null;
228
+ this.#sock = null;
229
+ };
230
+
231
+ #handleCallEvent = (eventType, eventData) => {
232
+ if (eventType === 16 && eventData) {
233
+ try {
234
+ const parsed = JSON.parse(eventData);
235
+ const info = parsed.call_info ?? parsed.callInfo ?? {};
236
+ const callState = Number(info.call_state ?? info.callState ?? 0);
237
+ this.#activeCall?._updateState(callState);
238
+ } catch {}
239
+ } else if (eventType === 156 && eventData) {
240
+ try {
241
+ const update = JSON.parse(eventData);
242
+ this.#relay?.updateRelayList(update);
243
+ } catch {}
244
+ } else if (eventType === 2) {
245
+ this.#activeCall?._forceEnd("remote_end");
246
+ }
247
+ };
248
+
249
+ #handleAudioCaptureInit = (config) => {
250
+ if (!this.#engine) return;
251
+ this.#captureSampleRate = config.sampleRate || 16000;
252
+ this.#captureChannels = config.channels || 1;
253
+ this.#captureFramesPerChunk = config.framesPerChunk || 320;
254
+ const chunkSamples = this.#captureFramesPerChunk * this.#captureChannels;
255
+ this.#captureChunkBytes = chunkSamples * Float32Array.BYTES_PER_ELEMENT;
256
+ this.#capturePtr = this.#engine.malloc(this.#captureChunkBytes);
257
+ };
258
+
259
+ #handleAudioCaptureStart = () => {
260
+ if (!this.#engine || !this.#capturePtr) return;
261
+ const audioSource = this.#activeCall?._audioSource ?? "silence";
262
+ this.#feeder = new AudioFeeder(
263
+ this.#captureSampleRate,
264
+ this.#captureChannels,
265
+ this.#captureFramesPerChunk,
266
+ (chunk) => {
267
+ if (this.#engine && this.#capturePtr) this.#engine.sendAudioData(chunk, this.#capturePtr);
268
+ },
269
+ audioSource,
270
+ );
271
+ this.#feeder.start();
272
+ };
273
+
274
+ #handleAudioCaptureStop = () => {
275
+ this.#feeder?.stop();
276
+ this.#feeder = null;
277
+ if (this.#engine && this.#capturePtr) {
278
+ try { this.#engine.free(this.#capturePtr); } catch {}
279
+ this.#capturePtr = 0;
280
+ }
281
+ };
282
+ }