refactor: replace axios with fetch API and update related types (#1666)

This commit is contained in:
João Lucas de Oliveira Lopes
2025-09-27 23:52:43 -03:00
committed by GitHub
parent cfe7a2b2b3
commit fb12f6d9d3
13 changed files with 97 additions and 124 deletions
-1
View File
@@ -43,7 +43,6 @@
"@cacheable/node-cache": "^1.4.0",
"@hapi/boom": "^9.1.3",
"async-mutex": "^0.5.0",
"axios": "^1.6.0",
"libsignal": "git+https://github.com/whiskeysockets/libsignal-node",
"lru-cache": "^11.1.0",
"music-metadata": "^11.7.0",
+2 -2
View File
@@ -57,7 +57,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
logger,
linkPreviewImageThumbnailWidth,
generateHighQualityLinkPreview,
options: axiosOptions,
options: httpRequestOptions,
patchMessageBeforeSending,
cachedGroupMetadata,
enableRecentMessageCache,
@@ -1077,7 +1077,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
thumbnailWidth: linkPreviewImageThumbnailWidth,
fetchOpts: {
timeout: 3_000,
...(axiosOptions || {})
...(httpRequestOptions || {})
},
logger,
uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
+1 -2
View File
@@ -1,4 +1,3 @@
import type { AxiosRequestConfig } from 'axios'
import type { Readable } from 'stream'
import type { URL } from 'url'
import { proto } from '../../WAProto/index.js'
@@ -335,7 +334,7 @@ export type MediaGenerationOptions = {
mediaUploadTimeoutMs?: number
options?: AxiosRequestConfig
options?: RequestInit
backgroundColor?: string
+2 -3
View File
@@ -1,4 +1,3 @@
import type { AxiosRequestConfig } from 'axios'
import type { Agent } from 'https'
import type { URL } from 'url'
import { proto } from '../../WAProto/index.js'
@@ -131,8 +130,8 @@ export type SocketConfig = {
snapshot: boolean
}
/** options for axios */
options: AxiosRequestConfig<{}>
/** options for HTTP fetch requests */
options: RequestInit
/**
* fetch a message from your store
* implement this so that messages failed to send
+7
View File
@@ -0,0 +1,7 @@
declare global {
interface RequestInit {
dispatcher?: any
}
}
export {}
+4 -5
View File
@@ -1,5 +1,4 @@
import { Boom } from '@hapi/boom'
import type { AxiosRequestConfig } from 'axios'
import { proto } from '../../WAProto/index.js'
import type {
BaileysEventEmitter,
@@ -303,7 +302,7 @@ export const decodeSyncdPatch = async (
return result
}
export const extractSyncdPatches = async (result: BinaryNode, options: AxiosRequestConfig<{}>) => {
export const extractSyncdPatches = async (result: BinaryNode, options: RequestInit) => {
const syncNode = getBinaryNodeChild(result, 'sync')
const collectionNodes = getBinaryNodeChildren(syncNode, 'collection')
@@ -355,7 +354,7 @@ export const extractSyncdPatches = async (result: BinaryNode, options: AxiosRequ
return final
}
export const downloadExternalBlob = async (blob: proto.IExternalBlobReference, options: AxiosRequestConfig<{}>) => {
export const downloadExternalBlob = async (blob: proto.IExternalBlobReference, options: RequestInit) => {
const stream = await downloadContentFromMessage(blob, 'md-app-state', { options })
const bufferArray: Buffer[] = []
for await (const chunk of stream) {
@@ -365,7 +364,7 @@ export const downloadExternalBlob = async (blob: proto.IExternalBlobReference, o
return Buffer.concat(bufferArray)
}
export const downloadExternalPatch = async (blob: proto.IExternalBlobReference, options: AxiosRequestConfig<{}>) => {
export const downloadExternalPatch = async (blob: proto.IExternalBlobReference, options: RequestInit) => {
const buffer = await downloadExternalBlob(blob, options)
const syncData = decodeAndHydrate(proto.SyncdMutations, buffer)
return syncData
@@ -424,7 +423,7 @@ export const decodePatches = async (
syncds: proto.ISyncdPatch[],
initial: LTHashState,
getAppStateSyncKey: FetchAppStateSyncKey,
options: AxiosRequestConfig<{}>,
options: RequestInit,
minimumVersionNumber?: number,
logger?: ILogger,
validateMacs = true
+20 -10
View File
@@ -1,5 +1,4 @@
import { Boom } from '@hapi/boom'
import axios, { type AxiosRequestConfig } from 'axios'
import { createHash, randomBytes } from 'crypto'
import { proto } from '../../WAProto/index.js'
const baileysVersion = [2, 3000, 1023223821]
@@ -225,16 +224,21 @@ export const bindWaitForConnectionUpdate = (ev: BaileysEventEmitter) => bindWait
* utility that fetches latest baileys version from the master branch.
* Use to ensure your WA connection is always on the latest version
*/
export const fetchLatestBaileysVersion = async (options: AxiosRequestConfig<{}> = {}) => {
export const fetchLatestBaileysVersion = async (options: RequestInit = {}) => {
const URL = 'https://raw.githubusercontent.com/WhiskeySockets/Baileys/master/src/Defaults/index.ts'
try {
const result = await axios.get<string>(URL, {
...options,
responseType: 'text'
const response = await fetch(URL, {
dispatcher: options.dispatcher,
method: 'GET',
headers: options.headers
})
if (!response.ok) {
throw new Boom(`Failed to fetch latest Baileys version: ${response.statusText}`, { statusCode: response.status })
}
const text = await response.text()
// Extract version from line 7 (const version = [...])
const lines = result.data.split('\n')
const lines = text.split('\n')
const versionLine = lines[6] // Line 7 (0-indexed)
const versionMatch = versionLine!.match(/const version = \[(\d+),\s*(\d+),\s*(\d+)\]/)
@@ -261,12 +265,18 @@ export const fetchLatestBaileysVersion = async (options: AxiosRequestConfig<{}>
* A utility that fetches the latest web version of whatsapp.
* Use to ensure your WA connection is always on the latest version
*/
export const fetchLatestWaWebVersion = async (options: AxiosRequestConfig<{}>) => {
export const fetchLatestWaWebVersion = async (options: RequestInit = {}) => {
try {
const { data } = await axios.get('https://web.whatsapp.com/sw.js', {
...options,
responseType: 'json'
const response = await fetch('https://web.whatsapp.com/sw.js', {
dispatcher: options.dispatcher,
method: 'GET',
headers: options.headers
})
if (!response.ok) {
throw new Boom(`Failed to fetch sw.js: ${response.statusText}`, { statusCode: response.status })
}
const data = await response.text()
const regex = /\\?"client_revision\\?":\s*(\d+)/
const match = data.match(regex)
+2 -3
View File
@@ -1,4 +1,3 @@
import type { AxiosRequestConfig } from 'axios'
import { promisify } from 'util'
import { inflate } from 'zlib'
import { proto } from '../../WAProto/index.js'
@@ -11,7 +10,7 @@ import { decodeAndHydrate } from './proto-utils'
const inflatePromise = promisify(inflate)
export const downloadHistory = async (msg: proto.Message.IHistorySyncNotification, options: AxiosRequestConfig<{}>) => {
export const downloadHistory = async (msg: proto.Message.IHistorySyncNotification, options: RequestInit) => {
const stream = await downloadContentFromMessage(msg, 'md-msg-hist', { options })
const bufferArray: Buffer[] = []
for await (const chunk of stream) {
@@ -96,7 +95,7 @@ export const processHistoryMessage = (item: proto.IHistorySync) => {
export const downloadAndProcessHistorySyncNotification = async (
msg: proto.Message.IHistorySyncNotification,
options: AxiosRequestConfig<{}>
options: RequestInit
) => {
const historyMsg = await downloadHistory(msg, options)
return processHistoryMessage(historyMsg)
+2 -3
View File
@@ -1,4 +1,3 @@
import type { AxiosRequestConfig } from 'axios'
import type { WAMediaUploadFunction, WAUrlInfo } from '../Types'
import type { ILogger } from './logger'
import { prepareWAMessageMedia } from './messages'
@@ -19,7 +18,7 @@ export type URLGenerationOptions = {
/** Timeout in ms */
timeout: number
proxyUrl?: string
headers?: AxiosRequestConfig<{}>['headers']
headers?: HeadersInit
}
uploadImage?: WAMediaUploadFunction
logger?: ILogger
@@ -70,7 +69,7 @@ export const getUrlInfo = async (
return false
}
},
headers: opts.fetchOpts as {}
headers: opts.fetchOpts?.headers as {}
})
if (info && 'title' in info && info.title) {
const [image] = info.images
+49 -28
View File
@@ -1,5 +1,4 @@
import { Boom } from '@hapi/boom'
import axios, { type AxiosRequestConfig } from 'axios'
import { exec } from 'child_process'
import * as Crypto from 'crypto'
import { once } from 'events'
@@ -301,7 +300,7 @@ export const toBuffer = async (stream: Readable) => {
return Buffer.concat(chunks)
}
export const getStream = async (item: WAMediaUpload, opts?: AxiosRequestConfig) => {
export const getStream = async (item: WAMediaUpload, opts?: RequestInit & { maxContentLength?: number }) => {
if (Buffer.isBuffer(item)) {
return { stream: toReadable(item), type: 'buffer' } as const
}
@@ -362,15 +361,24 @@ export async function generateThumbnail(
}
}
export const getHttpStream = async (url: string | URL, options: AxiosRequestConfig & { isStream?: true } = {}) => {
const fetched = await axios.get(url.toString(), { ...options, responseType: 'stream' })
return fetched.data as Readable
export const getHttpStream = async (url: string | URL, options: RequestInit & { isStream?: true } = {}) => {
const response = await fetch(url.toString(), {
dispatcher: options.dispatcher,
method: 'GET',
headers: options.headers as HeadersInit
})
if (!response.ok) {
throw new Boom(`Failed to fetch stream from ${url}`, { statusCode: response.status, data: { url } })
}
// @ts-ignore Node18+ Readable.fromWeb exists
return Readable.fromWeb(response.body as any)
}
type EncryptedStreamOptions = {
saveOriginalFileIfRequired?: boolean
logger?: ILogger
opts?: AxiosRequestConfig
opts?: RequestInit
}
export const encryptedStream = async (
@@ -412,7 +420,11 @@ export const encryptedStream = async (
for await (const data of stream) {
fileLength += data.length
if (type === 'remote' && opts?.maxContentLength && fileLength + data.length > opts.maxContentLength) {
if (
type === 'remote' &&
(opts as any)?.maxContentLength &&
fileLength + data.length > (opts as any).maxContentLength
) {
throw new Boom(`content length exceeded when encrypting "${type}"`, {
data: { media, type }
})
@@ -486,7 +498,7 @@ const toSmallestChunkSize = (num: number) => {
export type MediaDownloadOptions = {
startByte?: number
endByte?: number
options?: AxiosRequestConfig<{}>
options?: RequestInit
}
export const getUrlFromDirectPath = (directPath: string) => `https://${DEF_HOST}${directPath}`
@@ -532,8 +544,13 @@ export const downloadEncryptedContent = async (
const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined
const headers: AxiosRequestConfig['headers'] = {
...(options?.headers || {}),
const headersInit = options?.headers ? options.headers : undefined
const headers: Record<string, string> = {
...(headersInit
? Array.isArray(headersInit)
? Object.fromEntries(headersInit)
: (headersInit as Record<string, string>)
: {}),
Origin: DEFAULT_ORIGIN
}
if (startChunk || endChunk) {
@@ -546,9 +563,7 @@ export const downloadEncryptedContent = async (
// download the message
const fetched = await getHttpStream(downloadUrl, {
...(options || {}),
headers,
maxBodyLength: Infinity,
maxContentLength: Infinity
headers
})
let remainingBytes = Buffer.from([])
@@ -645,21 +660,31 @@ export const getWAUploadToServer = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let result: any
try {
const body = await axios.post(url, createReadStream(filePath), {
...options,
maxRedirects: 0,
const stream = createReadStream(filePath)
const response = await fetch(url, {
dispatcher: fetchAgent,
method: 'POST',
body: stream as any,
headers: {
...(options.headers || {}),
...(() => {
const hdrs = options?.headers
if (!hdrs) return {}
return Array.isArray(hdrs) ? Object.fromEntries(hdrs) : (hdrs as Record<string, string>)
})(),
'Content-Type': 'application/octet-stream',
Origin: DEFAULT_ORIGIN
},
httpsAgent: fetchAgent,
timeout: timeoutMs,
responseType: 'json',
maxBodyLength: Infinity,
maxContentLength: Infinity
// Note: custom agents/proxy require undici Agent; omitted here.
signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined
})
result = body.data
let parsed: any = undefined
try {
parsed = await response.json()
} catch {
parsed = undefined
}
result = parsed
if (result?.url || result?.directPath) {
urls = {
@@ -675,13 +700,9 @@ export const getWAUploadToServer = (
throw new Error(`upload failed, reason: ${JSON.stringify(result)}`)
}
} catch (error: any) {
if (axios.isAxiosError(error)) {
result = error.response?.data
}
const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname
logger.warn(
{ trace: error.stack, uploadResult: result },
{ trace: error?.stack, uploadResult: result },
`Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`
)
}
+6 -6
View File
@@ -1,5 +1,4 @@
import { Boom } from '@hapi/boom'
import axios from 'axios'
import { randomBytes } from 'crypto'
import { promises as fs } from 'fs'
import { type Transform } from 'stream'
@@ -455,9 +454,10 @@ export const generateWAMessageContent = async (
if (options.getProfilePicUrl) {
const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid, 'preview')
if (pfpUrl) {
const resp = await axios.get(pfpUrl, { responseType: 'arraybuffer' })
if (resp.status === 200) {
m.groupInviteMessage.jpegThumbnail = resp.data
const resp = await fetch(pfpUrl, { method: 'GET', dispatcher: options?.options?.dispatcher })
if (resp.ok) {
const buf = Buffer.from(await resp.arrayBuffer())
m.groupInviteMessage.jpegThumbnail = buf
}
}
}
@@ -933,8 +933,8 @@ export const downloadMediaMessage = async <Type extends 'buffer' | 'stream'>(
const result = await downloadMsg().catch(async error => {
if (
ctx &&
axios.isAxiosError(error) && // check if the message requires a reupload
REUPLOAD_REQUIRED_STATUS.includes(error.response?.status!)
typeof error?.status === 'number' && // treat errors with status as HTTP failures requiring reupload
REUPLOAD_REQUIRED_STATUS.includes(error.status as number)
) {
ctx.logger.info({ key: message.key }, 'sending reupload media request...')
// request reupload
+1 -2
View File
@@ -1,4 +1,3 @@
import type { AxiosRequestConfig } from 'axios'
import { proto } from '../../WAProto/index.js'
import type {
AuthenticationCreds,
@@ -28,7 +27,7 @@ type ProcessMessageContext = {
keyStore: SignalKeyStoreWithTransaction
ev: BaileysEventEmitter
logger?: ILogger
options: AxiosRequestConfig<{}>
options: RequestInit
signalRepository: SignalRepositoryWithLIDStore
}
+1 -59
View File
@@ -2874,13 +2874,6 @@ __metadata:
languageName: node
linkType: hard
"asynckit@npm:^0.4.0":
version: 0.4.0
resolution: "asynckit@npm:0.4.0"
checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d
languageName: node
linkType: hard
"atomic-sleep@npm:^1.0.0":
version: 1.0.0
resolution: "atomic-sleep@npm:1.0.0"
@@ -2904,17 +2897,6 @@ __metadata:
languageName: node
linkType: hard
"axios@npm:^1.6.0":
version: 1.10.0
resolution: "axios@npm:1.10.0"
dependencies:
follow-redirects: "npm:^1.15.6"
form-data: "npm:^4.0.0"
proxy-from-env: "npm:^1.1.0"
checksum: 10c0/2239cb269cc789eac22f5d1aabd58e1a83f8f364c92c2caa97b6f5cbb4ab2903d2e557d9dc670b5813e9bcdebfb149e783fb8ab3e45098635cd2f559b06bd5d8
languageName: node
linkType: hard
"babel-jest@npm:30.0.5":
version: 30.0.5
resolution: "babel-jest@npm:30.0.5"
@@ -3008,7 +2990,6 @@ __metadata:
"@typescript-eslint/parser": "npm:^8"
"@whiskeysockets/eslint-config": "npm:^1.0.0"
async-mutex: "npm:^0.5.0"
axios: "npm:^1.6.0"
conventional-changelog-cli: "npm:^2.2.2"
esbuild-register: "npm:^3.6.0"
eslint: "npm:^9"
@@ -3590,15 +3571,6 @@ __metadata:
languageName: node
linkType: hard
"combined-stream@npm:^1.0.8":
version: 1.0.8
resolution: "combined-stream@npm:1.0.8"
dependencies:
delayed-stream: "npm:~1.0.0"
checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5
languageName: node
linkType: hard
"compare-func@npm:^2.0.0":
version: 2.0.0
resolution: "compare-func@npm:2.0.0"
@@ -4137,13 +4109,6 @@ __metadata:
languageName: node
linkType: hard
"delayed-stream@npm:~1.0.0":
version: 1.0.0
resolution: "delayed-stream@npm:1.0.0"
checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19
languageName: node
linkType: hard
"deprecation@npm:^2.0.0":
version: 2.3.1
resolution: "deprecation@npm:2.3.1"
@@ -5153,16 +5118,6 @@ __metadata:
languageName: node
linkType: hard
"follow-redirects@npm:^1.15.6":
version: 1.15.9
resolution: "follow-redirects@npm:1.15.9"
peerDependenciesMeta:
debug:
optional: true
checksum: 10c0/5829165bd112c3c0e82be6c15b1a58fa9dcfaede3b3c54697a82fe4a62dd5ae5e8222956b448d2f98e331525f05d00404aba7d696de9e761ef6e42fdc780244f
languageName: node
linkType: hard
"for-each@npm:^0.3.3, for-each@npm:^0.3.5":
version: 0.3.5
resolution: "for-each@npm:0.3.5"
@@ -5189,19 +5144,6 @@ __metadata:
languageName: node
linkType: hard
"form-data@npm:^4.0.0":
version: 4.0.4
resolution: "form-data@npm:4.0.4"
dependencies:
asynckit: "npm:^0.4.0"
combined-stream: "npm:^1.0.8"
es-set-tostringtag: "npm:^2.1.0"
hasown: "npm:^2.0.2"
mime-types: "npm:^2.1.12"
checksum: 10c0/373525a9a034b9d57073e55eab79e501a714ffac02e7a9b01be1c820780652b16e4101819785e1e18f8d98f0aee866cc654d660a435c378e16a72f2e7cac9695
languageName: node
linkType: hard
"formdata-polyfill@npm:^4.0.10":
version: 4.0.10
resolution: "formdata-polyfill@npm:4.0.10"
@@ -7734,7 +7676,7 @@ __metadata:
languageName: node
linkType: hard
"mime-types@npm:2.1.35, mime-types@npm:^2.1.12":
"mime-types@npm:2.1.35":
version: 2.1.35
resolution: "mime-types@npm:2.1.35"
dependencies: