@hansaka02/baileys 7.3.2 → 7.3.6

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 (210) hide show
  1. package/lib/Defaults/baileys-version.json +2 -2
  2. package/lib/Defaults/connection.js +51 -0
  3. package/lib/Defaults/constants.js +74 -0
  4. package/lib/Defaults/history.js +19 -0
  5. package/lib/Defaults/index.js +36 -142
  6. package/lib/Defaults/media.js +48 -0
  7. package/lib/Defaults/prefix.js +18 -0
  8. package/lib/Signal/Group/group-session-builder.js +10 -42
  9. package/lib/Signal/Group/group_cipher.js +9 -6
  10. package/lib/Signal/Group/index.js +39 -53
  11. package/lib/Signal/Group/keyhelper.js +8 -41
  12. package/lib/Signal/Group/sender-chain-key.js +5 -18
  13. package/lib/Signal/Group/sender-key-distribution-message.js +7 -7
  14. package/lib/Signal/Group/sender-key-message.js +12 -8
  15. package/lib/Signal/Group/sender-key-record.js +7 -16
  16. package/lib/Signal/Group/sender-key-state.js +15 -61
  17. package/lib/Signal/Group/sender-message-key.js +2 -2
  18. package/lib/Signal/libsignal.js +237 -177
  19. package/lib/Signal/lid-mapping.js +128 -71
  20. package/lib/Socket/Client/types.js +2 -2
  21. package/lib/Socket/Client/websocket.js +25 -16
  22. package/lib/Socket/business.js +46 -33
  23. package/lib/Socket/chats.js +286 -170
  24. package/lib/Socket/community.js +215 -77
  25. package/lib/Socket/groups.js +77 -61
  26. package/lib/Socket/index.js +4 -4
  27. package/lib/Socket/messages-recv.js +629 -457
  28. package/lib/Socket/messages-send.js +645 -656
  29. package/lib/Socket/mex.js +61 -0
  30. package/lib/Socket/newsletter.js +166 -245
  31. package/lib/Socket/socket.js +396 -170
  32. package/lib/Store/index.js +27 -11
  33. package/lib/Store/make-cache-manager-store.js +14 -15
  34. package/lib/Store/make-in-memory-store.js +28 -24
  35. package/lib/Types/LabelAssociation.js +2 -2
  36. package/lib/Types/Message.js +6 -6
  37. package/lib/Types/MexUpdates.js +5 -5
  38. package/lib/Types/Newsletter.js +32 -25
  39. package/lib/Types/State.js +4 -4
  40. package/lib/Types/index.js +28 -12
  41. package/lib/Utils/auth-utils.js +212 -375
  42. package/lib/Utils/baileys-event-stream.js +68 -69
  43. package/lib/Utils/browser-utils.js +43 -0
  44. package/lib/Utils/business.js +63 -53
  45. package/lib/Utils/chat-utils.js +241 -106
  46. package/lib/Utils/crypto.js +25 -45
  47. package/lib/Utils/decode-wa-message.js +361 -311
  48. package/lib/Utils/event-buffer.js +97 -42
  49. package/lib/Utils/generics.js +90 -207
  50. package/lib/Utils/history.js +29 -27
  51. package/lib/Utils/index.js +28 -14
  52. package/lib/Utils/link-preview.js +24 -62
  53. package/lib/Utils/logger.js +5 -5
  54. package/lib/Utils/lt-hash.js +29 -23
  55. package/lib/Utils/make-mutex.js +26 -28
  56. package/lib/Utils/message-retry-manager.js +55 -7
  57. package/lib/Utils/messages-media.js +434 -247
  58. package/lib/Utils/messages.js +963 -917
  59. package/lib/Utils/noise-handler.js +60 -20
  60. package/lib/Utils/pre-key-manager.js +126 -0
  61. package/lib/Utils/process-message.js +216 -141
  62. package/lib/Utils/signal.js +75 -37
  63. package/lib/Utils/use-multi-file-auth-state.js +18 -22
  64. package/lib/Utils/validate-connection.js +96 -66
  65. package/lib/WABinary/constants.js +1268 -1268
  66. package/lib/WABinary/decode.js +62 -34
  67. package/lib/WABinary/encode.js +57 -36
  68. package/lib/WABinary/generic-utils.js +4 -4
  69. package/lib/WABinary/index.js +27 -11
  70. package/lib/WABinary/jid-utils.js +58 -11
  71. package/lib/WAM/constants.js +19064 -11563
  72. package/lib/WAM/encode.js +71 -14
  73. package/lib/WAM/index.js +27 -11
  74. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +20 -16
  75. package/lib/WAUSync/Protocols/USyncContactProtocol.js +2 -2
  76. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +7 -4
  77. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +2 -2
  78. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +0 -2
  79. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +2 -2
  80. package/lib/WAUSync/Protocols/index.js +27 -11
  81. package/lib/WAUSync/USyncQuery.js +51 -28
  82. package/lib/WAUSync/index.js +27 -11
  83. package/lib/index.js +60 -31
  84. package/package.json +12 -17
  85. package/WAProto/AICommon/AICommon.d.ts +0 -11702
  86. package/WAProto/Adv/Adv.d.ts +0 -643
  87. package/WAProto/BotMetadata/BotMetadata.d.ts +0 -5654
  88. package/WAProto/Cert/Cert.d.ts +0 -613
  89. package/WAProto/ChatLockSettings/ChatLockSettings.d.ts +0 -476
  90. package/WAProto/CompanionReg/CompanionReg.d.ts +0 -1361
  91. package/WAProto/DeviceCapabilities/DeviceCapabilities.d.ts +0 -577
  92. package/WAProto/E2E/E2E.d.ts +0 -41724
  93. package/WAProto/Ephemeral/Ephemeral.d.ts +0 -114
  94. package/WAProto/HistorySync/HistorySync.d.ts +0 -51700
  95. package/WAProto/LidMigrationSyncPayload/LidMigrationSyncPayload.d.ts +0 -229
  96. package/WAProto/MdStorageChatRowOpaqueData/MdStorageChatRowOpaqueData.d.ts +0 -583
  97. package/WAProto/MdStorageMsgRowOpaqueData/MdStorageMsgRowOpaqueData.d.ts +0 -42897
  98. package/WAProto/MmsRetry/MmsRetry.d.ts +0 -243
  99. package/WAProto/Protocol/Protocol.d.ts +0 -270
  100. package/WAProto/Reporting/Reporting.d.ts +0 -371
  101. package/WAProto/ServerSync/ServerSync.d.ts +0 -1285
  102. package/WAProto/SignalLocalStorageProtocol/SignalLocalStorageProtocol.d.ts +0 -1868
  103. package/WAProto/SignalWhisperTextProtocol/SignalWhisperTextProtocol.d.ts +0 -767
  104. package/WAProto/StatusAttributions/StatusAttributions.d.ts +0 -1027
  105. package/WAProto/SyncAction/SyncAction.d.ts +0 -11193
  106. package/WAProto/UserPassword/UserPassword.d.ts +0 -363
  107. package/WAProto/VnameCert/VnameCert.d.ts +0 -821
  108. package/WAProto/Wa6/Wa6.d.ts +0 -2128
  109. package/WAProto/Web/Web.d.ts +0 -46383
  110. package/WAProto/index.d.ts +0 -55
  111. package/lib/Defaults/index.d.ts +0 -77
  112. package/lib/Signal/Group/ciphertext-message.d.ts +0 -9
  113. package/lib/Signal/Group/group-session-builder.d.ts +0 -17
  114. package/lib/Signal/Group/group_cipher.d.ts +0 -19
  115. package/lib/Signal/Group/index.d.ts +0 -11
  116. package/lib/Signal/Group/keyhelper.d.ts +0 -16
  117. package/lib/Signal/Group/sender-chain-key.d.ts +0 -14
  118. package/lib/Signal/Group/sender-key-distribution-message.d.ts +0 -17
  119. package/lib/Signal/Group/sender-key-message.d.ts +0 -19
  120. package/lib/Signal/Group/sender-key-name.d.ts +0 -19
  121. package/lib/Signal/Group/sender-key-record.d.ts +0 -32
  122. package/lib/Signal/Group/sender-key-state.d.ts +0 -44
  123. package/lib/Signal/Group/sender-message-key.d.ts +0 -11
  124. package/lib/Signal/libsignal.d.ts +0 -8
  125. package/lib/Signal/lid-mapping.d.ts +0 -28
  126. package/lib/Socket/Client/index.d.ts +0 -2
  127. package/lib/Socket/Client/types.d.ts +0 -16
  128. package/lib/Socket/Client/websocket.d.ts +0 -13
  129. package/lib/Socket/business.d.ts +0 -187
  130. package/lib/Socket/chats.d.ts +0 -97
  131. package/lib/Socket/community.d.ts +0 -129
  132. package/lib/Socket/groups.d.ts +0 -129
  133. package/lib/Socket/index.d.ts +0 -191
  134. package/lib/Socket/messages-recv.d.ts +0 -174
  135. package/lib/Socket/messages-send.d.ts +0 -165
  136. package/lib/Socket/newsletter.d.ts +0 -145
  137. package/lib/Socket/socket.d.ts +0 -45
  138. package/lib/Socket/usync.d.ts +0 -37
  139. package/lib/Socket/usync.js +0 -83
  140. package/lib/Store/index.d.ts +0 -4
  141. package/lib/Store/make-cache-manager-store.d.ts +0 -14
  142. package/lib/Store/make-in-memory-store.d.ts +0 -123
  143. package/lib/Store/make-ordered-dictionary.d.ts +0 -12
  144. package/lib/Store/object-repository.d.ts +0 -10
  145. package/lib/Types/Auth.d.ts +0 -121
  146. package/lib/Types/Bussiness.d.ts +0 -28
  147. package/lib/Types/Call.d.ts +0 -14
  148. package/lib/Types/Chat.d.ts +0 -143
  149. package/lib/Types/Contact.d.ts +0 -23
  150. package/lib/Types/Events.d.ts +0 -226
  151. package/lib/Types/GroupMetadata.d.ts +0 -66
  152. package/lib/Types/Label.d.ts +0 -48
  153. package/lib/Types/LabelAssociation.d.ts +0 -35
  154. package/lib/Types/Message.d.ts +0 -484
  155. package/lib/Types/MexUpdates.d.ts +0 -9
  156. package/lib/Types/Newsletter.d.ts +0 -109
  157. package/lib/Types/Product.d.ts +0 -92
  158. package/lib/Types/Signal.d.ts +0 -98
  159. package/lib/Types/Socket.d.ts +0 -141
  160. package/lib/Types/State.d.ts +0 -41
  161. package/lib/Types/USync.d.ts +0 -26
  162. package/lib/Types/index.d.ts +0 -80
  163. package/lib/Utils/auth-utils.d.ts +0 -21
  164. package/lib/Utils/baileys-event-stream.d.ts +0 -18
  165. package/lib/Utils/business.d.ts +0 -29
  166. package/lib/Utils/chat-utils.d.ts +0 -82
  167. package/lib/Utils/crypto.d.ts +0 -56
  168. package/lib/Utils/decode-wa-message.d.ts +0 -53
  169. package/lib/Utils/event-buffer.d.ts +0 -39
  170. package/lib/Utils/generics.d.ts +0 -117
  171. package/lib/Utils/history.d.ts +0 -23
  172. package/lib/Utils/index.d.ts +0 -20
  173. package/lib/Utils/link-preview.d.ts +0 -23
  174. package/lib/Utils/logger.d.ts +0 -13
  175. package/lib/Utils/lt-hash.d.ts +0 -14
  176. package/lib/Utils/make-mutex.d.ts +0 -9
  177. package/lib/Utils/message-retry-manager.d.ts +0 -88
  178. package/lib/Utils/messages-media.d.ts +0 -135
  179. package/lib/Utils/messages.d.ts +0 -105
  180. package/lib/Utils/noise-handler.d.ts +0 -20
  181. package/lib/Utils/process-message.d.ts +0 -49
  182. package/lib/Utils/signal.d.ts +0 -42
  183. package/lib/Utils/use-mongo-file-auth-state.d.ts +0 -6
  184. package/lib/Utils/use-mongo-file-auth-state.js +0 -84
  185. package/lib/Utils/use-multi-file-auth-state.d.ts +0 -13
  186. package/lib/Utils/use-single-file-auth-state.d.ts +0 -13
  187. package/lib/Utils/use-single-file-auth-state.js +0 -80
  188. package/lib/Utils/validate-connection.d.ts +0 -13
  189. package/lib/WABinary/constants.d.ts +0 -30
  190. package/lib/WABinary/decode.d.ts +0 -9
  191. package/lib/WABinary/encode.d.ts +0 -3
  192. package/lib/WABinary/generic-utils.d.ts +0 -28
  193. package/lib/WABinary/index.d.ts +0 -5
  194. package/lib/WABinary/jid-utils.d.ts +0 -58
  195. package/lib/WABinary/types.d.ts +0 -22
  196. package/lib/WAM/BinaryInfo.d.ts +0 -16
  197. package/lib/WAM/constants.d.ts +0 -47
  198. package/lib/WAM/encode.d.ts +0 -3
  199. package/lib/WAM/index.d.ts +0 -3
  200. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.d.ts +0 -28
  201. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +0 -10
  202. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +0 -26
  203. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +0 -14
  204. package/lib/WAUSync/Protocols/USyncLIDProtocol.d.ts +0 -10
  205. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +0 -14
  206. package/lib/WAUSync/Protocols/index.d.ts +0 -6
  207. package/lib/WAUSync/USyncQuery.d.ts +0 -31
  208. package/lib/WAUSync/USyncUser.d.ts +0 -12
  209. package/lib/WAUSync/index.d.ts +0 -3
  210. package/lib/index.d.ts +0 -13
