gifted-baileys 1.5.4 → 1.5.5

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 (250) hide show
  1. package/LICENSE +1 -1
  2. package/WAProto/WAProto.proto +88 -969
  3. package/WAProto/index.d.ts +1256 -13195
  4. package/WAProto/index.js +74730 -125106
  5. package/package.json +9 -27
  6. package/src/Defaults/baileys-version.json +3 -0
  7. package/{lib → src}/Defaults/index.js +14 -7
  8. package/src/Defaults/index.ts +131 -0
  9. package/src/Signal/libsignal.js +180 -0
  10. package/src/Signal/libsignal.ts +141 -0
  11. package/src/Socket/Client/abstract-socket-client.ts +19 -0
  12. package/{lib → src}/Socket/Client/index.js +3 -2
  13. package/src/Socket/Client/index.ts +3 -0
  14. package/src/Socket/Client/mobile-socket-client.js +78 -0
  15. package/src/Socket/Client/mobile-socket-client.ts +66 -0
  16. package/src/Socket/Client/web-socket-client.js +75 -0
  17. package/src/Socket/Client/web-socket-client.ts +57 -0
  18. package/{lib → src}/Socket/business.js +33 -28
  19. package/src/Socket/business.ts +281 -0
  20. package/{lib → src}/Socket/chats.js +176 -174
  21. package/src/Socket/chats.ts +1030 -0
  22. package/{lib → src}/Socket/groups.js +68 -80
  23. package/src/Socket/groups.ts +356 -0
  24. package/{lib → src}/Socket/index.js +1 -4
  25. package/src/Socket/index.ts +13 -0
  26. package/{lib → src}/Socket/messages-recv.js +211 -378
  27. package/src/Socket/messages-recv.ts +985 -0
  28. package/{lib → src}/Socket/messages-send.js +177 -452
  29. package/src/Socket/messages-send.ts +871 -0
  30. package/{lib → src}/Socket/newsletter.js +98 -107
  31. package/src/Socket/newsletter.ts +282 -0
  32. package/{lib → src}/Socket/registration.js +48 -56
  33. package/src/Socket/registration.ts +250 -0
  34. package/{lib → src}/Socket/socket.js +77 -77
  35. package/src/Socket/socket.ts +777 -0
  36. package/src/Store/index.ts +3 -0
  37. package/{lib → src}/Store/make-cache-manager-store.js +34 -25
  38. package/src/Store/make-cache-manager-store.ts +100 -0
  39. package/{lib → src}/Store/make-in-memory-store.js +32 -36
  40. package/src/Store/make-in-memory-store.ts +475 -0
  41. package/src/Store/make-ordered-dictionary.ts +86 -0
  42. package/{lib → src}/Store/object-repository.js +1 -1
  43. package/src/Store/object-repository.ts +32 -0
  44. package/src/Tests/test.app-state-sync.js +204 -0
  45. package/src/Tests/test.app-state-sync.ts +207 -0
  46. package/src/Tests/test.event-buffer.js +270 -0
  47. package/src/Tests/test.event-buffer.ts +319 -0
  48. package/src/Tests/test.key-store.js +76 -0
  49. package/src/Tests/test.key-store.ts +92 -0
  50. package/src/Tests/test.libsignal.js +141 -0
  51. package/src/Tests/test.libsignal.ts +186 -0
  52. package/src/Tests/test.media-download.js +93 -0
  53. package/src/Tests/test.media-download.ts +76 -0
  54. package/src/Tests/test.messages.js +33 -0
  55. package/src/Tests/test.messages.ts +37 -0
  56. package/src/Tests/utils.js +34 -0
  57. package/src/Tests/utils.ts +36 -0
  58. package/src/Types/Auth.ts +113 -0
  59. package/src/Types/Call.ts +15 -0
  60. package/src/Types/Chat.ts +106 -0
  61. package/{lib/Types/Contact.d.ts → src/Types/Contact.ts} +9 -8
  62. package/src/Types/Events.ts +93 -0
  63. package/src/Types/GroupMetadata.ts +53 -0
  64. package/src/Types/Label.ts +36 -0
  65. package/{lib/Types/LabelAssociation.d.ts → src/Types/LabelAssociation.ts} +22 -16
  66. package/src/Types/Message.ts +288 -0
  67. package/src/Types/Newsletter.ts +98 -0
  68. package/src/Types/Product.ts +85 -0
  69. package/src/Types/Signal.ts +68 -0
  70. package/{lib/Types/Socket.d.ts → src/Types/Socket.ts} +68 -61
  71. package/src/Types/State.ts +29 -0
  72. package/src/Types/index.ts +59 -0
  73. package/{lib → src}/Utils/auth-utils.js +90 -73
  74. package/src/Utils/auth-utils.ts +222 -0
  75. package/src/Utils/baileys-event-stream.js +92 -0
  76. package/src/Utils/baileys-event-stream.ts +66 -0
  77. package/{lib → src}/Utils/business.js +43 -15
  78. package/src/Utils/business.ts +275 -0
  79. package/{lib → src}/Utils/chat-utils.js +94 -87
  80. package/src/Utils/chat-utils.ts +860 -0
  81. package/{lib → src}/Utils/crypto.js +2 -4
  82. package/src/Utils/crypto.ts +131 -0
  83. package/src/Utils/decode-wa-message.js +211 -0
  84. package/src/Utils/decode-wa-message.ts +228 -0
  85. package/{lib → src}/Utils/event-buffer.js +13 -4
  86. package/src/Utils/event-buffer.ts +613 -0
  87. package/{lib → src}/Utils/generics.js +86 -67
  88. package/src/Utils/generics.ts +434 -0
  89. package/{lib → src}/Utils/history.js +39 -13
  90. package/src/Utils/history.ts +112 -0
  91. package/src/Utils/index.ts +17 -0
  92. package/{lib → src}/Utils/link-preview.js +54 -17
  93. package/src/Utils/link-preview.ts +122 -0
  94. package/src/Utils/logger.ts +3 -0
  95. package/src/Utils/lt-hash.ts +61 -0
  96. package/{lib → src}/Utils/make-mutex.js +13 -4
  97. package/src/Utils/make-mutex.ts +44 -0
  98. package/{lib → src}/Utils/messages-media.js +255 -193
  99. package/src/Utils/messages-media.ts +847 -0
  100. package/{lib → src}/Utils/messages.js +118 -588
  101. package/src/Utils/messages.ts +956 -0
  102. package/src/Utils/noise-handler.ts +197 -0
  103. package/{lib → src}/Utils/process-message.js +30 -27
  104. package/src/Utils/process-message.ts +414 -0
  105. package/{lib → src}/Utils/signal.js +42 -25
  106. package/src/Utils/signal.ts +177 -0
  107. package/{lib → src}/Utils/use-multi-file-auth-state.js +28 -27
  108. package/src/Utils/use-multi-file-auth-state.ts +90 -0
  109. package/{lib → src}/Utils/validate-connection.js +9 -40
  110. package/src/Utils/validate-connection.ts +238 -0
  111. package/src/WABinary/constants.ts +42 -0
  112. package/src/WABinary/decode.ts +265 -0
  113. package/{lib → src}/WABinary/encode.js +10 -16
  114. package/src/WABinary/encode.ts +236 -0
  115. package/src/WABinary/generic-utils.ts +121 -0
  116. package/src/WABinary/index.ts +5 -0
  117. package/src/WABinary/jid-utils.ts +68 -0
  118. package/src/WABinary/types.ts +17 -0
  119. package/src/WAM/BinaryInfo.ts +12 -0
  120. package/src/WAM/constants.ts +15382 -0
  121. package/src/WAM/encode.ts +174 -0
  122. package/src/WAM/index.ts +3 -0
  123. package/{lib → src}/index.js +0 -1
  124. package/src/index.ts +13 -0
  125. package/lib/Defaults/baileys-version.json +0 -3
  126. package/lib/Defaults/index.d.ts +0 -284
  127. package/lib/Signal/libsignal.d.ts +0 -3
  128. package/lib/Signal/libsignal.js +0 -161
  129. package/lib/Socket/Client/abstract-socket-client.d.ts +0 -15
  130. package/lib/Socket/Client/index.d.ts +0 -2
  131. package/lib/Socket/Client/mobile-socket-client.d.ts +0 -12
  132. package/lib/Socket/Client/mobile-socket-client.js +0 -65
  133. package/lib/Socket/Client/types.d.ts +0 -17
  134. package/lib/Socket/Client/types.js +0 -13
  135. package/lib/Socket/Client/websocket.d.ts +0 -12
  136. package/lib/Socket/Client/websocket.js +0 -62
  137. package/lib/Socket/business.d.ts +0 -170
  138. package/lib/Socket/chats.d.ts +0 -81
  139. package/lib/Socket/groups.d.ts +0 -115
  140. package/lib/Socket/index.d.ts +0 -172
  141. package/lib/Socket/messages-recv.d.ts +0 -158
  142. package/lib/Socket/messages-send.d.ts +0 -155
  143. package/lib/Socket/newsletter.d.ts +0 -132
  144. package/lib/Socket/registration.d.ts +0 -264
  145. package/lib/Socket/socket.d.ts +0 -44
  146. package/lib/Socket/usync.d.ts +0 -37
  147. package/lib/Socket/usync.js +0 -70
  148. package/lib/Store/index.d.ts +0 -3
  149. package/lib/Store/make-cache-manager-store.d.ts +0 -14
  150. package/lib/Store/make-in-memory-store.d.ts +0 -118
  151. package/lib/Store/make-ordered-dictionary.d.ts +0 -13
  152. package/lib/Store/object-repository.d.ts +0 -10
  153. package/lib/Types/Auth.d.ts +0 -109
  154. package/lib/Types/Call.d.ts +0 -13
  155. package/lib/Types/Chat.d.ts +0 -107
  156. package/lib/Types/Events.d.ts +0 -172
  157. package/lib/Types/GroupMetadata.d.ts +0 -56
  158. package/lib/Types/Label.d.ts +0 -46
  159. package/lib/Types/Message.d.ts +0 -433
  160. package/lib/Types/Newsletter.d.ts +0 -92
  161. package/lib/Types/Product.d.ts +0 -78
  162. package/lib/Types/Signal.d.ts +0 -57
  163. package/lib/Types/State.d.ts +0 -27
  164. package/lib/Types/USync.d.ts +0 -25
  165. package/lib/Types/USync.js +0 -2
  166. package/lib/Types/index.d.ts +0 -66
  167. package/lib/Utils/auth-utils.d.ts +0 -18
  168. package/lib/Utils/baileys-event-stream.d.ts +0 -16
  169. package/lib/Utils/baileys-event-stream.js +0 -63
  170. package/lib/Utils/business.d.ts +0 -22
  171. package/lib/Utils/chat-utils.d.ts +0 -70
  172. package/lib/Utils/crypto.d.ts +0 -40
  173. package/lib/Utils/decode-wa-message.d.ts +0 -36
  174. package/lib/Utils/decode-wa-message.js +0 -226
  175. package/lib/Utils/event-buffer.d.ts +0 -35
  176. package/lib/Utils/generics.d.ts +0 -88
  177. package/lib/Utils/history.d.ts +0 -19
  178. package/lib/Utils/index.d.ts +0 -17
  179. package/lib/Utils/link-preview.d.ts +0 -21
  180. package/lib/Utils/logger.d.ts +0 -2
  181. package/lib/Utils/lt-hash.d.ts +0 -12
  182. package/lib/Utils/make-mutex.d.ts +0 -7
  183. package/lib/Utils/messages-media.d.ts +0 -113
  184. package/lib/Utils/messages.d.ts +0 -77
  185. package/lib/Utils/noise-handler.d.ts +0 -20
  186. package/lib/Utils/process-message.d.ts +0 -41
  187. package/lib/Utils/signal.d.ts +0 -33
  188. package/lib/Utils/use-multi-file-auth-state.d.ts +0 -12
  189. package/lib/Utils/validate-connection.d.ts +0 -11
  190. package/lib/WABinary/constants.d.ts +0 -27
  191. package/lib/WABinary/decode.d.ts +0 -6
  192. package/lib/WABinary/encode.d.ts +0 -2
  193. package/lib/WABinary/generic-utils.d.ts +0 -14
  194. package/lib/WABinary/index.d.ts +0 -5
  195. package/lib/WABinary/jid-utils.d.ts +0 -31
  196. package/lib/WABinary/types.d.ts +0 -18
  197. package/lib/WAM/BinaryInfo.d.ts +0 -8
  198. package/lib/WAM/constants.d.ts +0 -38
  199. package/lib/WAM/encode.d.ts +0 -2
  200. package/lib/WAM/index.d.ts +0 -3
  201. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +0 -9
  202. package/lib/WAUSync/Protocols/USyncContactProtocol.js +0 -32
  203. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +0 -22
  204. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +0 -57
  205. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +0 -12
  206. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +0 -30
  207. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +0 -12
  208. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +0 -42
  209. package/lib/WAUSync/Protocols/index.d.ts +0 -4
  210. package/lib/WAUSync/Protocols/index.js +0 -20
  211. package/lib/WAUSync/USyncQuery.d.ts +0 -26
  212. package/lib/WAUSync/USyncQuery.js +0 -79
  213. package/lib/WAUSync/USyncUser.d.ts +0 -10
  214. package/lib/WAUSync/USyncUser.js +0 -22
  215. package/lib/WAUSync/index.d.ts +0 -3
  216. package/lib/WAUSync/index.js +0 -19
  217. /package/{lib → src}/Defaults/phonenumber-mcc.json +0 -0
  218. /package/{lib → src}/Socket/Client/abstract-socket-client.js +0 -0
  219. /package/{lib → src}/Store/index.js +0 -0
  220. /package/{lib → src}/Store/make-ordered-dictionary.js +0 -0
  221. /package/{lib → src}/Types/Auth.js +0 -0
  222. /package/{lib → src}/Types/Call.js +0 -0
  223. /package/{lib → src}/Types/Chat.js +0 -0
  224. /package/{lib → src}/Types/Contact.js +0 -0
  225. /package/{lib → src}/Types/Events.js +0 -0
  226. /package/{lib → src}/Types/GroupMetadata.js +0 -0
  227. /package/{lib → src}/Types/Label.js +0 -0
  228. /package/{lib → src}/Types/LabelAssociation.js +0 -0
  229. /package/{lib → src}/Types/Message.js +0 -0
  230. /package/{lib → src}/Types/Newsletter.js +0 -0
  231. /package/{lib → src}/Types/Product.js +0 -0
  232. /package/{lib → src}/Types/Signal.js +0 -0
  233. /package/{lib → src}/Types/Socket.js +0 -0
  234. /package/{lib → src}/Types/State.js +0 -0
  235. /package/{lib → src}/Types/index.js +0 -0
  236. /package/{lib → src}/Utils/index.js +0 -0
  237. /package/{lib → src}/Utils/logger.js +0 -0
  238. /package/{lib → src}/Utils/lt-hash.js +0 -0
  239. /package/{lib → src}/Utils/noise-handler.js +0 -0
  240. /package/{lib → src}/WABinary/constants.js +0 -0
  241. /package/{lib → src}/WABinary/decode.js +0 -0
  242. /package/{lib → src}/WABinary/generic-utils.js +0 -0
  243. /package/{lib → src}/WABinary/index.js +0 -0
  244. /package/{lib → src}/WABinary/jid-utils.js +0 -0
  245. /package/{lib → src}/WABinary/types.js +0 -0
  246. /package/{lib → src}/WAM/BinaryInfo.js +0 -0
  247. /package/{lib → src}/WAM/constants.js +0 -0
  248. /package/{lib → src}/WAM/encode.js +0 -0
  249. /package/{lib → src}/WAM/index.js +0 -0
  250. /package/{lib → src}/gifted +0 -0
