@sjawhar/whatsapp-mcp 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +122 -0
- package/dist/__tests__/connection.test.d.ts +2 -0
- package/dist/__tests__/connection.test.d.ts.map +1 -0
- package/dist/__tests__/connection.test.js +105 -0
- package/dist/__tests__/connection.test.js.map +1 -0
- package/dist/__tests__/disconnect.test.d.ts +2 -0
- package/dist/__tests__/disconnect.test.d.ts.map +1 -0
- package/dist/__tests__/disconnect.test.js +166 -0
- package/dist/__tests__/disconnect.test.js.map +1 -0
- package/dist/__tests__/download-media.test.d.ts +2 -0
- package/dist/__tests__/download-media.test.d.ts.map +1 -0
- package/dist/__tests__/download-media.test.js +110 -0
- package/dist/__tests__/download-media.test.js.map +1 -0
- package/dist/__tests__/failures/connection-failures.test.d.ts +2 -0
- package/dist/__tests__/failures/connection-failures.test.d.ts.map +1 -0
- package/dist/__tests__/failures/connection-failures.test.js +146 -0
- package/dist/__tests__/failures/connection-failures.test.js.map +1 -0
- package/dist/__tests__/failures/edge-cases.test.d.ts +2 -0
- package/dist/__tests__/failures/edge-cases.test.d.ts.map +1 -0
- package/dist/__tests__/failures/edge-cases.test.js +121 -0
- package/dist/__tests__/failures/edge-cases.test.js.map +1 -0
- package/dist/__tests__/failures/resource-failures.test.d.ts +2 -0
- package/dist/__tests__/failures/resource-failures.test.d.ts.map +1 -0
- package/dist/__tests__/failures/resource-failures.test.js +136 -0
- package/dist/__tests__/failures/resource-failures.test.js.map +1 -0
- package/dist/__tests__/failures/security-failures.test.d.ts +2 -0
- package/dist/__tests__/failures/security-failures.test.d.ts.map +1 -0
- package/dist/__tests__/failures/security-failures.test.js +0 -0
- package/dist/__tests__/failures/security-failures.test.js.map +1 -0
- package/dist/__tests__/helpers/fake-baileys.d.ts +52 -0
- package/dist/__tests__/helpers/fake-baileys.d.ts.map +1 -0
- package/dist/__tests__/helpers/fake-baileys.js +60 -0
- package/dist/__tests__/helpers/fake-baileys.js.map +1 -0
- package/dist/__tests__/helpers/mcp-test-client.d.ts +9 -0
- package/dist/__tests__/helpers/mcp-test-client.d.ts.map +1 -0
- package/dist/__tests__/helpers/mcp-test-client.js +40 -0
- package/dist/__tests__/helpers/mcp-test-client.js.map +1 -0
- package/dist/__tests__/helpers/test-db.d.ts +4 -0
- package/dist/__tests__/helpers/test-db.d.ts.map +1 -0
- package/dist/__tests__/helpers/test-db.js +32 -0
- package/dist/__tests__/helpers/test-db.js.map +1 -0
- package/dist/__tests__/integration/chat-navigation.test.d.ts +2 -0
- package/dist/__tests__/integration/chat-navigation.test.d.ts.map +1 -0
- package/dist/__tests__/integration/chat-navigation.test.js +171 -0
- package/dist/__tests__/integration/chat-navigation.test.js.map +1 -0
- package/dist/__tests__/integration/contacts-flow.test.d.ts +2 -0
- package/dist/__tests__/integration/contacts-flow.test.d.ts.map +1 -0
- package/dist/__tests__/integration/contacts-flow.test.js +144 -0
- package/dist/__tests__/integration/contacts-flow.test.js.map +1 -0
- package/dist/__tests__/integration/media-flow.test.d.ts +2 -0
- package/dist/__tests__/integration/media-flow.test.d.ts.map +1 -0
- package/dist/__tests__/integration/media-flow.test.js +225 -0
- package/dist/__tests__/integration/media-flow.test.js.map +1 -0
- package/dist/__tests__/integration/search-flow.test.d.ts +2 -0
- package/dist/__tests__/integration/search-flow.test.d.ts.map +1 -0
- package/dist/__tests__/integration/search-flow.test.js +44 -0
- package/dist/__tests__/integration/search-flow.test.js.map +1 -0
- package/dist/__tests__/integration/send-message.test.d.ts +2 -0
- package/dist/__tests__/integration/send-message.test.d.ts.map +1 -0
- package/dist/__tests__/integration/send-message.test.js +160 -0
- package/dist/__tests__/integration/send-message.test.js.map +1 -0
- package/dist/__tests__/lock-file.test.d.ts +2 -0
- package/dist/__tests__/lock-file.test.d.ts.map +1 -0
- package/dist/__tests__/lock-file.test.js +63 -0
- package/dist/__tests__/lock-file.test.js.map +1 -0
- package/dist/__tests__/medium-fixes.test.d.ts +2 -0
- package/dist/__tests__/medium-fixes.test.d.ts.map +1 -0
- package/dist/__tests__/medium-fixes.test.js +141 -0
- package/dist/__tests__/medium-fixes.test.js.map +1 -0
- package/dist/__tests__/rate-limit.test.d.ts +2 -0
- package/dist/__tests__/rate-limit.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limit.test.js +193 -0
- package/dist/__tests__/rate-limit.test.js.map +1 -0
- package/dist/__tests__/send-file.test.d.ts +2 -0
- package/dist/__tests__/send-file.test.d.ts.map +1 -0
- package/dist/__tests__/send-file.test.js +237 -0
- package/dist/__tests__/send-file.test.js.map +1 -0
- package/dist/__tests__/smoke.test.d.ts +2 -0
- package/dist/__tests__/smoke.test.d.ts.map +1 -0
- package/dist/__tests__/smoke.test.js +28 -0
- package/dist/__tests__/smoke.test.js.map +1 -0
- package/dist/__tests__/transcribe.test.d.ts +2 -0
- package/dist/__tests__/transcribe.test.d.ts.map +1 -0
- package/dist/__tests__/transcribe.test.js +71 -0
- package/dist/__tests__/transcribe.test.js.map +1 -0
- package/dist/__tests__/zombie.test.d.ts +2 -0
- package/dist/__tests__/zombie.test.d.ts.map +1 -0
- package/dist/__tests__/zombie.test.js +145 -0
- package/dist/__tests__/zombie.test.js.map +1 -0
- package/dist/db.d.ts +53 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +509 -0
- package/dist/db.js.map +1 -0
- package/dist/import-contacts.d.ts +37 -0
- package/dist/import-contacts.d.ts.map +1 -0
- package/dist/import-contacts.js +242 -0
- package/dist/import-contacts.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/lock.d.ts +16 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +65 -0
- package/dist/lock.js.map +1 -0
- package/dist/tools.d.ts +6 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +339 -0
- package/dist/tools.js.map +1 -0
- package/dist/transcribe.d.ts +8 -0
- package/dist/transcribe.d.ts.map +1 -0
- package/dist/transcribe.js +63 -0
- package/dist/transcribe.js.map +1 -0
- package/dist/utils.d.ts +51 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +156 -0
- package/dist/utils.js.map +1 -0
- package/dist/whatsapp.d.ts +50 -0
- package/dist/whatsapp.d.ts.map +1 -0
- package/dist/whatsapp.js +896 -0
- package/dist/whatsapp.js.map +1 -0
- package/package.json +52 -0
- package/patches/@whiskeysockets+baileys+6.7.21.patch +46 -0
- package/patches/libsignal+2.0.1.patch +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 karlfoster
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# WhatsApp MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server that connects to WhatsApp via [Baileys](https://github.com/WhiskeySockets/Baileys), providing AI with the ability to manage WhatsApp. This is a security-hardened fork of [karlfoster/whatsapp-mcp-2.0](https://github.com/karlfoster/whatsapp-mcp-2.0).
|
|
4
|
+
|
|
5
|
+
## ⚠️ WARNING
|
|
6
|
+
|
|
7
|
+
**This project uses the UNOFFICIAL WhatsApp Web API (Baileys).**
|
|
8
|
+
WhatsApp may ban accounts using unofficial clients.
|
|
9
|
+
**USE A DEDICATED BURNER NUMBER** — never use your personal number.
|
|
10
|
+
The authors take no responsibility for account bans or any other consequences of using this software.
|
|
11
|
+
|
|
12
|
+
## What This Is
|
|
13
|
+
|
|
14
|
+
This is a fork of `karlfoster/whatsapp-mcp-2.0` with significant security hardening and operational improvements. It allows an AI assistant (like Claude) to:
|
|
15
|
+
- List and search chats, contacts, and messages.
|
|
16
|
+
- Send text messages and files (images, videos, documents, audio).
|
|
17
|
+
- Download media from received messages.
|
|
18
|
+
- Transcribe voice notes via Whisper-compatible APIs.
|
|
19
|
+
- Sync phone contacts from VCF files.
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
1. **Clone the repository**:
|
|
24
|
+
```bash
|
|
25
|
+
git clone <repo-url>
|
|
26
|
+
cd whatsapp-mcp
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. **Install dependencies**:
|
|
30
|
+
```bash
|
|
31
|
+
npm ci
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
3. **Configure environment variables**:
|
|
35
|
+
Create a `.env` file or set them in your MCP client configuration (see below).
|
|
36
|
+
|
|
37
|
+
4. **Run the server**:
|
|
38
|
+
```bash
|
|
39
|
+
npm run dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
5. **Scan the QR code**:
|
|
43
|
+
On first run, a QR code will appear in your terminal. Scan it with WhatsApp on your phone:
|
|
44
|
+
**WhatsApp > Settings > Linked Devices > Link a Device**
|
|
45
|
+
|
|
46
|
+
## Environment Variables
|
|
47
|
+
|
|
48
|
+
| Variable | Default | Description |
|
|
49
|
+
| :--- | :--- | :--- |
|
|
50
|
+
| `ALLOWED_SEND_DIR` | `./uploads/` | Directory for outbound file sends. |
|
|
51
|
+
| `MAX_SEND_FILE_SIZE` | `67108864` (64MB) | Max file size for `send_file`. |
|
|
52
|
+
| `DOWNLOADS_DIR` | `./downloads/` | Directory for downloaded media. |
|
|
53
|
+
| `CONTACTS_DIR` | `./contacts/` | Base directory for VCF imports. |
|
|
54
|
+
| `WHISPER_API_URL` | (none) | Whisper API endpoint for transcription. |
|
|
55
|
+
| `WHISPER_API_KEY` | (none) | API key for Whisper. |
|
|
56
|
+
| `WHISPER_MODEL` | `whisper-large-v3-turbo` | Model name for transcription. |
|
|
57
|
+
| `ZOMBIE_TIMEOUT_MS` | `120000` (2min) | Silence before zombie connection detection. |
|
|
58
|
+
| `MAX_SEND_FAILURES` | `3` | Consecutive failures before reconnect. |
|
|
59
|
+
| `MIN_SEND_INTERVAL_MS` | `3000` (3s) | Minimum delay between sends. |
|
|
60
|
+
| `SEND_JITTER_MS` | `2000` (2s) | Random jitter added to send delay. |
|
|
61
|
+
| `MAX_RECONNECT_ATTEMPTS` | `10` | Max reconnect attempts before giving up. |
|
|
62
|
+
|
|
63
|
+
## Storage Locations
|
|
64
|
+
|
|
65
|
+
- `auth_info/`: WhatsApp authentication credentials (DO NOT commit).
|
|
66
|
+
- `data/`: SQLite database containing messages, chats, and contacts.
|
|
67
|
+
- `store/`: Baileys message store and `.whatsapp.lock` file.
|
|
68
|
+
- `uploads/`: Files to send (only files in this directory can be sent via `send_file`).
|
|
69
|
+
- `downloads/`: Downloaded media files.
|
|
70
|
+
- `contacts/`: VCF files for contact import.
|
|
71
|
+
|
|
72
|
+
## Running Tests
|
|
73
|
+
|
|
74
|
+
This project uses Vitest for testing.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Run all tests
|
|
78
|
+
npx vitest run
|
|
79
|
+
|
|
80
|
+
# Run tests with coverage
|
|
81
|
+
npx vitest run --coverage
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Known Limitations
|
|
85
|
+
|
|
86
|
+
- **No FTS5**: SQLite full-text search is not used; substring search (`LIKE`) is used instead.
|
|
87
|
+
- **No Read Receipts**: The server does not send read receipts.
|
|
88
|
+
- **No Reactions**: Message reactions are not supported.
|
|
89
|
+
- **No Stories**: WhatsApp Stories/Status updates are not accessible.
|
|
90
|
+
|
|
91
|
+
## Security Changes (What changed from upstream)
|
|
92
|
+
|
|
93
|
+
- **Path Traversal Protection**: Strict containment checks for `send_file` and `download_media` using resolved paths.
|
|
94
|
+
- **SSRF Validation**: `WHISPER_API_URL` is validated to prevent SSRF attacks (rejects localhost and private IP ranges).
|
|
95
|
+
- **Atomic Lock File**: Prevents multiple instances from connecting to the same WhatsApp account simultaneously.
|
|
96
|
+
- **Strict Filename Sanitization**: All filenames are sanitized to prevent injection and traversal.
|
|
97
|
+
- **Bounded Memory Caches**: Prevents unbounded memory growth in Baileys message retry and device tracking.
|
|
98
|
+
- **Pre-key Pruning**: Automatically prunes old pre-keys to prevent disk bloat.
|
|
99
|
+
- **Removed `reply_spam`**: Removed the UAE-specific spam reply tool for better general-purpose use.
|
|
100
|
+
- **Streamed Uploads**: `send_file` uses streams instead of loading full files into memory.
|
|
101
|
+
|
|
102
|
+
## Available Tools
|
|
103
|
+
|
|
104
|
+
- `list_chats`: List chats sorted by last activity.
|
|
105
|
+
- `get_chat`: Get chat details with recent messages.
|
|
106
|
+
- `list_messages`: Get messages from a chat.
|
|
107
|
+
- `search_messages`: Substring search across messages.
|
|
108
|
+
- `search_contacts`: Find contacts by name or phone number.
|
|
109
|
+
- `get_message_context`: Get messages surrounding a specific message.
|
|
110
|
+
- `get_my_profile`: Get your own JID and profile info.
|
|
111
|
+
- `update_contact`: Update a contact's display name.
|
|
112
|
+
- `sync_contacts`: Import phone contacts from a VCF file.
|
|
113
|
+
- `send_message`: Send a text message (requires confirmation).
|
|
114
|
+
- `send_file`: Send a media file (requires confirmation).
|
|
115
|
+
- `delete_message`: Delete a message (requires confirmation).
|
|
116
|
+
- `delete_chat`: Delete an entire chat (requires confirmation).
|
|
117
|
+
- `download_media`: Download media from a message to disk.
|
|
118
|
+
- `transcribe_voice_note`: Transcribe a voice note to text.
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/connection.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { closeTestDb, seedTestDb, setupTestDb } from "./helpers/test-db.js";
|
|
6
|
+
import { createFakeBaileysSocket } from "./helpers/fake-baileys.js";
|
|
7
|
+
const mockState = vi.hoisted(() => ({
|
|
8
|
+
sockets: [],
|
|
9
|
+
}));
|
|
10
|
+
vi.mock("@whiskeysockets/baileys", () => ({
|
|
11
|
+
default: vi.fn(() => {
|
|
12
|
+
const socket = createFakeBaileysSocket();
|
|
13
|
+
mockState.sockets.push(socket);
|
|
14
|
+
return socket;
|
|
15
|
+
}),
|
|
16
|
+
DisconnectReason: { loggedOut: 401, connectionReplaced: 440 },
|
|
17
|
+
fetchLatestBaileysVersion: vi.fn(async () => ({ version: [2, 3000, 0] })),
|
|
18
|
+
downloadMediaMessage: vi.fn(),
|
|
19
|
+
getContentType: vi.fn(),
|
|
20
|
+
initAuthCreds: vi.fn(() => ({})),
|
|
21
|
+
BufferJSON: {
|
|
22
|
+
replacer: (_key, value) => value,
|
|
23
|
+
reviver: (_key, value) => value,
|
|
24
|
+
},
|
|
25
|
+
proto: {
|
|
26
|
+
Message: {
|
|
27
|
+
AppStateSyncKeyData: {
|
|
28
|
+
fromObject: (value) => value,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
function parseToolJson(result) {
|
|
34
|
+
const text = result.content?.find((entry) => entry.type === "text")?.text || "";
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(text);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return text;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function latestSocket() {
|
|
43
|
+
const socket = mockState.sockets.at(-1);
|
|
44
|
+
if (!socket) {
|
|
45
|
+
throw new Error("No fake socket was created");
|
|
46
|
+
}
|
|
47
|
+
return socket;
|
|
48
|
+
}
|
|
49
|
+
describe("connection lifecycle", () => {
|
|
50
|
+
let server;
|
|
51
|
+
let client;
|
|
52
|
+
let whatsapp;
|
|
53
|
+
beforeEach(async () => {
|
|
54
|
+
vi.resetModules();
|
|
55
|
+
mockState.sockets.length = 0;
|
|
56
|
+
await setupTestDb();
|
|
57
|
+
seedTestDb();
|
|
58
|
+
whatsapp = await import("../whatsapp.js");
|
|
59
|
+
await whatsapp.initWhatsApp();
|
|
60
|
+
latestSocket().emitConnectionOpen();
|
|
61
|
+
server = new McpServer({ name: "whatsapp-test", version: "1.0.0" });
|
|
62
|
+
const { registerTools } = await import("../tools.js");
|
|
63
|
+
registerTools(server);
|
|
64
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
65
|
+
client = new Client({ name: "whatsapp-test-client", version: "1.0.0" });
|
|
66
|
+
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
|
|
67
|
+
});
|
|
68
|
+
afterEach(async () => {
|
|
69
|
+
await Promise.all([client?.close(), server?.close()]);
|
|
70
|
+
await whatsapp?.closeWhatsApp();
|
|
71
|
+
closeTestDb();
|
|
72
|
+
vi.useRealTimers();
|
|
73
|
+
});
|
|
74
|
+
it("waits for reconnection after a 440 connectionReplaced disconnect and succeeds after reconnect", async () => {
|
|
75
|
+
latestSocket().emitConnectionClose(440);
|
|
76
|
+
const toolCall = client.callTool({
|
|
77
|
+
name: "list_chats",
|
|
78
|
+
arguments: { limit: 5 },
|
|
79
|
+
});
|
|
80
|
+
const stateBeforeReconnect = await Promise.race([
|
|
81
|
+
toolCall.then(() => "settled"),
|
|
82
|
+
new Promise((resolve) => setTimeout(() => resolve("pending"), 25)),
|
|
83
|
+
]);
|
|
84
|
+
expect(stateBeforeReconnect).toBe("pending");
|
|
85
|
+
await whatsapp.initWhatsApp();
|
|
86
|
+
latestSocket().emitConnectionOpen();
|
|
87
|
+
const result = await toolCall;
|
|
88
|
+
const payload = parseToolJson(result);
|
|
89
|
+
expect(result.isError).toBeFalsy();
|
|
90
|
+
expect(Array.isArray(payload)).toBe(true);
|
|
91
|
+
const postReconnect = (await client.callTool({
|
|
92
|
+
name: "list_chats",
|
|
93
|
+
arguments: { limit: 5 },
|
|
94
|
+
}));
|
|
95
|
+
expect(postReconnect.isError).toBeFalsy();
|
|
96
|
+
});
|
|
97
|
+
it("clears pending reconnect timer on shutdown", async () => {
|
|
98
|
+
vi.useFakeTimers();
|
|
99
|
+
latestSocket().emitConnectionClose(500);
|
|
100
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
101
|
+
await whatsapp.closeWhatsApp();
|
|
102
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=connection.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.test.js","sourceRoot":"","sources":["../../src/__tests__/connection.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disconnect.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/disconnect.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { createFakeBaileysSocket } from "./helpers/fake-baileys.js";
|
|
4
|
+
import { closeTestDb, seedTestDb, setupTestDb } from "./helpers/test-db.js";
|
|
5
|
+
const mockState = vi.hoisted(() => ({
|
|
6
|
+
sockets: [],
|
|
7
|
+
}));
|
|
8
|
+
vi.mock("@whiskeysockets/baileys", () => ({
|
|
9
|
+
default: vi.fn(() => {
|
|
10
|
+
const socket = createFakeBaileysSocket();
|
|
11
|
+
mockState.sockets.push(socket);
|
|
12
|
+
return socket;
|
|
13
|
+
}),
|
|
14
|
+
DisconnectReason: {
|
|
15
|
+
loggedOut: 401,
|
|
16
|
+
forbidden: 403,
|
|
17
|
+
connectionLost: 408,
|
|
18
|
+
multideviceMismatch: 411,
|
|
19
|
+
connectionClosed: 428,
|
|
20
|
+
connectionReplaced: 440,
|
|
21
|
+
badSession: 500,
|
|
22
|
+
unavailableService: 503,
|
|
23
|
+
restartRequired: 515,
|
|
24
|
+
},
|
|
25
|
+
fetchLatestBaileysVersion: vi.fn(async () => ({ version: [2, 3000, 0] })),
|
|
26
|
+
downloadMediaMessage: vi.fn(),
|
|
27
|
+
getContentType: vi.fn(),
|
|
28
|
+
initAuthCreds: vi.fn(() => ({})),
|
|
29
|
+
BufferJSON: {
|
|
30
|
+
replacer: (_key, value) => value,
|
|
31
|
+
reviver: (_key, value) => value,
|
|
32
|
+
},
|
|
33
|
+
proto: {
|
|
34
|
+
Message: {
|
|
35
|
+
AppStateSyncKeyData: {
|
|
36
|
+
fromObject: (value) => value,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
function latestSocket() {
|
|
42
|
+
const socket = mockState.sockets.at(-1);
|
|
43
|
+
if (!socket) {
|
|
44
|
+
throw new Error("No fake socket was created");
|
|
45
|
+
}
|
|
46
|
+
return socket;
|
|
47
|
+
}
|
|
48
|
+
async function importWhatsAppResolved() {
|
|
49
|
+
const whatsapp = await import("../whatsapp.js");
|
|
50
|
+
whatsapp.resolveConnectionAsReadOnly();
|
|
51
|
+
return whatsapp;
|
|
52
|
+
}
|
|
53
|
+
describe("disconnect handling", () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
vi.resetModules();
|
|
56
|
+
vi.useFakeTimers();
|
|
57
|
+
mockState.sockets.length = 0;
|
|
58
|
+
delete process.env.MAX_RECONNECT_ATTEMPTS;
|
|
59
|
+
});
|
|
60
|
+
afterEach(async () => {
|
|
61
|
+
vi.useRealTimers();
|
|
62
|
+
vi.restoreAllMocks();
|
|
63
|
+
const whatsapp = await import("../whatsapp.js");
|
|
64
|
+
await whatsapp.closeWhatsApp();
|
|
65
|
+
});
|
|
66
|
+
it("401 (loggedOut) does not reconnect, clears auth, and logs re-scan QR required", async () => {
|
|
67
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
68
|
+
const readdirSpy = vi.spyOn(fs.promises, "readdir").mockResolvedValue(["creds.json"]);
|
|
69
|
+
const rmSpy = vi.spyOn(fs.promises, "rm").mockResolvedValue(undefined);
|
|
70
|
+
const whatsapp = await importWhatsAppResolved();
|
|
71
|
+
await whatsapp.handleDisconnect(401, null);
|
|
72
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
73
|
+
expect(readdirSpy).toHaveBeenCalled();
|
|
74
|
+
expect(rmSpy).toHaveBeenCalled();
|
|
75
|
+
expect(errorSpy.mock.calls.flat().join(" ")).toContain("re-scan QR required");
|
|
76
|
+
});
|
|
77
|
+
it("403 (forbidden) does not reconnect and logs warning", async () => {
|
|
78
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
79
|
+
const whatsapp = await importWhatsAppResolved();
|
|
80
|
+
await whatsapp.handleDisconnect(403, null);
|
|
81
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
82
|
+
expect(errorSpy.mock.calls.flat().join(" ")).toContain("forbidden");
|
|
83
|
+
});
|
|
84
|
+
it("408 (connectionLost) reconnects with backoff", async () => {
|
|
85
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
86
|
+
const whatsapp = await importWhatsAppResolved();
|
|
87
|
+
await whatsapp.handleDisconnect(408, null);
|
|
88
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
89
|
+
expect(errorSpy.mock.calls.flat().join(" ")).toContain("Reconnecting in 2s");
|
|
90
|
+
});
|
|
91
|
+
it("411 (multideviceMismatch) does not reconnect and logs update Baileys", async () => {
|
|
92
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
93
|
+
const whatsapp = await importWhatsAppResolved();
|
|
94
|
+
await whatsapp.handleDisconnect(411, null);
|
|
95
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
96
|
+
expect(errorSpy.mock.calls.flat().join(" ")).toContain("update Baileys");
|
|
97
|
+
});
|
|
98
|
+
it("428 (connectionClosed) reconnects with backoff", async () => {
|
|
99
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
100
|
+
const whatsapp = await importWhatsAppResolved();
|
|
101
|
+
await whatsapp.handleDisconnect(428, null);
|
|
102
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
103
|
+
expect(errorSpy.mock.calls.flat().join(" ")).toContain("Reconnecting in 2s");
|
|
104
|
+
});
|
|
105
|
+
it("440 (connectionReplaced) reconnects with extended delay", async () => {
|
|
106
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
107
|
+
const whatsapp = await importWhatsAppResolved();
|
|
108
|
+
await whatsapp.handleDisconnect(440, null);
|
|
109
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
110
|
+
expect(errorSpy.mock.calls.flat().join(" ")).toContain("Reconnecting in 10s");
|
|
111
|
+
});
|
|
112
|
+
it("500 (badSession) clears auth and reconnects", async () => {
|
|
113
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
114
|
+
const readdirSpy = vi.spyOn(fs.promises, "readdir").mockResolvedValue(["creds.json"]);
|
|
115
|
+
const rmSpy = vi.spyOn(fs.promises, "rm").mockResolvedValue(undefined);
|
|
116
|
+
const whatsapp = await importWhatsAppResolved();
|
|
117
|
+
await whatsapp.handleDisconnect(500, null);
|
|
118
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
119
|
+
expect(readdirSpy).toHaveBeenCalled();
|
|
120
|
+
expect(rmSpy).toHaveBeenCalled();
|
|
121
|
+
expect(errorSpy.mock.calls.flat().join(" ")).toContain("Bad session");
|
|
122
|
+
});
|
|
123
|
+
it("503 (unavailableService) reconnects with backoff", async () => {
|
|
124
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
125
|
+
const whatsapp = await importWhatsAppResolved();
|
|
126
|
+
await whatsapp.handleDisconnect(503, null);
|
|
127
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
128
|
+
expect(errorSpy.mock.calls.flat().join(" ")).toContain("Reconnecting in 2s");
|
|
129
|
+
});
|
|
130
|
+
it("515 (restartRequired) reconnects immediately", async () => {
|
|
131
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
132
|
+
const whatsapp = await importWhatsAppResolved();
|
|
133
|
+
await whatsapp.handleDisconnect(515, null);
|
|
134
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
135
|
+
expect(errorSpy.mock.calls.flat().join(" ")).toContain("Reconnecting in 0s");
|
|
136
|
+
});
|
|
137
|
+
it("unknown code reconnects with backoff and logs warning", async () => {
|
|
138
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
139
|
+
const whatsapp = await importWhatsAppResolved();
|
|
140
|
+
await whatsapp.handleDisconnect(999, null);
|
|
141
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
142
|
+
expect(errorSpy.mock.calls.flat().join(" ")).toContain("Unknown disconnect code");
|
|
143
|
+
});
|
|
144
|
+
it("stops reconnecting after MAX_RECONNECT_ATTEMPTS", async () => {
|
|
145
|
+
process.env.MAX_RECONNECT_ATTEMPTS = "2";
|
|
146
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
147
|
+
const whatsapp = await importWhatsAppResolved();
|
|
148
|
+
await whatsapp.handleDisconnect(428, null);
|
|
149
|
+
await whatsapp.handleDisconnect(428, null);
|
|
150
|
+
await whatsapp.handleDisconnect(428, null);
|
|
151
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
152
|
+
expect(errorSpy.mock.calls.flat().join(" ")).toContain("Fatal");
|
|
153
|
+
});
|
|
154
|
+
it("successful connection resets reconnect attempt counter to zero", async () => {
|
|
155
|
+
await setupTestDb();
|
|
156
|
+
seedTestDb();
|
|
157
|
+
const whatsapp = await import("../whatsapp.js");
|
|
158
|
+
await whatsapp.initWhatsApp();
|
|
159
|
+
latestSocket().emitConnectionClose(428);
|
|
160
|
+
expect(whatsapp.getReconnectAttempts()).toBe(1);
|
|
161
|
+
latestSocket().emitConnectionOpen();
|
|
162
|
+
expect(whatsapp.getReconnectAttempts()).toBe(0);
|
|
163
|
+
closeTestDb();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
//# sourceMappingURL=disconnect.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disconnect.test.js","sourceRoot":"","sources":["../../src/__tests__/disconnect.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,uBAAuB,EAA0B,MAAM,2BAA2B,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE5E,MAAM,SAAS,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAClC,OAAO,EAAE,EAAyB;CACnC,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,yBAAyB,EAAE,GAAG,EAAE,CAAC,CAAC;IACxC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE;QAClB,MAAM,MAAM,GAAG,uBAAuB,EAAE,CAAC;QACzC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/B,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IACF,gBAAgB,EAAE;QAChB,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,GAAG;QACd,cAAc,EAAE,GAAG;QACnB,mBAAmB,EAAE,GAAG;QACxB,gBAAgB,EAAE,GAAG;QACrB,kBAAkB,EAAE,GAAG;QACvB,UAAU,EAAE,GAAG;QACf,kBAAkB,EAAE,GAAG;QACvB,eAAe,EAAE,GAAG;KACrB;IACD,yBAAyB,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IACzE,oBAAoB,EAAE,EAAE,CAAC,EAAE,EAAE;IAC7B,cAAc,EAAE,EAAE,CAAC,EAAE,EAAE;IACvB,aAAa,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAChC,UAAU,EAAE;QACV,QAAQ,EAAE,CAAC,IAAY,EAAE,KAAc,EAAE,EAAE,CAAC,KAAK;QACjD,OAAO,EAAE,CAAC,IAAY,EAAE,KAAc,EAAE,EAAE,CAAC,KAAK;KACjD;IACD,KAAK,EAAE;QACL,OAAO,EAAE;YACP,mBAAmB,EAAE;gBACnB,UAAU,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK;aACtC;SACF;KACF;CACF,CAAC,CAAC,CAAC;AAEJ,SAAS,YAAY;IACnB,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,sBAAsB;IACnC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAChD,QAAQ,CAAC,2BAA2B,EAAE,CAAC;IACvC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,YAAY,EAAE,CAAC;QAClB,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,SAAS,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,EAAE,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAChD,MAAM,QAAQ,CAAC,aAAa,EAAE,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+EAA+E,EAAE,KAAK,IAAI,EAAE;QAC7F,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,iBAAiB,CAAC,CAAC,YAAY,CAAQ,CAAC,CAAC;QAC7F,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAEvE,MAAM,QAAQ,GAAG,MAAM,sBAAsB,EAAE,CAAC;QAChD,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,UAAU,CAAC,CAAC,gBAAgB,EAAE,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,CAAC,gBAAgB,EAAE,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,sBAAsB,EAAE,CAAC;QAEhD,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,sBAAsB,EAAE,CAAC;QAEhD,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sEAAsE,EAAE,KAAK,IAAI,EAAE;QACpF,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,sBAAsB,EAAE,CAAC;QAEhD,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,sBAAsB,EAAE,CAAC;QAEhD,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,sBAAsB,EAAE,CAAC;QAEhD,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,iBAAiB,CAAC,CAAC,YAAY,CAAQ,CAAC,CAAC;QAC7F,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;QACvE,MAAM,QAAQ,GAAG,MAAM,sBAAsB,EAAE,CAAC;QAEhD,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,UAAU,CAAC,CAAC,gBAAgB,EAAE,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,CAAC,gBAAgB,EAAE,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,sBAAsB,EAAE,CAAC;QAEhD,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,sBAAsB,EAAE,CAAC;QAEhD,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,sBAAsB,EAAE,CAAC;QAEhD,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,OAAO,CAAC,GAAG,CAAC,sBAAsB,GAAG,GAAG,CAAC;QACzC,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,sBAAsB,EAAE,CAAC;QAEhD,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC3C,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC3C,MAAM,QAAQ,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,MAAM,WAAW,EAAE,CAAC;QACpB,UAAU,EAAE,CAAC;QAEb,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAChD,MAAM,QAAQ,CAAC,YAAY,EAAE,CAAC;QAE9B,YAAY,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAEhD,YAAY,EAAE,CAAC,kBAAkB,EAAE,CAAC;QACpC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAEhD,WAAW,EAAE,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download-media.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/download-media.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
const getMessageBlobMock = vi.fn(() => JSON.stringify({ message: { imageMessage: { mimetype: "image/png" } } }));
|
|
6
|
+
vi.mock("@whiskeysockets/baileys", () => ({
|
|
7
|
+
default: vi.fn(() => ({})),
|
|
8
|
+
DisconnectReason: { loggedOut: 401, connectionReplaced: 440 },
|
|
9
|
+
fetchLatestBaileysVersion: vi.fn(async () => ({ version: [2, 3000, 0] })),
|
|
10
|
+
downloadMediaMessage: vi.fn(async () => Buffer.from("fake-image")),
|
|
11
|
+
getContentType: vi.fn(() => "imageMessage"),
|
|
12
|
+
initAuthCreds: vi.fn(() => ({})),
|
|
13
|
+
BufferJSON: {
|
|
14
|
+
replacer: (_key, value) => value,
|
|
15
|
+
reviver: (_key, value) => value,
|
|
16
|
+
},
|
|
17
|
+
proto: {
|
|
18
|
+
Message: {
|
|
19
|
+
AppStateSyncKeyData: {
|
|
20
|
+
fromObject: (value) => value,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
vi.mock("../db.js", () => ({
|
|
26
|
+
getMessageBlob: getMessageBlobMock,
|
|
27
|
+
getChatName: vi.fn(() => null),
|
|
28
|
+
getContactName: vi.fn(() => null),
|
|
29
|
+
upsertContact: vi.fn(),
|
|
30
|
+
upsertChat: vi.fn(),
|
|
31
|
+
saveJidMapping: vi.fn(),
|
|
32
|
+
upsertChats: vi.fn(),
|
|
33
|
+
upsertContacts: vi.fn(),
|
|
34
|
+
upsertMessages: vi.fn(),
|
|
35
|
+
upsertMessage: vi.fn(),
|
|
36
|
+
deleteChat: vi.fn(),
|
|
37
|
+
deleteChatMessages: vi.fn(),
|
|
38
|
+
getChats: vi.fn(() => []),
|
|
39
|
+
getChat: vi.fn(() => ({})),
|
|
40
|
+
getMessages: vi.fn(() => []),
|
|
41
|
+
searchMessages: vi.fn(() => []),
|
|
42
|
+
searchContacts: vi.fn(() => []),
|
|
43
|
+
getMessageContext: vi.fn(() => ({})),
|
|
44
|
+
getLastMessageKey: vi.fn(() => null),
|
|
45
|
+
getMessageFromMe: vi.fn(() => null),
|
|
46
|
+
deleteMessage: vi.fn(),
|
|
47
|
+
getMessageTypeById: vi.fn(() => "voice_note"),
|
|
48
|
+
getTranscription: vi.fn(() => null),
|
|
49
|
+
saveTranscription: vi.fn(),
|
|
50
|
+
}));
|
|
51
|
+
const originalDownloadsDir = process.env.DOWNLOADS_DIR;
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
vi.resetModules();
|
|
54
|
+
vi.clearAllMocks();
|
|
55
|
+
vi.doUnmock("../utils.js");
|
|
56
|
+
if (originalDownloadsDir === undefined) {
|
|
57
|
+
delete process.env.DOWNLOADS_DIR;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
process.env.DOWNLOADS_DIR = originalDownloadsDir;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
describe("download_media security hardening", () => {
|
|
64
|
+
it("sanitizes traversal messageId so writes stay inside downloads dir", async () => {
|
|
65
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "download-media-"));
|
|
66
|
+
process.env.DOWNLOADS_DIR = tempDir;
|
|
67
|
+
const whatsapp = await import("../whatsapp.js");
|
|
68
|
+
whatsapp.resolveConnectionAsReadOnly();
|
|
69
|
+
const result = await whatsapp.downloadMessageMedia("15550001111", "../../../tmp/evil");
|
|
70
|
+
expect(result.fileName).toBe(".._.._.._tmp_evil.png");
|
|
71
|
+
expect(path.resolve(String(result.filePath)).startsWith(path.resolve(tempDir) + path.sep)).toBe(true);
|
|
72
|
+
expect(fs.existsSync(String(result.filePath))).toBe(true);
|
|
73
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
74
|
+
});
|
|
75
|
+
it("sanitizes backslashes in messageId", async () => {
|
|
76
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "download-media-"));
|
|
77
|
+
process.env.DOWNLOADS_DIR = tempDir;
|
|
78
|
+
const whatsapp = await import("../whatsapp.js");
|
|
79
|
+
whatsapp.resolveConnectionAsReadOnly();
|
|
80
|
+
const result = await whatsapp.downloadMessageMedia("15550001111", "..\\..\\evil");
|
|
81
|
+
expect(result.fileName).toBe(".._.._evil.png");
|
|
82
|
+
expect(path.resolve(String(result.filePath)).startsWith(path.resolve(tempDir) + path.sep)).toBe(true);
|
|
83
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
84
|
+
});
|
|
85
|
+
it("keeps traversal-like message IDs inside downloads directory", async () => {
|
|
86
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "download-media-"));
|
|
87
|
+
process.env.DOWNLOADS_DIR = tempDir;
|
|
88
|
+
const whatsapp = await import("../whatsapp.js");
|
|
89
|
+
whatsapp.resolveConnectionAsReadOnly();
|
|
90
|
+
const result = await whatsapp.downloadMessageMedia("15550001111", "../../../tmp/evil");
|
|
91
|
+
expect(path.resolve(String(result.filePath)).startsWith(path.resolve(tempDir) + path.sep)).toBe(true);
|
|
92
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
93
|
+
});
|
|
94
|
+
it("keeps normal message IDs unchanged", async () => {
|
|
95
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "download-media-"));
|
|
96
|
+
process.env.DOWNLOADS_DIR = tempDir;
|
|
97
|
+
const whatsapp = await import("../whatsapp.js");
|
|
98
|
+
whatsapp.resolveConnectionAsReadOnly();
|
|
99
|
+
const result = await whatsapp.downloadMessageMedia("15550001111", "abc-123-def");
|
|
100
|
+
expect(result.fileName).toBe("abc-123-def.png");
|
|
101
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
102
|
+
});
|
|
103
|
+
it("exports strict sanitizeFilename allowlist behavior", async () => {
|
|
104
|
+
const { sanitizeFilename } = await import("../utils.js");
|
|
105
|
+
expect(sanitizeFilename("../../../tmp/evil")).toBe(".._.._.._tmp_evil");
|
|
106
|
+
expect(sanitizeFilename("..\\..\\evil")).toBe(".._.._evil");
|
|
107
|
+
expect(sanitizeFilename("abc-123_DEF.9")).toBe("abc-123_DEF.9");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
//# sourceMappingURL=download-media.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download-media.test.js","sourceRoot":"","sources":["../../src/__tests__/download-media.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-failures.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/failures/connection-failures.test.ts"],"names":[],"mappings":""}
|