@@ -1,75 +1,80 @@
1
1
  "use strict"
2
2
 
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k
5
- var desc = Object.getOwnPropertyDescriptor(m, k)
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k] } }
8
- }
9
- Object.defineProperty(o, k2, desc)
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k
12
- o[k2] = m[k]
13
- }))
14
-
15
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
- Object.defineProperty(o, "default", { enumerable: true, value: v })
17
- }) : function(o, v) {
18
- o["default"] = v
19
- })
3
+ Object.defineProperty(exports, "__esModule", { value: true })
20
4
 
21
- var __importStar = (this && this.__importStar) || function (mod) {
22
- if (mod && mod.__esModule) return mod
23
- var result = {}
24
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k)
25
- __setModuleDefault(result, mod)
26
- return result
27
- }
5
+ const { Boom } = require("@hapi/boom")
6
+ const { exec } = require("child_process")
7
+ const { once } = require("events")
8
+ const {
9
+ createHash,
10
+ randomBytes,
11
+ createHmac,
12
+ createCipheriv,
13
+ createDecipheriv
14
+ } = require("crypto")
15
+ const {
16
+ promises,
17
+ createReadStream,
18
+ createWriteStream
19
+ } = require("fs")
20
+ const {
21
+ parseBuffer,
22
+ parseFile,
23
+ parseStream
24
+ } = require('music-metadata')
25
+ const {
26
+ default: decoder
27
+ } = require("audio-decode")
28
+ const { tmpdir } = require("os")
29
+ const { join } = require("path")
30
+ const {
31
+ Readable,
32
+ Transform
33
+ } = require("stream")
34
+ const { proto } = require("../../WAProto")
35
+ const {
36
+ MEDIA_PATH_MAP,
37
+ MEDIA_HKDF_KEY_MAPPING
38
+ } = require("../Defaults/media")
39
+ const { DEFAULT_ORIGIN } = require("../Defaults/constants")
40
+ const {
41
+ getBinaryNodeChild,
42
+ getBinaryNodeChildBuffer,
43
+ jidNormalizedUser
44
+ } = require("../WABinary")
45
+ const {
46
+ aesDecryptGCM,
47
+ aesEncryptGCM,
48
+ hkdf
49
+ } = require("./crypto")
50
+ const { generateMessageID } = require("./generics")
28
51
 
