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
+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
}