wailey-whatsapp-lib
Version:
A robust WhatsApp library for Node.js with support for QR code and pairing code authentication
396 lines (395 loc) • 13.7 kB
JavaScript
;
/**
* Wailey-library Socket Implementation
* (Renamed from Baileys while maintaining identical functionality)
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeWASocket = void 0;
const ws_1 = __importDefault(require("ws"));
const events_1 = require("events");
const Defaults_1 = require("../Defaults");
const Types_1 = require("../Types");
const generics_1 = require("../Utils/generics");
const noise_handler_1 = require("../Utils/noise-handler");
const WABinary_1 = require("../WABinary");
/**
* Creates a WhatsApp socket connection
*
* Implements WhatsApp Web API using WebSocket
*/
const makeWASocket = (config = {}) => {
const logger = config.logger || Defaults_1.DEFAULT_CONNECTION_CONFIG.logger;
const ws = new events_1.EventEmitter();
// Import requestPairingCode from auth module
const { requestPairingCode: _requestPairingCode } = require('./auth');
// Complete configuration with defaults
const configComplete = {
...Defaults_1.DEFAULT_CONNECTION_CONFIG,
...config
};
let connectionState = 'close';
let qrTimeout;
let noiseHandler = (0, noise_handler_1.makeNoiseHandler)();
// Websocket implementation
let sock;
// Pending requests waiting for response
const pendingRequests = new Map();
// Callbacks registered for different events
const callbacks = new Map();
/**
* Opens a WebSocket connection to WhatsApp
*/
const connect = async () => {
if (connectionState === 'connecting' || connectionState === 'open') {
return;
}
connectionState = 'connecting';
logger.info('Connecting to WhatsApp...');
try {
const wsUrl = configComplete.waWebSocketUrl || Defaults_1.DEFAULT_ORIGIN;
sock = new ws_1.default(wsUrl);
sock.on('open', () => {
logger.info('Connected to WhatsApp');
connectionState = 'open';
// Begin authentication and handshake process
startHandshake();
});
sock.on('close', (code) => {
handleConnectionClose(code);
});
sock.on('error', (err) => {
logger.error('WebSocket error:', err);
closeSocket(err);
});
sock.on('message', (data) => {
// In a real implementation, each message would be parsed and processed
handleWAMessage(data);
});
}
catch (error) {
closeSocket(error);
}
};
/**
* Begin handshake process with WhatsApp server
*/
const startHandshake = () => {
if (!sock || connectionState !== 'open') {
return;
}
try {
// In a real implementation, this would follow WhatsApp Web's protocol
// Starting with a client hello message to establish connection
const clientHello = {
tag: 'client',
attrs: {
platform: configComplete.browser[0],
version: configComplete.version.join('.'),
id: (0, generics_1.generateMessageID)()
},
content: []
};
// Send client hello message to begin connection
sendNode(clientHello);
}
catch (error) {
logger.error('Failed to start handshake:', error);
closeSocket(error);
}
};
/**
* Close the WebSocket connection
*/
const closeSocket = (error) => {
if (connectionState === 'close') {
return;
}
connectionState = 'close';
if (qrTimeout) {
clearTimeout(qrTimeout);
qrTimeout = undefined;
}
// Clear pending requests
for (const { reject } of pendingRequests.values()) {
reject(error || new Error('Connection closed'));
}
pendingRequests.clear();
if (sock) {
sock.removeAllListeners();
try {
sock.close();
}
catch (_a) { }
sock = undefined;
}
logger.info('Disconnected from WhatsApp', error ? { error } : undefined);
// Reset authentication
noiseHandler = (0, noise_handler_1.makeNoiseHandler)();
ws.emit('close', {
reason: (error === null || error === void 0 ? void 0 : error.message) || 'Connection closed',
isReconnecting: false
});
};
/**
* Handle WebSocket connection closure
*/
const handleConnectionClose = (code) => {
logger.info(`WebSocket closed with code ${code}`);
const reason = getDisconnectReason(code) || Types_1.DisconnectReason.connectionLost;
closeSocket(new Error(`Connection closed with code ${code}. Reason: ${Types_1.DisconnectReason[reason]}`));
};
/**
* Map WebSocket close code to DisconnectReason
*/
const getDisconnectReason = (code) => {
switch (code) {
case 1000: // Normal closure
return Types_1.DisconnectReason.connectionClosed;
case 1006: // Abnormal closure
return Types_1.DisconnectReason.connectionLost;
case 1011: // Internal error
return Types_1.DisconnectReason.restartRequired;
case 1012: // Service restart
return Types_1.DisconnectReason.restartRequired;
case 1013: // Try again later
return Types_1.DisconnectReason.timedOut;
case 1014: // Bad gateway
return Types_1.DisconnectReason.timedOut;
case 1015: // TLS handshake
return Types_1.DisconnectReason.badSession;
default:
return undefined;
}
};
/**
* Send a binary node to WhatsApp server
*/
const sendNode = (node) => {
if (!sock || connectionState !== 'open') {
throw new Error('Connection not open');
}
const encodedNode = (0, WABinary_1.encodeBinaryNode)(node);
sock.send(encodedNode);
};
/**
* Handle incoming messages from WhatsApp
*/
const handleWAMessage = (data) => {
try {
// Decode the binary message
const node = (0, WABinary_1.decodeBinaryNode)(data);
// Handle different message types based on tag
if (node.tag === 'response') {
handleResponse(node);
}
else if (node.tag === 'action') {
handleAction(node);
}
else if (node.tag === 'iq') {
handleIQ(node);
}
else if (node.tag === 'notification') {
handleNotification(node);
}
else if (node.tag === 'challenge') {
handleChallenge(node);
}
else {
logger.info(`Received unknown message type: ${node.tag}`);
}
}
catch (error) {
logger.error('Failed to process message:', error);
}
};
/**
* Handle response messages
*/
const handleResponse = (node) => {
const id = node.attrs.id;
if (!id) {
return;
}
// Check if this is a response to a pending request
const request = pendingRequests.get(id);
if (request) {
pendingRequests.delete(id);
request.resolve(node);
}
// Check for callbacks
const callbackName = `${Defaults_1.DEF_CALLBACK_PREFIX}${id}`;
const callback = callbacks.get(callbackName);
if (callback) {
callback(node);
callbacks.delete(callbackName);
}
};
/**
* Handle action messages
*/
const handleAction = (node) => {
// In a real implementation, this would handle different action types
// such as chat updates, message status updates, etc.
// Emit the action for event listeners
ws.emit('action', node);
};
/**
* Handle IQ (Info/Query) messages
*/
const handleIQ = (node) => {
// Handle different types of IQ queries
const type = node.attrs.type;
if (type === 'get') {
// Handle get requests
}
else if (type === 'set') {
// Handle set requests
}
else if (type === 'result') {
// Handle results
}
// Emit the IQ for event listeners
ws.emit('iq', node);
};
/**
* Handle notification messages
*/
const handleNotification = (node) => {
// Process notifications (e.g., new messages, status updates)
const type = node.attrs.type;
// Emit the notification for event listeners
ws.emit('notification', { type, node });
};
/**
* Handle challenge messages (for encryption)
*/
const handleChallenge = (node) => {
// In a real implementation, this would handle the cryptographic challenge
// part of the WhatsApp Web protocol
// For now, just log it
logger.info('Received challenge');
};
/**
* Send a query to the WhatsApp server
*/
const query = async (node, timeoutMs) => {
if (!sock || connectionState !== 'open') {
throw new Error('Connection not open');
}
// Generate ID if none provided
const id = node.attrs.id || (0, generics_1.generateMessageID)();
node.attrs.id = id;
// Create a promise for the response
const promise = new Promise((resolve, reject) => {
pendingRequests.set(id, { resolve, reject, timeoutMs });
// Set timeout for the request
const timeout = setTimeout(() => {
pendingRequests.delete(id);
reject(new Error('Query timed out'));
}, timeoutMs || configComplete.defaultQueryTimeoutMs || 60000);
// Clear timeout when resolved
const originalResolve = resolve;
resolve = ((result) => {
clearTimeout(timeout);
originalResolve(result);
});
});
// Send the query
sendNode(node);
return promise;
};
/**
* Wait for a specific callback
*/
const waitForCallback = (callbackName, timeoutMs) => {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
callbacks.delete(callbackName);
reject(new Error('Callback timed out'));
}, timeoutMs || configComplete.defaultQueryTimeoutMs || 60000);
callbacks.set(callbackName, (data) => {
clearTimeout(timeout);
resolve(data);
});
});
};
/**
* Wait for a message with a specific tag
*/
const waitForMessage = (tag, timeoutMs) => {
const callbackName = `${Defaults_1.DEF_TAG_PREFIX}${tag}`;
return waitForCallback(callbackName, timeoutMs);
};
/**
* Checks if phone is connected (middleware for requests)
*/
const assertConnected = () => {
if (connectionState !== 'open') {
throw new Error('Connection not open');
}
};
/**
* Sends a keep-alive ping to the server
*/
const sendKeepAlive = async () => {
if (connectionState !== 'open') {
return;
}
try {
const pingMsg = {
tag: 'iq',
attrs: {
id: (0, generics_1.generateMessageID)(),
type: 'get',
xmlns: 'w:p',
to: 's.whatsapp.net'
},
content: [{ tag: 'ping', attrs: {}, content: undefined }]
};
const result = await query(pingMsg, 10000);
ws.emit(Defaults_1.PHONE_CONNECTION_CB, { connected: true });
return result;
}
catch (error) {
logger.warn('Ping failed', error);
ws.emit(Defaults_1.PHONE_CONNECTION_CB, { connected: false });
return undefined;
}
};
// Implement requestPairingCode wrapper
const requestPairingCode = async (phoneNumber) => {
logger.info(`Requesting pairing code for ${phoneNumber}...`);
if (connectionState !== 'open') {
// Auto-connect if not connected
await connect();
}
try {
// Use the imported function but pass socket (ws) as first parameter
const result = await _requestPairingCode(ws, phoneNumber);
logger.info(`Received pairing code for ${phoneNumber}`);
return result.pairingCode;
}
catch (error) {
logger.error(`Failed to request pairing code: ${error}`);
throw error;
}
};
// Expose functions to the client
ws.connect = connect;
ws.close = closeSocket;
ws.sendNode = sendNode;
ws.query = query;
ws.waitForMessage = waitForMessage;
ws.assertConnected = assertConnected;
ws.sendKeepAlive = sendKeepAlive;
ws.requestPairingCode = requestPairingCode;
// Expose connection state
Object.defineProperty(ws, 'connectionState', {
get: () => connectionState
});
return ws;
};
exports.makeWASocket = makeWASocket;