29
- var __importDefault = (this && this.__importDefault) || function (mod) {
30
- return (mod && mod.__esModule) ? mod : { "default": mod }
31
- }
52
+ const getTmpFilesDirectory = () => tmpdir()
32
53
 
33
- Object.defineProperty(exports, "__esModule", { value: true })
54
+ const getImageProcessingLibrary = () => {
55
+ let sharp, jimp
56
+
57
+ try {
58
+ sharp = require('sharp')
59
+ } catch {}
34
60
 
35
- const boom_1 = require("@hapi/boom")
36
- const axios_1 = __importDefault(require("axios"))
37
- const child_process_1 = require("child_process")
38
- const Crypto = __importStar(require("crypto"))
39
- const events_1 = require("events")
40
- const fs_1 = require("fs")
41
- const os_1 = require("os")
42
- const path_1 = require("path")
43
- const stream_1 = require("stream")
44
- const WAProto_1 = require("../../WAProto")
45
- const Defaults_1 = require("../Defaults")
46
- const WABinary_1 = require("../WABinary")
47
- const crypto_1 = require("./crypto")
48
- const generics_1 = require("./generics")
49
-
50
- const getImageProcessingLibrary = async () => {
51
- const [_jimp, sharp] = await Promise.all([
52
- (async () => {
53
- const jimp = await (Promise.resolve().then(() => __importStar(require('jimp'))).catch(() => { }))
54
- return jimp
55
- })(),
56
- (async () => {
57
- const sharp = await (Promise.resolve().then(() => __importStar(require('sharp'))).catch(() => { }))
58
- return sharp
59
- })()
60
- ])
61
61
  if (sharp) {
62
62
  return { sharp }
63
63
  }
64
- const jimp = _jimp?.default || _jimp
64
+
65
+ try {
66
+ jimp = require('jimp')
67
+ } catch {}
68
+
65
69
  if (jimp) {
66
70
  return { jimp }
67
71
  }
68
- throw new boom_1.Boom('No image processing library available')
72
+
73
+ throw new Boom('No image processing library available')
69
74
  }
70
75
 
71
76
  const hkdfInfoKey = (type) => {
72
- const hkdfInfo = Defaults_1.MEDIA_HKDF_KEY_MAPPING[type]
77
+ const hkdfInfo = MEDIA_HKDF_KEY_MAPPING[type]
73
78
  return `WhatsApp ${hkdfInfo} Keys`
74
79
  }
75
80
 
@@ -78,9 +83,9 @@ const getRawMediaUploadData = async (media, mediaType, logger) => {
78
83
 
79
84
  logger?.debug('got stream for raw upload')
80
85
 
81
- const hasher = Crypto.createHash('sha256')
82
- const filePath = path_1.join(os_1.tmpdir(), mediaType + generics_1.generateMessageID())
83
- const fileWriteStream = fs_1.createWriteStream(filePath)
86
+ const hasher = createHash('sha256')
87
+ const filePath = join(getTmpFilesDirectory(), mediaType + generateMessageID())
88
+ const fileWriteStream = createWriteStream(filePath)
84
89
 
85
90
  let fileLength = 0
86
91
 
@@ -90,12 +95,12 @@ const getRawMediaUploadData = async (media, mediaType, logger) => {
90
95
  hasher.update(data)
91
96
 
92
97
  if (!fileWriteStream.write(data)) {
93
- await events_1.once(fileWriteStream, 'drain')
98
+ await once(fileWriteStream, 'drain')
94
99
  }
95
100
  }
96
101
 
97
102
  fileWriteStream.end()
98
- await events_1.once(fileWriteStream, 'finish')
103
+ await once(fileWriteStream, 'finish')
99
104
  stream.destroy()
100
105
 
101
106
  const fileSha256 = hasher.digest()
@@ -113,7 +118,7 @@ const getRawMediaUploadData = async (media, mediaType, logger) => {
113
118
  stream.destroy()
114
119
 
115
120
  try {
116
- await fs_1.promises.unlink(filePath)
121
+ await promises.unlink(filePath)
117
122
  }
118
123
  catch {
119
124
  //
@@ -125,85 +130,60 @@ const getRawMediaUploadData = async (media, mediaType, logger) => {
125
130
  /** generates all the keys required to encrypt/decrypt & sign a media message */
126
131
  async function getMediaKeys(buffer, mediaType) {
127
132
  if (!buffer) {
128
- throw new boom_1.Boom('Cannot derive from empty media key')
133
+ throw new Boom('Cannot derive from empty media key')
129
134
  }
130
135
  if (typeof buffer === 'string') {
131
136
  buffer = Buffer.from(buffer.replace('data:base64,', ''), 'base64')
132
137
  }
133
138
  // expand using HKDF to 112 bytes, also pass in the relevant app info
134
- const expandedMediaKey = await crypto_1.hkdf(buffer, 112, { info: hkdfInfoKey(mediaType) })
139
+ const expandedMediaKey = await hkdf(buffer, 112, { info: hkdfInfoKey(mediaType) })
135
140
  return {
136
141
  iv: expandedMediaKey.slice(0, 16),
137
142
  cipherKey: expandedMediaKey.slice(16, 48),
138
- macKey: expandedMediaKey.slice(48, 80),
143
+ macKey: expandedMediaKey.slice(48, 80)
139
144
  }
140
145
  }
141
146
 
142
147
  /** Extracts video thumb using FFMPEG */
143
- const extractVideoThumb = (videoPath, time = '00:00:00', size = { width: 320 }) => {
144
- return new Promise((resolve, reject) => {
145
- const args = [
146
- '-ss', time,
147
- '-i', videoPath,
148
- '-y',
149
- '-vf', `scale=${size.width}:-1`,
150
- '-vframes', '1',
151
- '-f', 'image2',
152
- '-vcodec', 'mjpeg',
153
- 'pipe:1'
154
- ]
155
-
156
- const ffmpeg = child_process_1.spawn('ffmpeg', args)
157
- const chunks = []
158
- let errorOutput = ''
159
-
160
- ffmpeg.stdout.on('data', chunk => chunks.push(chunk))
161
- ffmpeg.stderr.on('data', data => {
162
- errorOutput += data.toString()
163
- })
164
- ffmpeg.on('error', reject)
165
- ffmpeg.on('close', code => {
166
- if (code === 0) return resolve(Buffer.concat(chunks))
167
- reject(new Error(`ffmpeg exited with code ${code}\n${errorOutput}`))
168
- })
148
+ const extractVideoThumb = async (path, destPath, time, size) => new Promise((resolve, reject) => {
149
+ const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}`
150
+ exec(cmd, err => {
151
+ if (err) {
152
+ reject(err)
153
+ }
154
+ else {
155
+ resolve()
156
+ }
169
157
  })
170
- }
158
+ })
171
159
 
172
160
  const extractImageThumb = async (bufferOrFilePath, width = 32, quality = 50) => {
173
- if (typeof bufferOrFilePath === "string" && bufferOrFilePath.startsWith("http")) {
174
- const response = await axios_1.default.get(bufferOrFilePath, { responseType: "arraybuffer" })
175
- bufferOrFilePath = Buffer.from(response.data)
176
- }
177
- if (bufferOrFilePath instanceof stream_1.Readable) {
161
+ // TODO: Move entirely to sharp, removing jimp as it supports readable streams
162
+ // This will have positive speed and performance impacts as well as minimizing RAM usage.
163
+ if (bufferOrFilePath instanceof Readable) {
178
164
  bufferOrFilePath = await toBuffer(bufferOrFilePath)
179
165
  }
166
+
180
167
  const lib = await getImageProcessingLibrary()
181
- if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
182
- const img = lib.sharp.default(bufferOrFilePath)
168
+
169
+ if ('sharp' in lib && typeof lib.sharp === 'function') {
170
+ const img = lib.sharp(bufferOrFilePath)
183
171
  const dimensions = await img.metadata()
184
- const buffer = await img
185
- .resize({
186
- width,
187
- height: width,
188
- fit: 'contain',
189
- background: { r: 255, g: 255, b: 255, alpha: 0 }
190
- })
191
- .jpeg({ quality })
192
- .toBuffer()
172
+ const buffer = await img.resize(width).jpeg({ quality: 50 }).toBuffer()
193
173
  return {
194
174
  buffer,
195
175
  original: {
196
176
  width: dimensions.width,
197
- height: dimensions.height,
198
- },
177
+ height: dimensions.height
178
+ }
199
179
  }
200
180
  }
201
- else if ('jimp' in lib && typeof lib.jimp?.read === 'function') {
202
- const { read, MIME_JPEG, RESIZE_BEZIER, AUTO } = lib.jimp
181
+ else if ('jimp' in lib && typeof lib.jimp.read === 'function') {
182
+ const { read, MIME_JPEG, RESIZE_BEZIER, AUTO } = lib.jimp
203
183
  const jimp = await read(bufferOrFilePath)
204
184
  const dimensions = {
205
185
  width: jimp.getWidth(),
206
- height: jimp.getHeight()
186
+ height: jimp.getHeight()
207
187
  }
208
188
  const buffer = await jimp
209
189
  .quality(quality)
@@ -215,7 +195,7 @@ const extractImageThumb = async (bufferOrFilePath, width = 32, quality = 50) =>
215
195
  }
216
196
  }
217
197
  else {
218
- throw new boom_1.Boom('No image processing library available')
198
+ throw new Boom('No image processing library available')
219
199
  }
220
200
  }
221
201
 
@@ -224,52 +204,53 @@ const encodeBase64EncodedStringForUpload = (b64) => (encodeURIComponent(b64
224
204
  .replace(/\//g, '_')
225
205
  .replace(/\=+$/, '')))
226
206
 
227
- const generateProfilePicture = async (mediaUpload) => {
228
- let bufferOrFilePath
207
+ const generateProfilePicture = async (mediaUpload, dimensions) => {
208
+ let buffer
209
+
210
+ const { width: w = 640, height: h = 640 } = dimensions || {}
211
+
229
212
  if (Buffer.isBuffer(mediaUpload)) {
230
- bufferOrFilePath = mediaUpload
231
- }
232
- else if ('url' in mediaUpload) {
233
- bufferOrFilePath = mediaUpload.url.toString()
213
+ buffer = mediaUpload
234
214
  }
235
215
  else {
236
- bufferOrFilePath = await toBuffer(mediaUpload.stream)
216
+ // Use getStream to handle all WAMediaUpload types (Buffer, Stream, URL)
217
+ const { stream } = await getStream(mediaUpload)
218
+ // Convert the resulting stream to a buffer
219
+ buffer = await toBuffer(stream)
237
220
  }
238
221
  const lib = await getImageProcessingLibrary()
222
+
239
223
  let img
224
+
240
225
  if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
241
- img = await lib.sharp.default(bufferOrFilePath)
242
- .resize(720, 720, {
243
- fit: 'inside',
244
- })
245
- .jpeg({ quality: 50 })
246
- .toBuffer()
226
+ img = lib.sharp
227
+ .default(buffer)
228
+ .resize(w, h)
229
+ .jpeg({
230
+ quality: 50
231
+ }).toBuffer()
247
232
  }
248
233
  else if ('jimp' in lib && typeof lib.jimp?.read === 'function') {
249
- const { read, MIME_JPEG } = lib.jimp
250
- const image = await read(bufferOrFilePath)
251
- const min = image.getWidth()
252
- const max = image.getHeight()
253
- const cropped = image.crop(0, 0, min, max)
254
- img = await cropped.scaleToFit(720, 720).getBufferAsync(MIME_JPEG)
234
+ const jimp = await lib.jimp.read(buffer)
235
+ const min = Math.min(jimp.width, jimp.height)
236
+ const cropped = jimp.crop({ x: 0, y: 0, w: min, h: min })
237
+ img = cropped.resize({ w, h, mode: lib.jimp.ResizeStrategy.BILINEAR }).getBuffer('image/jpeg', { quality: 50 })
255
238
  }
256
239
  else {
257
- throw new boom_1.Boom('No image processing library available')
240
+ throw new Boom('No image processing library available');
258
241
  }
259
242
  return {
260
- img: await img,
243
+ img: await img
261
244
  }
262
245
  }
263
246
 
264
-
265
247
  /** gets the SHA256 of the given media message */
266
248
  const mediaMessageSHA256B64 = (message) => {
267
249
  const media = Object.values(message)[0]
268
- return (media === null || media === void 0 ? void 0 : media.fileSha256) && Buffer.from(media.fileSha256).toString('base64')
250
+ return media?.fileSha256 && Buffer.from(media.fileSha256).toString('base64')
269
251
  }
270
252
 
271
253
  async function getAudioDuration(buffer) {
272
- const musicMetadata = await Promise.resolve().then(() => __importStar(require('music-metadata')))
273
254
  const options = {
274
255
  duration: true
275
256
  }
@@ -277,15 +258,15 @@ async function getAudioDuration(buffer) {
277
258
  let metadata
278
259
 
279
260
  if (Buffer.isBuffer(buffer)) {
280
- metadata = await musicMetadata.parseBuffer(buffer, undefined, options)
261
+ metadata = await parseBuffer(buffer, undefined, options)
281
262
  }
282
263
  else if (typeof buffer === 'string') {
283
- metadata = await musicMetadata.parseFile(buffer, options)
264
+ metadata = await parseFile(buffer, options)
284
265
  }
285
266
  else {
286
- metadata = await musicMetadata.parseStream(buffer, undefined, options)
267
+ metadata = await parseStream(buffer, undefined, options)
287
268
  }
288
- return metadata.format.duration
269
+ return metadata.format?.duration
289
270
  }
290
271
 
291
272
  /**
@@ -293,23 +274,25 @@ async function getAudioDuration(buffer) {
293
274
  */
294
275
  async function getAudioWaveform(buffer, logger) {
295
276
  try {
296
- const { default: decoder } = await eval('import(\'audio-decode\')')
297
277
  let audioData
278
+
298
279
  if (Buffer.isBuffer(buffer)) {
299
280
  audioData = buffer
300
281
  }
301
282
  else if (typeof buffer === 'string') {
302
- const rStream = fs_1.createReadStream(buffer)
283
+ const rStream = createReadStream(buffer)
303
284
  audioData = await toBuffer(rStream)
304
285
  }
305
286
  else {
306
287
  audioData = await toBuffer(buffer)
307
288
  }
289
+
308
290
  const audioBuffer = await decoder(audioData)
309
291
  const rawData = audioBuffer.getChannelData(0) // We only need to work with one channel of data
310
292
  const samples = 64 // Number of samples we want to have in our final data set
311
293
  const blockSize = Math.floor(rawData.length / samples) // the number of samples in each subdivision
312
294
  const filteredData = []
295
+
313
296
  for (let i = 0; i < samples; i++) {
314
297
  const blockStart = blockSize * i // the location of the first sample in the block
315
298
  let sum = 0
@@ -318,9 +301,11 @@ async function getAudioWaveform(buffer, logger) {
318
301
  }
319
302
  filteredData.push(sum / blockSize) // divide the sum by the block size to get the average
320
303
  }
304
+
321
305
  // This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
322
306
  const multiplier = Math.pow(Math.max(...filteredData), -1)
323
307
  const normalizedData = filteredData.map((n) => n * multiplier)
308
+
324
309
  // Generate waveform like WhatsApp
325
310
  const waveform = new Uint8Array(normalizedData.map((n) => Math.floor(100 * n)))
326
311
  return waveform
@@ -331,7 +316,7 @@ async function getAudioWaveform(buffer, logger) {
331
316
  }
332
317
 
333
318
  const toReadable = (buffer) => {
334
- const readable = new stream_1.Readable({ read: () => { } })
319
+ const readable = new Readable({ read: () => { } })
335
320
  readable.push(buffer)
336
321
  readable.push(null)
337
322
  return readable
@@ -366,30 +351,38 @@ const getStream = async (item, opts) => {
366
351
  return { stream: await getHttpStream(item.url, opts), type: 'remote' }
367
352
  }
368
353
 
369
- return { stream: fs_1.createReadStream(item.url), type: 'file' }
354
+ return { stream: createReadStream(item.url), type: 'file' }
370
355
  }
371
356
 
372
357
  /** generates a thumbnail for a given media, if required */
373
358
  async function generateThumbnail(file, mediaType, options) {
374
359
  let thumbnail
375
360
  let originalImageDimensions
361
+
376
362
  if (mediaType === 'image') {
377
- const { buffer, original } = await extractImageThumb(file, 256, 95)
363
+ const { buffer, original } = await extractImageThumb(file)
364
+
378
365
  thumbnail = buffer.toString('base64')
366
+
379
367
  if (original.width && original.height) {
380
368
  originalImageDimensions = {
381
369
  width: original.width,
382
- height: original.height,
370
+ height: original.height
383
371
  }
384
372
  }
385
373
  }
386
374
  else if (mediaType === 'video') {
375
+ const imgFilename = join(getTmpFilesDirectory(), generateMessageID() + '.jpg')
387
376
  try {
388
- const buff = await extractVideoThumb(file, '00:00:00', { width: 32, height: 32 })
377
+ await extractVideoThumb(file, imgFilename, '00:00:00', { width: 32, height: 32 })
378
+ const buff = await promises.readFile(imgFilename)
379
+
389
380
  thumbnail = buff.toString('base64')
381
+
382
+ await promises.unlink(imgFilename)
390
383
  }
391
384
  catch (err) {
392
- options?.logger?.debug('could not generate video thumb: ' + err)
385
+ options.logger?.debug('could not generate video thumb: ' + err)
393
386
  }
394
387
  }
395
388
  return {
@@ -399,16 +392,25 @@ async function generateThumbnail(file, mediaType, options) {
399
392
  }
400
393
 
401
394
  const getHttpStream = async (url, options = {}) => {
402
- const fetched = await axios_1.default.get(url.toString(), { ...options, responseType: 'stream' })
403
- return fetched.data
395
+ const response = await fetch(url.toString(), {
396
+ dispatcher: options.dispatcher,
397
+ method: 'GET',
398
+ headers: options.headers
399
+ })
400
+
401
+ if (!response.ok) {
402
+ throw new Boom(`Failed to fetch stream from ${url}`, { statusCode: response.status, data: { url } })
403
+ }
404
+
405
+ return response.body instanceof Readable ? response.body : Readable.fromWeb(response.body)
404
406
  }
405
407
 
406
- const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
408
+ /*const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
407
409
  const { stream, type } = await getStream(media, opts)
408
410
  logger?.debug('fetched media stream')
409
411
 
410
- const encFilePath = path_1.join(os_1.tmpdir(), mediaType + generics_1.generateMessageID() + '-plain')
411
- const encFileWriteStream = fs_1.createWriteStream(encFilePath)
412
+ const encFilePath = join(tmpdir(), mediaType + generateMessageID() + '-plain')
413
+ const encFileWriteStream = createWriteStream(encFilePath)
412
414
 
413
415
  let originalFilePath
414
416
  let originalFileStream
@@ -416,12 +418,12 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
416
418
  if (type === 'file') {
417
419
  originalFilePath = media.url.toString()
418
420
  } else if (saveOriginalFileIfRequired) {
419
- originalFilePath = path_1.join(os_1.tmpdir(), mediaType + generics_1.generateMessageID() + '-original')
420
- originalFileStream = fs_1.createWriteStream(originalFilePath)
421
+ originalFilePath = join(tmpdir(), mediaType + generateMessageID() + '-original')
422
+ originalFileStream = createWriteStream(originalFilePath)
421
423
  }
422
424
 
423
425
  let fileLength = 0
424
- const sha256 = Crypto.createHash('sha256')
426
+ const sha256 = createHash('sha256')
425
427
 
426
428
  try {
427
429
  for await (const data of stream) {
@@ -430,7 +432,7 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
430
432
  if (type === 'remote'
431
433
  && opts?.maxContentLength
432
434
  && fileLength + data.length > opts.maxContentLength) {
433
- throw new boom_1.Boom(`content length exceeded when preparing "${type}"`, {
435
+ throw new Boom(`content length exceeded when preparing "${type}"`, {
434
436
  data: { media, type }
435
437
  })
436
438
  }
@@ -439,7 +441,7 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
439
441
  encFileWriteStream.write(data)
440
442
 
441
443
  if (originalFileStream && !originalFileStream.write(data)) {
442
- await events_1.once(originalFileStream, 'drain')
444
+ await once(originalFileStream, 'drain')
443
445
  }
444
446
  }
445
447
 
@@ -466,68 +468,99 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
466
468
  sha256.destroy()
467
469
  stream.destroy()
468
470
  try {
469
- await fs_1.promises.unlink(encFilePath)
471
+ await promises.unlink(encFilePath)
470
472
  if (originalFilePath && didSaveToTmpPath) {
471
- await fs_1.promises.unlink(originalFilePath)
473
+ await promises.unlink(originalFilePath)
472
474
  }
473
475
  } catch (err) {
474
476
  logger?.error({ err }, 'failed deleting tmp files')
475
477
  }
476
478
  throw error
477
479
  }
478
- }
480
+ }*/
479
481
 
480
482
  const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
481
483
  const { stream, type } = await getStream(media, opts)
484
+
482
485
  logger?.debug('fetched media stream')
483
- const mediaKey = Crypto.randomBytes(32)
486
+
487
+ const mediaKey = randomBytes(32)
484
488
  const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType)
485
- const encFilePath = path_1.join(os_1.tmpdir(), mediaType + generics_1.generateMessageID() + '-enc')
486
- const encFileWriteStream = fs_1.createWriteStream(encFilePath)
487
- let originalFileStream
489
+ const encFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageID() + '-enc')
490
+ const encFileWriteStream = createWriteStream(encFilePath)
491
+
492
+ let originalFileStream;
488
493
  let originalFilePath
494
+
489
495
  if (saveOriginalFileIfRequired) {
490
- originalFilePath = path_1.join(os_1.tmpdir(), mediaType + generics_1.generateMessageID() + '-original')
491
- originalFileStream = fs_1.createWriteStream(originalFilePath)
496
+ originalFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageID() + '-original')
497
+ originalFileStream = createWriteStream(originalFilePath)
492
498
  }
499
+
493
500
  let fileLength = 0
494
- const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv)
495
- const hmac = Crypto.createHmac('sha256', macKey).update(iv)
496
- const sha256Plain = Crypto.createHash('sha256')
497
- const sha256Enc = Crypto.createHash('sha256')
498
- const onChunk = (buff) => {
501
+
502
+ const aes = createCipheriv('aes-256-cbc', cipherKey, iv)
503
+ const hmac = createHmac('sha256', macKey).update(iv)
504
+ const sha256Plain = createHash('sha256');
505
+ const sha256Enc = createHash('sha256')
506
+
507
+ const onChunk = async (buff) => {
499
508
  sha256Enc.update(buff)
500
509
  hmac.update(buff)
501
- encFileWriteStream.write(buff)
510
+
511
+ // Handle backpressure: if write returns false, wait for drain
512
+ if (!encFileWriteStream.write(buff)) {
513
+ await once(encFileWriteStream, 'drain')
514
+ }
502
515
  }
516
+
503
517
  try {
504
518
  for await (const data of stream) {
505
519
  fileLength += data.length
506
- if (type === 'remote'
507
- && opts?.maxContentLength
508
- && fileLength + data.length > opts.maxContentLength) {
509
- throw new boom_1.Boom(`content length exceeded when encrypting "${type}"`, {
520
+
521
+ if (type === 'remote' &&
522
+ opts?.maxContentLength &&
523
+ fileLength + data.length > opts.maxContentLength) {
524
+ throw new Boom(`content length exceeded when encrypting "${type}"`, {
510
525
  data: { media, type }
511
526
  })
512
527
  }
528
+
513
529
  if (originalFileStream) {
514
530
  if (!originalFileStream.write(data)) {
515
- await events_1.once(originalFileStream, 'drain')
531
+ await once(originalFileStream, 'drain')
516
532
  }
517
533
  }
534
+
518
535
  sha256Plain.update(data)
519
- onChunk(aes.update(data))
536
+
537
+ await onChunk(aes.update(data))
520
538
  }
521
- onChunk(aes.final())
539
+
540
+ await onChunk(aes.final())
522
541
  const mac = hmac.digest().slice(0, 10)
542
+
523
543
  sha256Enc.update(mac)
544
+
524
545
  const fileSha256 = sha256Plain.digest()
525
546
  const fileEncSha256 = sha256Enc.digest()
547
+
526
548
  encFileWriteStream.write(mac)
549
+
550
+ const encFinishPromise = once(encFileWriteStream, 'finish')
551
+ const originalFinishPromise = originalFileStream ? once(originalFileStream, 'finish') : Promise.resolve()
552
+
527
553
  encFileWriteStream.end()
528
- originalFileStream?.end?.call(originalFileStream)
554
+ originalFileStream?.end?.()
529
555
  stream.destroy()
556
+
557
+ // Wait for write streams to fully flush to disk
558
+ // This helps reduce memory pressure by allowing OS to release buffers
559
+ await encFinishPromise
560
+ await originalFinishPromise
561
+
530
562
  logger?.debug('encrypted data successfully')
563
+
531
564
  return {
532
565
  mediaKey,
533
566
  originalFilePath,
@@ -541,16 +574,18 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
541
574
  catch (error) {
542
575
  // destroy all streams with error
543
576
  encFileWriteStream.destroy()
544
- originalFileStream?.destroy?.call(originalFileStream)
577
+ originalFileStream?.destroy?.()
545
578
  aes.destroy()
546
579
  hmac.destroy()
547
580
  sha256Plain.destroy()
548
581
  sha256Enc.destroy()
549
582
  stream.destroy()
583
+
550
584
  try {
551
- await fs_1.promises.unlink(encFilePath)
585
+ await promises.unlink(encFilePath)
586
+
552
587
  if (originalFilePath) {
553
- await fs_1.promises.unlink(originalFilePath)
588
+ await promises.unlink(originalFilePath)
554
589
  }
555
590
  }
556
591
  catch (err) {
@@ -574,7 +609,7 @@ const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, o
574
609
  const downloadUrl = isValidMediaUrl ? url : getUrlFromDirectPath(directPath)
575
610
 
576
611
  if (!downloadUrl) {
577
- throw new boom_1.Boom('No valid media URL or directPath present in message', { statusCode: 400 })
612
+ throw new Boom('No valid media URL or directPath present in message', { statusCode: 400 })
578
613
  }
579
614
 
580
615
  const keys = await getMediaKeys(mediaKey, type)
@@ -589,39 +624,51 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
589
624
  let bytesFetched = 0
590
625
  let startChunk = 0
591
626
  let firstBlockIsIV = false
627
+
592
628
  // if a start byte is specified -- then we need to fetch the previous chunk as that will form the IV
593
629
  if (startByte) {
594
630
  const chunk = toSmallestChunkSize(startByte || 0)
631
+
595
632
  if (chunk) {
596
633
  startChunk = chunk - AES_CHUNK_SIZE
597
634
  bytesFetched = chunk
598
635
  firstBlockIsIV = true
599
636
  }
600
637
  }
638
+
601
639
  const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined
640
+ const headersInit = options?.headers ? options.headers : undefined
602
641
  const headers = {
603
- ...(options?.headers) || {},
604
- Origin: Defaults_1.DEFAULT_ORIGIN,
642
+ ...(headersInit
643
+ ? Array.isArray(headersInit)
644
+ ? Object.fromEntries(headersInit)
645
+ : headersInit
646
+ : {}),
647
+ Origin: DEFAULT_ORIGIN
605
648
  }
649
+
606
650
  if (startChunk || endChunk) {
607
651
  headers.Range = `bytes=${startChunk}-`
652
+
608
653
  if (endChunk) {
609
654
  headers.Range += endChunk
610
655
  }
611
656
  }
657
+
612
658
  // download the message
613
659
  const fetched = await getHttpStream(downloadUrl, {
614
- ...options || {},
615
- headers,
616
- maxBodyLength: Infinity,
617
- maxContentLength: Infinity,
660
+ ...(options || {}),
661
+ headers
618
662
  })
663
+
619
664
  let remainingBytes = Buffer.from([])
620
665
  let aes
666
+
621
667
  const pushBytes = (bytes, push) => {
622
668
  if (startByte || endByte) {
623
669
  const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0)
624
670
  const end = bytesFetched + bytes.length < endByte ? undefined : Math.max(endByte - bytesFetched, 0)
671
+
625
672
  push(bytes.slice(start, end))
626
673
  bytesFetched += bytes.length
627
674
  }
@@ -629,19 +676,26 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
629
676
  push(bytes)
630
677
  }
631
678
  }
632
- const output = new stream_1.Transform({
679
+
680
+ const output = new Transform({
633
681
  transform(chunk, _, callback) {
634
682
  let data = Buffer.concat([remainingBytes, chunk])
683
+
635
684
  const decryptLength = toSmallestChunkSize(data.length)
685
+
636
686
  remainingBytes = data.slice(decryptLength)
637
687
  data = data.slice(0, decryptLength)
688
+
638
689
  if (!aes) {
639
690
  let ivValue = iv
691
+
640
692
  if (firstBlockIsIV) {
641
693
  ivValue = data.slice(0, AES_CHUNK_SIZE)
642
694
  data = data.slice(AES_CHUNK_SIZE)
643
695
  }
644
- aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue)
696
+
697
+ aes = createDecipheriv('aes-256-cbc', cipherKey, ivValue)
698
+
645
699
  // if an end byte that is not EOF is specified
646
700
  // stop auto padding (PKCS7) -- otherwise throws an error for decryption
647
701
  if (endByte) {
@@ -664,8 +718,9 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
664
718
  catch (error) {
665
719
  callback(error)
666
720
  }
667
- },
721
+ }
668
722
  })
723
+
669
724
  return fetched.pipe(output, { end: true })
670
725
  }
671
726
 
@@ -685,43 +740,176 @@ function extensionForMediaMessage(message) {
685
740
  return extension
686
741
  }
687
742
 
743
+ const isNodeRuntime = () => {
744
+ return (typeof process !== 'undefined' &&
745
+ process.versions?.node !== null &&
746
+ typeof process.versions.bun === 'undefined' &&
747
+ typeof globalThis.Deno === 'undefined')
748
+ }
749
+
750
+ const uploadWithNodeHttp = async ({ url, filePath, headers, timeoutMs, agent }, redirectCount = 0) => {
751
+ if (redirectCount > 5) {
752
+ throw new Error('Too many redirects')
753
+ }
754
+
755
+ const parsedUrl = new URL(url)
756
+ const httpModule = parsedUrl.protocol === 'https:' ? require('https') : require('http')
757
+
758
+ // Get file size for Content-Length header (required for Node.js streaming)
759
+ const fileStats = await promises.stat(filePath)
760
+ const fileSize = fileStats.size
761
+
762
+ return new Promise((resolve, reject) => {
763
+ const req = httpModule.request({
764
+ hostname: parsedUrl.hostname,
765
+ port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
766
+ path: parsedUrl.pathname + parsedUrl.search,
767
+ method: 'POST',
768
+ headers: {
769
+ ...headers,
770
+ 'Content-Length': fileSize
771
+ },
772
+ agent,
773
+ timeout: timeoutMs
774
+ }, res => {
775
+ // Handle redirects (3xx)
776
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
777
+ res.resume() // Consume response to free resources
778
+
779
+ const newUrl = new URL(res.headers.location, url).toString()
780
+
781
+ resolve(uploadWithNodeHttp({
782
+ url: newUrl,
783
+ filePath,
784
+ headers,
785
+ timeoutMs,
786
+ agent
787
+ }, redirectCount + 1))
788
+ return
789
+ }
790
+
791
+ let body = ''
792
+
793
+ res.on('data', chunk => (body += chunk))
794
+ res.on('end', () => {
795
+ try {
796
+ resolve(JSON.parse(body))
797
+ }
798
+ catch {
799
+ resolve(undefined)
800
+ }
801
+ })
802
+ })
803
+
804
+ req.on('error', reject)
805
+ req.on('timeout', () => {
806
+ req.destroy()
807
+ reject(new Error('Upload timeout'))
808
+ })
809
+
810
+ const stream = createReadStream(filePath)
811
+
812
+ stream.pipe(req)
813
+ stream.on('error', err => {
814
+ req.destroy()
815
+ reject(err)
816
+ })
817
+ })
818
+ }
819
+
820
+ const uploadWithFetch = async ({ url, filePath, headers, timeoutMs, agent }) => {
821
+ // Convert Node.js Readable to Web ReadableStream
822
+ const nodeStream = createReadStream(filePath)
823
+ const webStream = Readable.toWeb(nodeStream)
824
+ const response = await fetch(url, {
825
+ dispatcher: agent,
826
+ method: 'POST',
827
+ body: webStream,
828
+ headers,
829
+ duplex: 'half',
830
+ signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined
831
+ })
832
+
833
+ try {
834
+ return (await response.json())
835
+ }
836
+ catch {
837
+ return undefined
838
+ }
839
+ }
840
+
841
+ /**
842
+ * Uploads media to WhatsApp servers.
843
+ *
844
+ * ## Why we have two upload implementations:
845
+ *
846
+ * Node.js's native `fetch` (powered by undici) has a known bug where it buffers
847
+ * the entire request body in memory before sending, even when using streams.
848
+ * This causes memory issues with large files (e.g., 1GB file = 1GB+ memory usage).
849
+ * See: https://github.com/nodejs/undici/issues/4058
850
+ *
851
+ * Other runtimes (Bun, Deno, browsers) correctly stream the request body without
852
+ * buffering, so we can use the web-standard Fetch API there.
853
+ *
854
+ * ## Future considerations:
855
+ * Once the undici bug is fixed, we can simplify this to use only the Fetch API
856
+ * across all runtimes. Monitor the GitHub issue for updates.
857
+ */
858
+ const uploadMedia = async (params, logger) => {
859
+ if (isNodeRuntime()) {
860
+ logger?.debug('Using Node.js https module for upload (avoids undici buffering bug)')
861
+ return uploadWithNodeHttp(params)
862
+ }
863
+ else {
864
+ logger?.debug('Using web-standard Fetch API for upload');
865
+ return uploadWithFetch(params)
866
+ }
867
+ }
868
+
688
869
  const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
689
- return async (filePath, { mediaType, fileEncSha256B64, newsletter, timeoutMs }) => {
870
+ return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
690
871
  // send a query JSON to obtain the url & auth token to upload our media
691
872
  let uploadInfo = await refreshMediaConn(false)
692
873
  let urls
693
- let media = Defaults_1.MEDIA_PATH_MAP[mediaType]
874
+
694
875
  const hosts = [...customUploadHosts, ...uploadInfo.hosts]
876
+
695
877
  fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64)
696
- if (newsletter) {
697
- media = media?.replace('/mms/', '/newsletter/newsletter-')
878
+
879
+ // Prepare common headers
880
+ const customHeaders = (() => {
881
+ const hdrs = options?.headers;
882
+ if (!hdrs)
883
+ return {};
884
+ return Array.isArray(hdrs) ? Object.fromEntries(hdrs) : hdrs
885
+ })()
886
+
887
+ const headers = {
888
+ ...customHeaders,
889
+ 'Content-Type': 'application/octet-stream',
890
+ Origin: DEFAULT_ORIGIN
698
891
  }
892
+
699
893
  for (const { hostname } of hosts) {
700
894
  logger.debug(`uploading to "${hostname}"`)
701
- const auth = encodeURIComponent(uploadInfo.auth) // the auth token
702
- const url = `https://${hostname}${media}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
703
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
895
+
896
+ const auth = encodeURIComponent(uploadInfo.auth)
897
+ const url = `https://${hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
898
+
704
899
  let result
900
+
705
901
  try {
706
- const body = await axios_1.default.post(url, fs_1.createReadStream(filePath), {
707
- ...options,
708
- maxRedirects: 0,
709
- headers: {
710
- ...options.headers || {},
711
- 'Content-Type': 'application/octet-stream',
712
- 'Origin': Defaults_1.DEFAULT_ORIGIN
713
- },
714
- httpsAgent: fetchAgent,
715
- timeout: timeoutMs,
716
- responseType: 'json',
717
- maxBodyLength: Infinity,
718
- maxContentLength: Infinity,
719
- })
720
- result = body.data
721
- if (result?.url || result?.directPath) {
902
+ result = await uploadMedia({
903
+ url,
904
+ filePath,
905
+ headers,
906
+ timeoutMs,
907
+ agent: fetchAgent
908
+ }, logger);
909
+ if (result?.url || result?.direct_path) {
722
910
  urls = {
723
911
  mediaUrl: result.url,
724
- directPath: result.direct_path,
912
+ directPath: result.direct_path,
725
913
  meta_hmac: result.meta_hmac,
726
914
  fbid: result.fbid,
727
915
  ts: result.ts
@@ -734,37 +922,36 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
734
922
  }
735
923
  }
736
924
  catch (error) {
737
- if (axios_1.default.isAxiosError(error)) {
738
- result = error.response?.data
739
- }
740
925
  const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname
741
- logger.warn({ trace: error.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`)
926
+ logger.warn({ trace: error?.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`)
742
927
  }
743
928
  }
929
+
744
930
  if (!urls) {
745
- throw new boom_1.Boom('Media upload failed on all hosts', { statusCode: 500 })
931
+ throw new Boom('Media upload failed on all hosts', { statusCode: 500 })
746
932
  }
933
+
747
934
  return urls
748
935
  }
749
936
  }
750
937
 
751
938
  const getMediaRetryKey = (mediaKey) => {
752
- return crypto_1.hkdf(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' })
939
+ return hkdf(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' })
753
940
  }
754
941
  /**
755
942
  * Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
756
943
  */
757
944
  const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
758
945
  const recp = { stanzaId: key.id }
759
- const recpBuffer = WAProto_1.proto.ServerErrorReceipt.encode(recp).finish()
760
- const iv = Crypto.randomBytes(12)
946
+ const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish()
947
+ const iv = randomBytes(12)
761
948
  const retryKey = await getMediaRetryKey(mediaKey)
762
- const ciphertext = crypto_1.aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id))
949
+ const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id))
763
950
  const req = {
764
951
  tag: 'receipt',
765
952
  attrs: {
766
953
  id: key.id,
767
- to: WABinary_1.jidNormalizedUser(meId),
954
+ to: jidNormalizedUser(meId),
768
955
  type: 'server-error'
769
956
  },
770
957
  content: [
@@ -783,8 +970,7 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
783
970
  tag: 'rmr',
784
971
  attrs: {
785
972
  jid: key.remoteJid,
786
- 'from_me': (!!key.fromMe).toString(),
787
- // @ts-ignore
973
+ from_me: (!!key.fromMe).toString(),
788
974
  participant: key.participant || undefined
789
975
  }
790
976
  }
@@ -794,7 +980,7 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
794
980
  }
795
981
 
796
982
  const decodeMediaRetryNode = (node) => {
797
- const rmrNode = WABinary_1.getBinaryNodeChild(node, 'rmr')
983
+ const rmrNode = getBinaryNodeChild(node, 'rmr')
798
984
  const event = {
799
985
  key: {
800
986
  id: node.attrs.id,
@@ -803,20 +989,20 @@ const decodeMediaRetryNode = (node) => {
803
989
  participant: rmrNode.attrs.participant
804
990
  }
805
991
  }
806
- const errorNode = WABinary_1.getBinaryNodeChild(node, 'error')
992
+ const errorNode = getBinaryNodeChild(node, 'error')
807
993
  if (errorNode) {
808
994
  const errorCode = +errorNode.attrs.code
809
- event.error = new boom_1.Boom(`Failed to re-upload media (${errorCode})`, { data: errorNode.attrs, statusCode: getStatusCodeForMediaRetry(errorCode) })
995
+ event.error = new Boom(`Failed to re-upload media (${errorCode})`, { data: errorNode.attrs, statusCode: getStatusCodeForMediaRetry(errorCode) })
810
996
  }
811
997
  else {
812
- const encryptedInfoNode = WABinary_1.getBinaryNodeChild(node, 'encrypt')
813
- const ciphertext = WABinary_1.getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_p')
814
- const iv = WABinary_1.getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_iv')
998
+ const encryptedInfoNode = getBinaryNodeChild(node, 'encrypt')
999
+ const ciphertext = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_p')
1000
+ const iv = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_iv')
815
1001
  if (ciphertext && iv) {
816
1002
  event.media = { ciphertext, iv }
817
1003
  }
818
1004
  else {
819
- event.error = new boom_1.Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 })
1005
+ event.error = new Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 })
820
1006
  }
821
1007
  }
822
1008
  return event
@@ -824,17 +1010,17 @@ const decodeMediaRetryNode = (node) => {
824
1010
 
825
1011
  const decryptMediaRetryData = async ({ ciphertext, iv }, mediaKey, msgId) => {
826
1012
  const retryKey = await getMediaRetryKey(mediaKey)
827
- const plaintext = crypto_1.aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId))
828
- return WAProto_1.proto.MediaRetryNotification.decode(plaintext)
1013
+ const plaintext = aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId))
1014
+ return proto.MediaRetryNotification.decode(plaintext)
829
1015
  }
830
1016
 
831
1017
  const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code]
832
1018
 
833
1019
  const MEDIA_RETRY_STATUS_MAP = {
834
- [WAProto_1.proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
835
- [WAProto_1.proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
836
- [WAProto_1.proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
837
- [WAProto_1.proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
1020
+ [proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
1021
+ [proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
1022
+ [proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
1023
+ [proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
838
1024
  }
839
1025
 
840
1026
  module.exports = {
@@ -852,12 +1038,13 @@ module.exports = {
852
1038
  getStream,
853
1039
  generateThumbnail,
854
1040
  getHttpStream,
855
- prepareStream,
1041
+ //prepareStream,
856
1042
  encryptedStream,
857
1043
  getUrlFromDirectPath,
858
1044
  downloadContentFromMessage,
859
1045
  downloadEncryptedContent,
860
1046
  extensionForMediaMessage,
1047
+ uploadWithNodeHttp,
861
1048
  getRawMediaUploadData,
862
1049
  getWAUploadToServer,
863
1050
  getMediaRetryKey,