@@ -0,0 +1,985 @@
1
+
2
+ import { Boom } from '@hapi/boom'
3
+ import { randomBytes } from 'crypto'
4
+ import NodeCache from 'node-cache'
5
+ import { proto } from '../../WAProto'
6
+ import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults'
7
+ import { MessageReceiptType, MessageRelayOptions, MessageUserReceipt, SocketConfig, WACallEvent, WAMessageKey, WAMessageStatus, WAMessageStubType, WAPatchName } from '../Types'
8
+ import {
9
+ aesDecryptCTR,
10
+ aesEncryptGCM,
11
+ Curve,
12
+ decodeMediaRetryNode,
13
+ decryptMessageNode,
14
+ delay,
15
+ derivePairingCodeKey,
16
+ encodeBigEndian,
17
+ encodeSignedDeviceIdentity,
18
+ generateMessageIDV2,
19
+ getCallStatusFromNode,
20
+ getHistoryMsg,
21
+ getNextPreKeys,
22
+ getStatusFromReceiptType, hkdf,
23
+ unixTimestampSeconds,
24
+ xmppPreKey,
25
+ xmppSignedPreKey
26
+ } from '../Utils'
27
+ import { cleanMessage } from '../Utils'
28
+ import { makeMutex } from '../Utils/make-mutex'
29
+ import {
30
+ areJidsSameUser,
31
+ BinaryNode,
32
+ getAllBinaryNodeChildren,
33
+ getBinaryNodeChild,
34
+ getBinaryNodeChildBuffer,
35
+ getBinaryNodeChildren,
36
+ isJidGroup,
37
+ isJidUser,
38
+ jidDecode,
39
+ jidEncode,
40
+ jidNormalizedUser,
41
+ S_WHATSAPP_NET
42
+ } from '../WABinary'
43
+ import { extractGroupMetadata } from './groups'
44
+ import { makeMessagesSocket } from './messages-send'
45
+
46
+ export const makeMessagesRecvSocket = (config: SocketConfig) => {
47
+ const {
48
+ logger,
49
+ retryRequestDelayMs,
50
+ maxMsgRetryCount,
51
+ getMessage,
52
+ shouldIgnoreJid
53
+ } = config
54
+ const sock = makeMessagesSocket(config)
55
+ const {
56
+ ev,
57
+ authState,
58
+ ws,
59
+ processingMutex,
60
+ signalRepository,
61
+ query,
62
+ upsertMessage,
63
+ resyncAppState,
64
+ onUnexpectedError,
65
+ assertSessions,
66
+ sendNode,
67
+ relayMessage,
68
+ sendReceipt,
69
+ uploadPreKeys,
70
+ getUSyncDevices,
71
+ createParticipantNodes
72
+ } = sock
73
+
74
+ /** this mutex ensures that each retryRequest will wait for the previous one to finish */
75
+ const retryMutex = makeMutex()
76
+
77
+ const msgRetryCache = config.msgRetryCounterCache || new NodeCache({
78
+ stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
79
+ useClones: false
80
+ })
81
+ const callOfferCache = config.callOfferCache || new NodeCache({
82
+ stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
83
+ useClones: false
84
+ })
85
+
86
+ let sendActiveReceipts = false
87
+
88
+ const sendMessageAck = async({ tag, attrs, content }: BinaryNode) => {
89
+ const stanza: BinaryNode = {
90
+ tag: 'ack',
91
+ attrs: {
92
+ id: attrs.id,
93
+ to: attrs.from,
94
+ class: tag,
95
+ }
96
+ }
97
+
98
+ if(!!attrs.participant) {
99
+ stanza.attrs.participant = attrs.participant
100
+ }
101
+
102
+ if(!!attrs.recipient) {
103
+ stanza.attrs.recipient = attrs.recipient
104
+ }
105
+
106
+
107
+ if(!!attrs.type && (tag !== 'message' || getBinaryNodeChild({ tag, attrs, content }, 'unavailable'))) {
108
+ stanza.attrs.type = attrs.type
109
+ }
110
+
111
+ if(tag === 'message' && getBinaryNodeChild({ tag, attrs, content }, 'unavailable')) {
112
+ stanza.attrs.from = authState.creds.me!.id
113
+ }
114
+
115
+ logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack')
116
+ await sendNode(stanza)
117
+ }
118
+
119
+ const offerCall = async(toJid: string, isVideo = false) => {
120
+ const callId = randomBytes(16).toString('hex').toUpperCase().substring(0, 64)
121
+
122
+ const offerContent: BinaryNode[] = []
123
+ offerContent.push({ tag: 'audio', attrs: { enc: 'opus', rate: '16000' }, content: undefined })
124
+ offerContent.push({ tag: 'audio', attrs: { enc: 'opus', rate: '8000' }, content: undefined })
125
+
126
+ if(isVideo) {
127
+ offerContent.push({
128
+ tag: 'video',
129
+ attrs: { enc: 'vp8', dec: 'vp8', orientation: '0', 'screen_width': '1920', 'screen_height': '1080', 'device_orientation': '0' },
130
+ content: undefined
131
+ })
132
+ }
133
+
134
+ offerContent.push({ tag: 'net', attrs: { medium: '3' }, content: undefined })
135
+ offerContent.push({ tag: 'capability', attrs: { ver: '1' }, content: new Uint8Array([1, 4, 255, 131, 207, 4]) })
136
+ offerContent.push({ tag: 'encopt', attrs: { keygen: '2' }, content: undefined })
137
+
138
+ const encKey = randomBytes(32)
139
+
140
+ const devices = (await getUSyncDevices([toJid], true, false)).map(({ user, device }) => jidEncode(user, 's.whatsapp.net', device))
141
+
142
+ await assertSessions(devices, true)
143
+
144
+ const { nodes: destinations, shouldIncludeDeviceIdentity } = await createParticipantNodes(devices, {
145
+ call: {
146
+ callKey: new Uint8Array(encKey)
147
+ }
148
+ }, { count: '0' })
149
+
150
+ offerContent.push({ tag: 'destination', attrs: {}, content: destinations })
151
+
152
+ if(shouldIncludeDeviceIdentity) {
153
+ offerContent.push({
154
+ tag: 'device-identity',
155
+ attrs: {},
156
+ content: encodeSignedDeviceIdentity(authState.creds.account!, true)
157
+ })
158
+ }
159
+
160
+ const stanza: BinaryNode = ({
161
+ tag: 'call',
162
+ attrs: {
163
+ id: generateMessageIDV2(),
164
+ to: toJid,
165
+ },
166
+ content: [{
167
+ tag: 'offer',
168
+ attrs: {
169
+ 'call-id': callId,
170
+ 'call-creator': authState.creds.me!.id,
171
+ },
172
+ content: offerContent,
173
+ }],
174
+ })
175
+ await query(stanza)
176
+ return {
177
+ id: callId,
178
+ to: toJid
179
+ }
180
+ }
181
+
182
+ const rejectCall = async(callId: string, callFrom: string) => {
183
+ const stanza: BinaryNode = ({
184
+ tag: 'call',
185
+ attrs: {
186
+ from: authState.creds.me!.id,
187
+ to: callFrom,
188
+ },
189
+ content: [{
190
+ tag: 'reject',
191
+ attrs: {
192
+ 'call-id': callId,
193
+ 'call-creator': callFrom,
194
+ count: '0',
195
+ },
196
+ content: undefined,
197
+ }],
198
+ })
199
+ await query(stanza)
200
+ }
201
+
202
+ const sendRetryRequest = async(node: BinaryNode, forceIncludeKeys = false) => {
203
+ const msgId = node.attrs.id
204
+
205
+ let retryCount = msgRetryCache.get<number>(msgId) || 0
206
+ if(retryCount >= maxMsgRetryCount) {
207
+ logger.debug({ retryCount, msgId }, 'reached retry limit, clearing')
208
+ msgRetryCache.del(msgId)
209
+ return
210
+ }
211
+
212
+ retryCount += 1
213
+ msgRetryCache.set(msgId, retryCount)
214
+
215
+ const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds
216
+
217
+ const deviceIdentity = encodeSignedDeviceIdentity(account!, true)
218
+ await authState.keys.transaction(
219
+ async() => {
220
+ const receipt: BinaryNode = {
221
+ tag: 'receipt',
222
+ attrs: {
223
+ id: msgId,
224
+ type: 'retry',
225
+ to: node.attrs.from
226
+ },
227
+ content: [
228
+ {
229
+ tag: 'retry',
230
+ attrs: {
231
+ count: retryCount.toString(),
232
+ id: node.attrs.id,
233
+ t: node.attrs.t,
234
+ v: '1'
235
+ }
236
+ },
237
+ {
238
+ tag: 'registration',
239
+ attrs: { },
240
+ content: encodeBigEndian(authState.creds.registrationId)
241
+ }
242
+ ]
243
+ }
244
+
245
+ if(node.attrs.recipient) {
246
+ receipt.attrs.recipient = node.attrs.recipient
247
+ }
248
+
249
+ if(node.attrs.participant) {
250
+ receipt.attrs.participant = node.attrs.participant
251
+ }
252
+
253
+ if(retryCount > 1 || forceIncludeKeys) {
254
+ const { update, preKeys } = await getNextPreKeys(authState, 1)
255
+
256
+ const [keyId] = Object.keys(preKeys)
257
+ const key = preKeys[+keyId]
258
+
259
+ const content = receipt.content! as BinaryNode[]
260
+ content.push({
261
+ tag: 'keys',
262
+ attrs: { },
263
+ content: [
264
+ { tag: 'type', attrs: { }, content: Buffer.from(KEY_BUNDLE_TYPE) },
265
+ { tag: 'identity', attrs: { }, content: identityKey.public },
266
+ xmppPreKey(key, +keyId),
267
+ xmppSignedPreKey(signedPreKey),
268
+ { tag: 'device-identity', attrs: { }, content: deviceIdentity }
269
+ ]
270
+ })
271
+
272
+ ev.emit('creds.update', update)
273
+ }
274
+
275
+ await sendNode(receipt)
276
+
277
+ logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt')
278
+ }
279
+ )
280
+ }
281
+
282
+ const handleEncryptNotification = async(node: BinaryNode) => {
283
+ const from = node.attrs.from
284
+ if(from === S_WHATSAPP_NET) {
285
+ const countChild = getBinaryNodeChild(node, 'count')
286
+ const count = +countChild!.attrs.value
287
+ const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT
288
+
289
+ logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count')
290
+ if(shouldUploadMorePreKeys) {
291
+ await uploadPreKeys()
292
+ }
293
+ } else {
294
+ const identityNode = getBinaryNodeChild(node, 'identity')
295
+ if(identityNode) {
296
+ logger.info({ jid: from }, 'identity changed')
297
+ // not handling right now
298
+ // signal will override new identity anyway
299
+ } else {
300
+ logger.info({ node }, 'unknown encrypt notification')
301
+ }
302
+ }
303
+ }
304
+
305
+ const handleGroupNotification = (
306
+ participant: string,
307
+ child: BinaryNode,
308
+ msg: Partial<proto.IWebMessageInfo>
309
+ ) => {
310
+ switch (child?.tag) {
311
+ case 'create':
312
+ const metadata = extractGroupMetadata(child)
313
+
314
+ msg.messageStubType = WAMessageStubType.GROUP_CREATE
315
+ msg.messageStubParameters = [metadata.subject]
316
+ msg.key = { participant: metadata.owner }
317
+
318
+ ev.emit('chats.upsert', [{
319
+ id: metadata.id,
320
+ name: metadata.subject,
321
+ conversationTimestamp: metadata.creation,
322
+ }])
323
+ ev.emit('groups.upsert', [{
324
+ ...metadata,
325
+ author: participant
326
+ }])
327
+ break
328
+ case 'ephemeral':
329
+ case 'not_ephemeral':
330
+ msg.message = {
331
+ protocolMessage: {
332
+ type: proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
333
+ ephemeralExpiration: +(child.attrs.expiration || 0)
334
+ }
335
+ }
336
+ break
337
+ case 'promote':
338
+ case 'demote':
339
+ case 'remove':
340
+ case 'add':
341
+ case 'leave':
342
+ const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`
343
+ msg.messageStubType = WAMessageStubType[stubType]
344
+
345
+ const participants = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid)
346
+ if(
347
+ participants.length === 1 &&
348
+ // if recv. "remove" message and sender removed themselves
349
+ // mark as left
350
+ areJidsSameUser(participants[0], participant) &&
351
+ child.tag === 'remove'
352
+ ) {
353
+ msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE
354
+ }
355
+
356
+ msg.messageStubParameters = participants
357
+ break
358
+ case 'subject':
359
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT
360
+ msg.messageStubParameters = [ child.attrs.subject ]
361
+ break
362
+ case 'announcement':
363
+ case 'not_announcement':
364
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE
365
+ msg.messageStubParameters = [ (child.tag === 'announcement') ? 'on' : 'off' ]
366
+ break
367
+ case 'locked':
368
+ case 'unlocked':
369
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT
370
+ msg.messageStubParameters = [ (child.tag === 'locked') ? 'on' : 'off' ]
371
+ break
372
+ case 'invite':
373
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK
374
+ msg.messageStubParameters = [ child.attrs.code ]
375
+ break
376
+ case 'member_add_mode':
377
+ const addMode = child.content
378
+ if(addMode) {
379
+ msg.messageStubType = WAMessageStubType.GROUP_MEMBER_ADD_MODE
380
+ msg.messageStubParameters = [ addMode.toString() ]
381
+ }
382
+
383
+ break
384
+ case 'membership_approval_mode':
385
+ const approvalMode: any = getBinaryNodeChild(child, 'group_join')
386
+ if(approvalMode) {
387
+ msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE
388
+ msg.messageStubParameters = [ approvalMode.attrs.state ]
389
+ }
390
+
391
+ break
392
+ }
393
+ }
394
+
395
+ const processNotification = async(node: BinaryNode) => {
396
+ const result: Partial<proto.IWebMessageInfo> = { }
397
+ const [child] = getAllBinaryNodeChildren(node)
398
+ const nodeType = node.attrs.type
399
+ const from = jidNormalizedUser(node.attrs.from)
400
+
401
+ switch (nodeType) {
402
+ case 'privacy_token':
403
+ const tokenList = getBinaryNodeChildren(child, 'token')
404
+ for(const { attrs, content } of tokenList) {
405
+ const jid = attrs.jid
406
+ ev.emit('chats.update', [
407
+ {
408
+ id: jid,
409
+ tcToken: content as Buffer
410
+ }
411
+ ])
412
+
413
+ logger.debug({ jid }, 'got privacy token update')
414
+ }
415
+
416
+ break
417
+ case 'w:gp2':
418
+ handleGroupNotification(node.attrs.participant, child, result)
419
+ break
420
+ case 'mediaretry':
421
+ const event = decodeMediaRetryNode(node)
422
+ ev.emit('messages.media-update', [event])
423
+ break
424
+ case 'encrypt':
425
+ await handleEncryptNotification(node)
426
+ break
427
+ case 'newsletter':
428
+ // TO DO
429
+ break
430
+ case 'devices':
431
+ const devices = getBinaryNodeChildren(child, 'device')
432
+ if(areJidsSameUser(child.attrs.jid, authState.creds.me!.id)) {
433
+ const deviceJids = devices.map(d => d.attrs.jid)
434
+ logger.info({ deviceJids }, 'got my own devices')
435
+ }
436
+
437
+ break
438
+ case 'server_sync':
439
+ const update = getBinaryNodeChild(node, 'collection')
440
+ if(update) {
441
+ const name = update.attrs.name as WAPatchName
442
+ await resyncAppState([name], false)
443
+ }
444
+
445
+ break
446
+ case 'picture':
447
+ const setPicture = getBinaryNodeChild(node, 'set')
448
+ const delPicture = getBinaryNodeChild(node, 'delete')
449
+
450
+ ev.emit('contacts.update', [{
451
+ id: jidNormalizedUser(node?.attrs?.jid) || ((setPicture || delPicture)?.attrs?.hash) || '',
452
+ imgUrl: setPicture ? 'changed' : 'removed'
453
+ }])
454
+
455
+ if(isJidGroup(from)) {
456
+ const node = setPicture || delPicture
457
+ result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON
458
+
459
+ if(setPicture) {
460
+ result.messageStubParameters = [setPicture.attrs.id]
461
+ }
462
+
463
+ result.participant = node?.attrs.author
464
+ result.key = {
465
+ ...result.key || {},
466
+ participant: setPicture?.attrs.author
467
+ }
468
+ }
469
+
470
+ break
471
+ case 'account_sync':
472
+ if(child.tag === 'disappearing_mode') {
473
+ const newDuration = +child.attrs.duration
474
+ const timestamp = +child.attrs.t
475
+
476
+ logger.info({ newDuration }, 'updated account disappearing mode')
477
+
478
+ ev.emit('creds.update', {
479
+ accountSettings: {
480
+ ...authState.creds.accountSettings,
481
+ defaultDisappearingMode: {
482
+ ephemeralExpiration: newDuration,
483
+ ephemeralSettingTimestamp: timestamp,
484
+ },
485
+ }
486
+ })
487
+ } else if(child.tag === 'blocklist') {
488
+ const blocklists = getBinaryNodeChildren(child, 'item')
489
+
490
+ for(const { attrs } of blocklists) {
491
+ const blocklist = [attrs.jid]
492
+ const type = (attrs.action === 'block') ? 'add' : 'remove'
493
+ ev.emit('blocklist.update', { blocklist, type })
494
+ }
495
+ }
496
+
497
+ break
498
+ case 'link_code_companion_reg':
499
+ const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg')
500
+ const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref'))
501
+ const primaryIdentityPublicKey = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub'))
502
+ const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'))
503
+ const codePairingPublicKey = decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped)
504
+ const companionSharedKey = Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey)
505
+ const random = randomBytes(32)
506
+ const linkCodeSalt = randomBytes(32)
507
+ const linkCodePairingExpanded = hkdf(companionSharedKey, 32, {
508
+ salt: linkCodeSalt,
509
+ info: 'link_code_pairing_key_bundle_encryption_key'
510
+ })
511
+ const encryptPayload = Buffer.concat([Buffer.from(authState.creds.signedIdentityKey.public), primaryIdentityPublicKey, random])
512
+ const encryptIv = randomBytes(12)
513
+ const encrypted = aesEncryptGCM(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0))
514
+ const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted])
515
+ const identitySharedKey = Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey)
516
+ const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random])
517
+ authState.creds.advSecretKey = hkdf(identityPayload, 32, { info: 'adv_secret' }).toString('base64')
518
+ await query({
519
+ tag: 'iq',
520
+ attrs: {
521
+ to: S_WHATSAPP_NET,
522
+ type: 'set',
523
+ id: sock.generateMessageTag(),
524
+ xmlns: 'md'
525
+ },
526
+ content: [
527
+ {
528
+ tag: 'link_code_companion_reg',
529
+ attrs: {
530
+ jid: authState.creds.me!.id,
531
+ stage: 'companion_finish',
532
+ },
533
+ content: [
534
+ {
535
+ tag: 'link_code_pairing_wrapped_key_bundle',
536
+ attrs: {},
537
+ content: encryptedPayload
538
+ },
539
+ {
540
+ tag: 'companion_identity_public',
541
+ attrs: {},
542
+ content: authState.creds.signedIdentityKey.public
543
+ },
544
+ {
545
+ tag: 'link_code_pairing_ref',
546
+ attrs: {},
547
+ content: ref
548
+ }
549
+ ]
550
+ }
551
+ ]
552
+ })
553
+ authState.creds.registered = true
554
+ ev.emit('creds.update', authState.creds)
555
+ }
556
+
557
+ if(Object.keys(result).length) {
558
+ return result
559
+ }
560
+ }
561
+
562
+ function decipherLinkPublicKey(data: Uint8Array | Buffer) {
563
+ const buffer = toRequiredBuffer(data)
564
+ const salt = buffer.slice(0, 32)
565
+ const secretKey = derivePairingCodeKey(authState.creds.pairingCode!, salt)
566
+ const iv = buffer.slice(32, 48)
567
+ const payload = buffer.slice(48, 80)
568
+ return aesDecryptCTR(payload, secretKey, iv)
569
+ }
570
+
571
+ function toRequiredBuffer(data: Uint8Array | Buffer | undefined) {
572
+ if(data === undefined) {
573
+ throw new Boom('Invalid buffer', { statusCode: 400 })
574
+ }
575
+
576
+ return data instanceof Buffer ? data : Buffer.from(data)
577
+ }
578
+
579
+ const willSendMessageAgain = (id: string, participant: string) => {
580
+ const key = `${id}:${participant}`
581
+ const retryCount = msgRetryCache.get<number>(key) || 0
582
+ return retryCount < maxMsgRetryCount
583
+ }
584
+
585
+ const updateSendMessageAgainCount = (id: string, participant: string) => {
586
+ const key = `${id}:${participant}`
587
+ const newValue = (msgRetryCache.get<number>(key) || 0) + 1
588
+ msgRetryCache.set(key, newValue)
589
+ }
590
+
591
+ const sendMessagesAgain = async(
592
+ key: proto.IMessageKey,
593
+ ids: string[],
594
+ retryNode: BinaryNode
595
+ ) => {
596
+ const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id })))
597
+ const remoteJid = key.remoteJid!
598
+ const participant = key.participant || remoteJid
599
+ // if it's the primary jid sending the request
600
+ // just re-send the message to everyone
601
+ // prevents the first message decryption failure
602
+ const sendToAll = !jidDecode(participant)?.device
603
+ await assertSessions([participant], true)
604
+
605
+ if(isJidGroup(remoteJid)) {
606
+ await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } })
607
+ }
608
+
609
+ logger.debug({ participant, sendToAll }, 'forced new session for retry recp')
610
+
611
+ for(let i = 0; i < msgs.length;i++) {
612
+ const msg = msgs[i]
613
+ if(msg) {
614
+ updateSendMessageAgainCount(ids[i], participant)
615
+ const msgRelayOpts: MessageRelayOptions = { messageId: ids[i] }
616
+
617
+ if(sendToAll) {
618
+ msgRelayOpts.useUserDevicesCache = false
619
+ } else {
620
+ msgRelayOpts.participant = {
621
+ jid: participant,
622
+ count: +retryNode.attrs.count
623
+ }
624
+ }
625
+
626
+ await relayMessage(key.remoteJid!, msg, msgRelayOpts)
627
+ } else {
628
+ logger.debug({ jid: key.remoteJid, id: ids[i] }, 'recv retry request, but message not available')
629
+ }
630
+ }
631
+ }
632
+
633
+ const handleReceipt = async(node: BinaryNode) => {
634
+ const { attrs, content } = node
635
+ const isLid = attrs.from.includes('lid')
636
+ const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id)
637
+ const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient
638
+ const fromMe = !attrs.recipient || (attrs.type === 'retry' && isNodeFromMe)
639
+
640
+ const key: proto.IMessageKey = {
641
+ remoteJid,
642
+ id: '',
643
+ fromMe,
644
+ participant: attrs.participant
645
+ }
646
+
647
+ if(shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
648
+ logger.debug({ remoteJid }, 'ignoring receipt from jid')
649
+ await sendMessageAck(node)
650
+ return
651
+ }
652
+
653
+ const ids = [attrs.id]
654
+ if(Array.isArray(content)) {
655
+ const items = getBinaryNodeChildren(content[0], 'item')
656
+ ids.push(...items.map(i => i.attrs.id))
657
+ }
658
+
659
+ await Promise.all([
660
+ processingMutex.mutex(
661
+ async() => {
662
+ const status = getStatusFromReceiptType(attrs.type)
663
+ if(
664
+ typeof status !== 'undefined' &&
665
+ (
666
+ // basically, we only want to know when a message from us has been delivered to/read by the other person
667
+ // or another device of ours has read some messages
668
+ status > proto.WebMessageInfo.Status.DELIVERY_ACK ||
669
+ !isNodeFromMe
670
+ )
671
+ ) {
672
+ if(isJidGroup(remoteJid)) {
673
+ if(attrs.participant) {
674
+ const updateKey: keyof MessageUserReceipt = status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp'
675
+ ev.emit(
676
+ 'message-receipt.update',
677
+ ids.map(id => ({
678
+ key: { ...key, id },
679
+ receipt: {
680
+ userJid: jidNormalizedUser(attrs.participant),
681
+ [updateKey]: +attrs.t
682
+ }
683
+ }))
684
+ )
685
+ }
686
+ } else {
687
+ ev.emit(
688
+ 'messages.update',
689
+ ids.map(id => ({
690
+ key: { ...key, id },
691
+ update: { status }
692
+ }))
693
+ )
694
+ }
695
+ }
696
+
697
+ if(attrs.type === 'retry') {
698
+ // correctly set who is asking for the retry
699
+ key.participant = key.participant || attrs.from
700
+ const retryNode = getBinaryNodeChild(node, 'retry')
701
+ if(willSendMessageAgain(ids[0], key.participant)) {
702
+ if(key.fromMe) {
703
+ try {
704
+ logger.debug({ attrs, key }, 'recv retry request')
705
+ await sendMessagesAgain(key, ids, retryNode!)
706
+ } catch(error) {
707
+ logger.error({ key, ids, trace: error.stack }, 'error in sending message again')
708
+ }
709
+ } else {
710
+ logger.info({ attrs, key }, 'recv retry for not fromMe message')
711
+ }
712
+ } else {
713
+ logger.info({ attrs, key }, 'will not send message again, as sent too many times')
714
+ }
715
+ }
716
+ }
717
+ ),
718
+ sendMessageAck(node)
719
+ ])
720
+ }
721
+
722
+ const handleNotification = async(node: BinaryNode) => {
723
+ const remoteJid = node.attrs.from
724
+ if(shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
725
+ logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification')
726
+ await sendMessageAck(node)
727
+ return
728
+ }
729
+
730
+ await Promise.all([
731
+ processingMutex.mutex(
732
+ async() => {
733
+ const msg = await processNotification(node)
734
+ if(msg) {
735
+ const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me!.id)
736
+ msg.key = {
737
+ remoteJid,
738
+ fromMe,
739
+ participant: node.attrs.participant,
740
+ id: node.attrs.id,
741
+ ...(msg.key || {})
742
+ }
743
+ msg.participant ??= node.attrs.participant
744
+ msg.messageTimestamp = +node.attrs.t
745
+
746
+ const fullMsg = proto.WebMessageInfo.fromObject(msg)
747
+ await upsertMessage(fullMsg, 'append')
748
+ }
749
+ }
750
+ ),
751
+ sendMessageAck(node)
752
+ ])
753
+ }
754
+
755
+ const handleMessage = async(node: BinaryNode) => {
756
+ if(shouldIgnoreJid(node.attrs.from!) && node.attrs.from! !== '@s.whatsapp.net') {
757
+ logger.debug({ key: node.attrs.key }, 'ignored message')
758
+ await sendMessageAck(node)
759
+ return
760
+ }
761
+
762
+ const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(
763
+ node,
764
+ authState.creds.me!.id,
765
+ authState.creds.me!.lid || '',
766
+ signalRepository,
767
+ logger,
768
+ )
769
+
770
+ if(msg.message?.protocolMessage?.type === proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER) {
771
+ if(node.attrs.sender_pn) {
772
+ ev.emit('chats.phoneNumberShare', { lid: node.attrs.from, jid: node.attrs.sender_pn })
773
+ }
774
+ }
775
+
776
+ await Promise.all([
777
+ processingMutex.mutex(
778
+ async() => {
779
+ await decrypt()
780
+ // message failed to decrypt
781
+ if(msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT) {
782
+ retryMutex.mutex(
783
+ async() => {
784
+ if(ws.isOpen) {
785
+ const encNode = getBinaryNodeChild(node, 'enc')
786
+ await sendRetryRequest(node, !encNode)
787
+ if(retryRequestDelayMs) {
788
+ await delay(retryRequestDelayMs)
789
+ }
790
+ } else {
791
+ logger.debug({ node }, 'connection closed, ignoring retry req')
792
+ }
793
+ }
794
+ )
795
+ } else {
796
+ // no type in the receipt => message delivered
797
+ let type: MessageReceiptType = undefined
798
+ let participant = msg.key.participant
799
+ if(category === 'peer') { // special peer message
800
+ type = 'peer_msg'
801
+ } else if(msg.key.fromMe) { // message was sent by us from a different device
802
+ type = 'sender'
803
+ // need to specially handle this case
804
+ if(isJidUser(msg.key.remoteJid!)) {
805
+ participant = author
806
+ }
807
+ } else if(!sendActiveReceipts) {
808
+ type = 'inactive'
809
+ }
810
+
811
+ await sendReceipt(msg.key.remoteJid!, participant!, [msg.key.id!], type)
812
+
813
+ // send ack for history message
814
+ const isAnyHistoryMsg = getHistoryMsg(msg.message!)
815
+ if(isAnyHistoryMsg) {
816
+ const jid = jidNormalizedUser(msg.key.remoteJid!)
817
+ await sendReceipt(jid, undefined, [msg.key.id!], 'hist_sync')
818
+ }
819
+ }
820
+
821
+ cleanMessage(msg, authState.creds.me!.id)
822
+
823
+ await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify')
824
+ }
825
+ ),
826
+ sendMessageAck(node)
827
+ ])
828
+ }
829
+
830
+ const handleCall = async(node: BinaryNode) => {
831
+ const { attrs } = node
832
+ const [infoChild] = getAllBinaryNodeChildren(node)
833
+ const callId = infoChild.attrs['call-id']
834
+ const from = infoChild.attrs.from || infoChild.attrs['call-creator']
835
+ const status = getCallStatusFromNode(infoChild)
836
+ const call: WACallEvent = {
837
+ chatId: attrs.from,
838
+ from,
839
+ id: callId,
840
+ date: new Date(+attrs.t * 1000),
841
+ offline: !!attrs.offline,
842
+ status,
843
+ }
844
+
845
+ if(status === 'offer') {
846
+ call.isVideo = !!getBinaryNodeChild(infoChild, 'video')
847
+ call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid']
848
+ call.groupJid = infoChild.attrs['group-jid']
849
+ callOfferCache.set(call.id, call)
850
+ }
851
+
852
+ const existingCall = callOfferCache.get<WACallEvent>(call.id)
853
+
854
+ // use existing call info to populate this event
855
+ if(existingCall) {
856
+ call.isVideo = existingCall.isVideo
857
+ call.isGroup = existingCall.isGroup
858
+ }
859
+
860
+ // delete data once call has ended
861
+ if(status === 'reject' || status === 'accept' || status === 'timeout') {
862
+ callOfferCache.del(call.id)
863
+ }
864
+
865
+ ev.emit('call', [call])
866
+
867
+ await sendMessageAck(node)
868
+ }
869
+
870
+ const handleBadAck = async({ attrs }: BinaryNode) => {
871
+ const key: WAMessageKey = { remoteJid: attrs.from, fromMe: true, id: attrs.id }
872
+ // current hypothesis is that if pash is sent in the ack
873
+ // it means -- the message hasn't reached all devices yet
874
+ // we'll retry sending the message here
875
+ if(attrs.phash) {
876
+ logger.info({ attrs }, 'received phash in ack, resending message...')
877
+ const msg = await getMessage(key)
878
+ if(msg) {
879
+ await relayMessage(key.remoteJid!, msg, { messageId: key.id!, useUserDevicesCache: false })
880
+ } else {
881
+ logger.warn({ attrs }, 'could not send message again, as it was not found')
882
+ }
883
+ }
884
+
885
+ // error in acknowledgement,
886
+ // device could not display the message
887
+ if(attrs.error) {
888
+ logger.warn({ attrs }, 'received error in ack')
889
+ ev.emit(
890
+ 'messages.update',
891
+ [
892
+ {
893
+ key,
894
+ update: {
895
+ status: WAMessageStatus.ERROR,
896
+ messageStubParameters: [
897
+ attrs.error
898
+ ]
899
+ }
900
+ }
901
+ ]
902
+ )
903
+ }
904
+ }
905
+
906
+ /// processes a node with the given function
907
+ /// and adds the task to the existing buffer if we're buffering events
908
+ const processNodeWithBuffer = async<T>(
909
+ node: BinaryNode,
910
+ identifier: string,
911
+ exec: (node: BinaryNode) => Promise<T>
912
+ ) => {
913
+ ev.buffer()
914
+ await execTask()
915
+ ev.flush()
916
+
917
+ function execTask() {
918
+ return exec(node)
919
+ .catch(err => onUnexpectedError(err, identifier))
920
+ }
921
+ }
922
+
923
+ // recv a message
924
+ ws.on('CB:message', (node: BinaryNode) => {
925
+ processNodeWithBuffer(node, 'processing message', handleMessage)
926
+ })
927
+
928
+ ws.on('CB:call', async(node: BinaryNode) => {
929
+ processNodeWithBuffer(node, 'handling call', handleCall)
930
+ })
931
+
932
+ ws.on('CB:receipt', node => {
933
+ processNodeWithBuffer(node, 'handling receipt', handleReceipt)
934
+ })
935
+
936
+ ws.on('CB:notification', async(node: BinaryNode) => {
937
+ processNodeWithBuffer(node, 'handling notification', handleNotification)
938
+ })
939
+
940
+ ws.on('CB:ack,class:message', (node: BinaryNode) => {
941
+ handleBadAck(node)
942
+ .catch(error => onUnexpectedError(error, 'handling bad ack'))
943
+ })
944
+
945
+ ev.on('call', ([ call ]) => {
946
+ // missed call + group call notification message generation
947
+ if(call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) {
948
+ const msg: proto.IWebMessageInfo = {
949
+ key: {
950
+ remoteJid: call.chatId,
951
+ id: call.id,
952
+ fromMe: false
953
+ },
954
+ messageTimestamp: unixTimestampSeconds(call.date),
955
+ }
956
+ if(call.status === 'timeout') {
957
+ if(call.isGroup) {
958
+ msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_GROUP_VIDEO : WAMessageStubType.CALL_MISSED_GROUP_VOICE
959
+ } else {
960
+ msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_VIDEO : WAMessageStubType.CALL_MISSED_VOICE
961
+ }
962
+ } else {
963
+ msg.message = { call: { callKey: Buffer.from(call.id) } }
964
+ }
965
+
966
+ const protoMsg = proto.WebMessageInfo.fromObject(msg)
967
+ upsertMessage(protoMsg, call.offline ? 'append' : 'notify')
968
+ }
969
+ })
970
+
971
+ ev.on('connection.update', ({ isOnline }) => {
972
+ if(typeof isOnline !== 'undefined') {
973
+ sendActiveReceipts = isOnline
974
+ logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`)
975
+ }
976
+ })
977
+
978
+ return {
979
+ ...sock,
980
+ sendMessageAck,
981
+ sendRetryRequest,
982
+ offerCall,
983
+ rejectCall
984
+ }
985
+ }