@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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
2
|
+
import { closeTestDb, seedTestDb, setupTestDb } from "./helpers/test-db.js";
|
|
3
|
+
import { createMcpTestClient } from "./helpers/mcp-test-client.js";
|
|
4
|
+
// ─── Fix 1: JID device suffix stripping ─────────────────────────────
|
|
5
|
+
describe("toJid — device suffix stripping", () => {
|
|
6
|
+
let toJid;
|
|
7
|
+
beforeAll(async () => {
|
|
8
|
+
const utils = await import("../utils.js");
|
|
9
|
+
toJid = utils.toJid;
|
|
10
|
+
});
|
|
11
|
+
it("strips device suffix from phone JID", () => {
|
|
12
|
+
expect(toJid("1234567890:5@s.whatsapp.net")).toBe("1234567890@s.whatsapp.net");
|
|
13
|
+
});
|
|
14
|
+
it("strips multi-digit device suffix", () => {
|
|
15
|
+
expect(toJid("9876543210:42@s.whatsapp.net")).toBe("9876543210@s.whatsapp.net");
|
|
16
|
+
});
|
|
17
|
+
it("passes through normal phone JID unchanged", () => {
|
|
18
|
+
expect(toJid("1234567890@s.whatsapp.net")).toBe("1234567890@s.whatsapp.net");
|
|
19
|
+
});
|
|
20
|
+
it("passes through group JID unchanged", () => {
|
|
21
|
+
expect(toJid("123456789-987654321@g.us")).toBe("123456789-987654321@g.us");
|
|
22
|
+
});
|
|
23
|
+
it("passes through LID JID unchanged", () => {
|
|
24
|
+
expect(toJid("12345@lid")).toBe("12345@lid");
|
|
25
|
+
});
|
|
26
|
+
it("normalizes bare phone number to JID", () => {
|
|
27
|
+
expect(toJid("1234567890")).toBe("1234567890@s.whatsapp.net");
|
|
28
|
+
});
|
|
29
|
+
it("strips + prefix from phone number", () => {
|
|
30
|
+
expect(toJid("+1234567890")).toBe("1234567890@s.whatsapp.net");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
// ─── Fix 2: Search LIKE wildcard escaping ────────────────────────────
|
|
34
|
+
describe("searchMessages — LIKE wildcard escaping", () => {
|
|
35
|
+
let dbModule;
|
|
36
|
+
beforeAll(async () => {
|
|
37
|
+
await setupTestDb();
|
|
38
|
+
seedTestDb();
|
|
39
|
+
dbModule = await import("../db.js");
|
|
40
|
+
// Insert messages with specific content for wildcard tests
|
|
41
|
+
const db = dbModule.getDb();
|
|
42
|
+
db.prepare("INSERT INTO messages (id, chat_jid, from_me, type, text, timestamp, has_media) VALUES (?, ?, ?, ?, ?, ?, ?)").run("msg-percent", "15550001111@s.whatsapp.net", 0, "text", "100% done", 1700000010, 0);
|
|
43
|
+
db.prepare("INSERT INTO messages (id, chat_jid, from_me, type, text, timestamp, has_media) VALUES (?, ?, ?, ?, ?, ?, ?)").run("msg-underscore", "15550001111@s.whatsapp.net", 0, "text", "use_snake_case", 1700000020, 0);
|
|
44
|
+
db.prepare("INSERT INTO messages (id, chat_jid, from_me, type, text, timestamp, has_media) VALUES (?, ?, ?, ?, ?, ?, ?)").run("msg-normal", "15550001111@s.whatsapp.net", 0, "text", "normal message", 1700000030, 0);
|
|
45
|
+
});
|
|
46
|
+
afterAll(() => {
|
|
47
|
+
closeTestDb();
|
|
48
|
+
});
|
|
49
|
+
it("searching '%' does NOT match all messages", () => {
|
|
50
|
+
const results = dbModule.searchMessages("%");
|
|
51
|
+
// Should only match messages containing a literal "%" character
|
|
52
|
+
expect(results.length).toBeLessThanOrEqual(1);
|
|
53
|
+
if (results.length > 0) {
|
|
54
|
+
expect(results.every((r) => typeof r.text === "string" && r.text.includes("%"))).toBe(true);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
it("searching '_' does NOT match single characters", () => {
|
|
58
|
+
const results = dbModule.searchMessages("_");
|
|
59
|
+
// Should only match messages containing a literal "_" character
|
|
60
|
+
for (const r of results) {
|
|
61
|
+
expect(typeof r.text === "string" && r.text.includes("_")).toBe(true);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
it("finds messages with literal % in text", () => {
|
|
65
|
+
const results = dbModule.searchMessages("100%");
|
|
66
|
+
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
67
|
+
expect(results.some((r) => r.text === "100% done")).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
it("finds messages with literal _ in text", () => {
|
|
70
|
+
const results = dbModule.searchMessages("snake_case");
|
|
71
|
+
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
72
|
+
expect(results.some((r) => r.text === "use_snake_case")).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it("normal substring search still works", () => {
|
|
75
|
+
const results = dbModule.searchMessages("normal");
|
|
76
|
+
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
77
|
+
expect(results.some((r) => r.text === "normal message")).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
// ─── Fix 3: VCF path containment ────────────────────────────────────
|
|
81
|
+
describe("importContactsFromVcf — path containment", () => {
|
|
82
|
+
let importContactsFromVcf;
|
|
83
|
+
let Database;
|
|
84
|
+
beforeAll(async () => {
|
|
85
|
+
const mod = await import("../import-contacts.js");
|
|
86
|
+
importContactsFromVcf = mod.importContactsFromVcf;
|
|
87
|
+
const dbMod = await import("better-sqlite3");
|
|
88
|
+
Database = (dbMod.default ?? dbMod);
|
|
89
|
+
});
|
|
90
|
+
it("rejects /etc/passwd as VCF path", () => {
|
|
91
|
+
// Create a minimal in-memory DB with schema
|
|
92
|
+
const db = new Database(":memory:");
|
|
93
|
+
db.exec(`
|
|
94
|
+
CREATE TABLE IF NOT EXISTS chats (jid TEXT PRIMARY KEY, name TEXT, conversation_ts INTEGER DEFAULT 0, unread_count INTEGER DEFAULT 0);
|
|
95
|
+
CREATE TABLE IF NOT EXISTS contacts (jid TEXT PRIMARY KEY, name TEXT, notify TEXT);
|
|
96
|
+
`);
|
|
97
|
+
expect(() => importContactsFromVcf(db, "/etc/passwd")).toThrow(/VCF path not allowed/);
|
|
98
|
+
db.close();
|
|
99
|
+
});
|
|
100
|
+
it("rejects path traversal attempts", () => {
|
|
101
|
+
const db = new Database(":memory:");
|
|
102
|
+
db.exec(`
|
|
103
|
+
CREATE TABLE IF NOT EXISTS chats (jid TEXT PRIMARY KEY, name TEXT, conversation_ts INTEGER DEFAULT 0, unread_count INTEGER DEFAULT 0);
|
|
104
|
+
CREATE TABLE IF NOT EXISTS contacts (jid TEXT PRIMARY KEY, name TEXT, notify TEXT);
|
|
105
|
+
`);
|
|
106
|
+
expect(() => importContactsFromVcf(db, "../../../etc/shadow")).toThrow(/VCF path not allowed/);
|
|
107
|
+
db.close();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
// ─── Fix 4: list_chats limit passthrough ─────────────────────────────
|
|
111
|
+
describe("list_chats — limit passthrough to DB", () => {
|
|
112
|
+
let client;
|
|
113
|
+
beforeAll(async () => {
|
|
114
|
+
// Re-setup DB for MCP client tests (clean state)
|
|
115
|
+
await setupTestDb();
|
|
116
|
+
const dbModule = await import("../db.js");
|
|
117
|
+
const db = dbModule.getDb();
|
|
118
|
+
// Seed 10 chats with messages
|
|
119
|
+
for (let i = 1; i <= 10; i++) {
|
|
120
|
+
const jid = `1555000${String(i).padStart(4, "0")}@s.whatsapp.net`;
|
|
121
|
+
db.prepare("INSERT OR REPLACE INTO chats (jid, name, conversation_ts, unread_count) VALUES (?, ?, ?, ?)").run(jid, `Contact ${i}`, 1700000000 + i * 100, 0);
|
|
122
|
+
db.prepare("INSERT OR REPLACE INTO messages (id, chat_jid, from_me, type, text, timestamp, has_media) VALUES (?, ?, ?, ?, ?, ?, ?)").run(`msg-${i}`, jid, 0, "text", `Message from contact ${i}`, 1700000000 + i * 100, 0);
|
|
123
|
+
}
|
|
124
|
+
client = await createMcpTestClient();
|
|
125
|
+
});
|
|
126
|
+
afterAll(async () => {
|
|
127
|
+
await client?.close();
|
|
128
|
+
closeTestDb();
|
|
129
|
+
});
|
|
130
|
+
it("returns at most `limit` chats from DB", async () => {
|
|
131
|
+
const result = await client.callTool("list_chats", { limit: 5 });
|
|
132
|
+
expect(Array.isArray(result)).toBe(true);
|
|
133
|
+
expect(result.length).toBeLessThanOrEqual(5);
|
|
134
|
+
});
|
|
135
|
+
it("returns more chats with higher limit", async () => {
|
|
136
|
+
const result = await client.callTool("list_chats", { limit: 100 });
|
|
137
|
+
expect(Array.isArray(result)).toBe(true);
|
|
138
|
+
expect(result.length).toBeGreaterThanOrEqual(10);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
//# sourceMappingURL=medium-fixes.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"medium-fixes.test.js","sourceRoot":"","sources":["../../src/__tests__/medium-fixes.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/rate-limit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createFakeBaileysSocket } from "./helpers/fake-baileys.js";
|
|
3
|
+
import { closeTestDb, seedTestDb, setupTestDb } from "./helpers/test-db.js";
|
|
4
|
+
const mockState = vi.hoisted(() => ({
|
|
5
|
+
sockets: [],
|
|
6
|
+
}));
|
|
7
|
+
vi.mock("@whiskeysockets/baileys", () => ({
|
|
8
|
+
default: vi.fn(() => {
|
|
9
|
+
const socket = createFakeBaileysSocket();
|
|
10
|
+
mockState.sockets.push(socket);
|
|
11
|
+
return socket;
|
|
12
|
+
}),
|
|
13
|
+
DisconnectReason: {
|
|
14
|
+
loggedOut: 401,
|
|
15
|
+
connectionClosed: 428,
|
|
16
|
+
connectionReplaced: 440,
|
|
17
|
+
badSession: 500,
|
|
18
|
+
unavailableService: 503,
|
|
19
|
+
restartRequired: 515,
|
|
20
|
+
connectionLost: 408,
|
|
21
|
+
forbidden: 403,
|
|
22
|
+
multideviceMismatch: 411,
|
|
23
|
+
},
|
|
24
|
+
fetchLatestBaileysVersion: vi.fn(async () => ({ version: [2, 3000, 0] })),
|
|
25
|
+
downloadMediaMessage: vi.fn(),
|
|
26
|
+
getContentType: vi.fn(),
|
|
27
|
+
initAuthCreds: vi.fn(() => ({})),
|
|
28
|
+
BufferJSON: {
|
|
29
|
+
replacer: (_key, value) => value,
|
|
30
|
+
reviver: (_key, value) => value,
|
|
31
|
+
},
|
|
32
|
+
proto: {
|
|
33
|
+
Message: {
|
|
34
|
+
AppStateSyncKeyData: {
|
|
35
|
+
fromObject: (value) => value,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
function latestSocket() {
|
|
41
|
+
const socket = mockState.sockets.at(-1);
|
|
42
|
+
if (!socket)
|
|
43
|
+
throw new Error("No fake socket was created");
|
|
44
|
+
return socket;
|
|
45
|
+
}
|
|
46
|
+
describe("SendRateLimiter", () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
vi.useFakeTimers();
|
|
49
|
+
});
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
vi.useRealTimers();
|
|
52
|
+
});
|
|
53
|
+
it("delays when called rapidly", async () => {
|
|
54
|
+
const { SendRateLimiter } = await import("../whatsapp.js");
|
|
55
|
+
const limiter = new SendRateLimiter(3000, 0); // no jitter for deterministic test
|
|
56
|
+
// First call: no delay (lastSendTimestamp is 0)
|
|
57
|
+
const start = Date.now();
|
|
58
|
+
const p1 = limiter.throttle();
|
|
59
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
60
|
+
await p1;
|
|
61
|
+
const afterFirst = Date.now();
|
|
62
|
+
// Second call immediately: should delay by ~3000ms
|
|
63
|
+
const p2 = limiter.throttle();
|
|
64
|
+
// Should NOT have resolved yet
|
|
65
|
+
let resolved = false;
|
|
66
|
+
p2.then(() => { resolved = true; });
|
|
67
|
+
await vi.advanceTimersByTimeAsync(2999);
|
|
68
|
+
expect(resolved).toBe(false);
|
|
69
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
70
|
+
await p2;
|
|
71
|
+
expect(Date.now() - afterFirst).toBeGreaterThanOrEqual(3000);
|
|
72
|
+
});
|
|
73
|
+
it("does not delay when enough time has elapsed", async () => {
|
|
74
|
+
const { SendRateLimiter } = await import("../whatsapp.js");
|
|
75
|
+
const limiter = new SendRateLimiter(3000, 0);
|
|
76
|
+
await limiter.throttle();
|
|
77
|
+
// Advance well past the interval
|
|
78
|
+
vi.advanceTimersByTime(5000);
|
|
79
|
+
const before = Date.now();
|
|
80
|
+
const p2 = limiter.throttle();
|
|
81
|
+
// Should resolve almost immediately (no setTimeout needed)
|
|
82
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
83
|
+
await p2;
|
|
84
|
+
// Elapsed should be minimal (just jitter=0)
|
|
85
|
+
expect(Date.now() - before).toBeLessThan(100);
|
|
86
|
+
});
|
|
87
|
+
it("adds random jitter to delay", async () => {
|
|
88
|
+
const { SendRateLimiter } = await import("../whatsapp.js");
|
|
89
|
+
// First call: no jitter. Second call: jitter = 0.5 * 2000 = 1000ms
|
|
90
|
+
vi.spyOn(Math, "random").mockReturnValueOnce(0).mockReturnValueOnce(0.5);
|
|
91
|
+
const limiter = new SendRateLimiter(3000, 2000);
|
|
92
|
+
await limiter.throttle();
|
|
93
|
+
// Second call: should delay 3000ms (min interval) + 1000ms (jitter) = 4000ms
|
|
94
|
+
const p2 = limiter.throttle();
|
|
95
|
+
let resolved = false;
|
|
96
|
+
p2.then(() => { resolved = true; });
|
|
97
|
+
await vi.advanceTimersByTimeAsync(3999);
|
|
98
|
+
expect(resolved).toBe(false);
|
|
99
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
100
|
+
await p2;
|
|
101
|
+
expect(resolved).toBe(true);
|
|
102
|
+
vi.spyOn(Math, "random").mockRestore();
|
|
103
|
+
});
|
|
104
|
+
it("always adds jitter even when enough time elapsed", async () => {
|
|
105
|
+
const { SendRateLimiter } = await import("../whatsapp.js");
|
|
106
|
+
vi.spyOn(Math, "random").mockReturnValueOnce(0).mockReturnValueOnce(0.5);
|
|
107
|
+
const limiter = new SendRateLimiter(3000, 2000);
|
|
108
|
+
await limiter.throttle();
|
|
109
|
+
vi.advanceTimersByTime(10000); // well past min interval
|
|
110
|
+
// Jitter = 0.5 * 2000 = 1000ms delay even though min interval passed
|
|
111
|
+
const p2 = limiter.throttle();
|
|
112
|
+
let resolved = false;
|
|
113
|
+
p2.then(() => { resolved = true; });
|
|
114
|
+
await vi.advanceTimersByTimeAsync(999);
|
|
115
|
+
expect(resolved).toBe(false);
|
|
116
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
117
|
+
await p2;
|
|
118
|
+
expect(resolved).toBe(true);
|
|
119
|
+
vi.spyOn(Math, "random").mockRestore();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe("rate limiting integration", () => {
|
|
123
|
+
const originalMinInterval = process.env.MIN_SEND_INTERVAL_MS;
|
|
124
|
+
const originalJitter = process.env.SEND_JITTER_MS;
|
|
125
|
+
beforeEach(async () => {
|
|
126
|
+
vi.resetModules();
|
|
127
|
+
vi.useFakeTimers();
|
|
128
|
+
mockState.sockets.length = 0;
|
|
129
|
+
process.env.MIN_SEND_INTERVAL_MS = "3000";
|
|
130
|
+
process.env.SEND_JITTER_MS = "0"; // no jitter for predictable integration tests
|
|
131
|
+
await setupTestDb();
|
|
132
|
+
seedTestDb();
|
|
133
|
+
});
|
|
134
|
+
afterEach(async () => {
|
|
135
|
+
const whatsapp = await import("../whatsapp.js");
|
|
136
|
+
await whatsapp.closeWhatsApp();
|
|
137
|
+
closeTestDb();
|
|
138
|
+
vi.useRealTimers();
|
|
139
|
+
if (originalMinInterval === undefined) {
|
|
140
|
+
delete process.env.MIN_SEND_INTERVAL_MS;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
process.env.MIN_SEND_INTERVAL_MS = originalMinInterval;
|
|
144
|
+
}
|
|
145
|
+
if (originalJitter === undefined) {
|
|
146
|
+
delete process.env.SEND_JITTER_MS;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
process.env.SEND_JITTER_MS = originalJitter;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
it("delays second rapid send_message by at least MIN_SEND_INTERVAL_MS", async () => {
|
|
153
|
+
const whatsapp = await import("../whatsapp.js");
|
|
154
|
+
await whatsapp.initWhatsApp();
|
|
155
|
+
const socket = latestSocket();
|
|
156
|
+
socket.emitConnectionOpen();
|
|
157
|
+
// First send — should go immediately
|
|
158
|
+
const p1 = whatsapp.sendTextMessage("15550001111", "first");
|
|
159
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
160
|
+
await p1;
|
|
161
|
+
// Second send immediately — should be delayed
|
|
162
|
+
const p2 = whatsapp.sendTextMessage("15550001111", "second");
|
|
163
|
+
let secondDone = false;
|
|
164
|
+
p2.then(() => { secondDone = true; });
|
|
165
|
+
// At 2999ms, should NOT be done
|
|
166
|
+
await vi.advanceTimersByTimeAsync(2999);
|
|
167
|
+
expect(secondDone).toBe(false);
|
|
168
|
+
// At 3000ms, should complete
|
|
169
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
170
|
+
await p2;
|
|
171
|
+
expect(secondDone).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
it("respects env var configuration for MIN_SEND_INTERVAL_MS", async () => {
|
|
174
|
+
// Already set MIN_SEND_INTERVAL_MS=3000 and SEND_JITTER_MS=0 in beforeEach
|
|
175
|
+
// This test validates the module reads them
|
|
176
|
+
const whatsapp = await import("../whatsapp.js");
|
|
177
|
+
await whatsapp.initWhatsApp();
|
|
178
|
+
const socket = latestSocket();
|
|
179
|
+
socket.emitConnectionOpen();
|
|
180
|
+
const p1 = whatsapp.sendTextMessage("15550001111", "a");
|
|
181
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
182
|
+
await p1;
|
|
183
|
+
const p2 = whatsapp.sendTextMessage("15550001111", "b");
|
|
184
|
+
let done = false;
|
|
185
|
+
p2.then(() => { done = true; });
|
|
186
|
+
await vi.advanceTimersByTimeAsync(2500);
|
|
187
|
+
expect(done).toBe(false);
|
|
188
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
189
|
+
await p2;
|
|
190
|
+
expect(done).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
//# sourceMappingURL=rate-limit.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.test.js","sourceRoot":"","sources":["../../src/__tests__/rate-limit.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send-file.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/send-file.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import { closeTestDb, seedTestDb, setupTestDb } from "./helpers/test-db.js";
|
|
9
|
+
vi.mock("@whiskeysockets/baileys", () => {
|
|
10
|
+
const listeners = new Map();
|
|
11
|
+
const ev = {
|
|
12
|
+
on(event, handler) {
|
|
13
|
+
const arr = listeners.get(event) || [];
|
|
14
|
+
arr.push(handler);
|
|
15
|
+
listeners.set(event, arr);
|
|
16
|
+
},
|
|
17
|
+
process(handler) {
|
|
18
|
+
const arr = listeners.get("event") || [];
|
|
19
|
+
arr.push(handler);
|
|
20
|
+
listeners.set("event", arr);
|
|
21
|
+
},
|
|
22
|
+
emit(event, payload) {
|
|
23
|
+
for (const handler of listeners.get(event) || []) {
|
|
24
|
+
void handler(payload);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
emitBatch(payload) {
|
|
28
|
+
for (const handler of listeners.get("event") || []) {
|
|
29
|
+
void handler(payload);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
removeAllListeners() {
|
|
33
|
+
listeners.clear();
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
const fakeSocket = {
|
|
37
|
+
ev,
|
|
38
|
+
sentMessages: [],
|
|
39
|
+
user: {
|
|
40
|
+
id: "15559990000@s.whatsapp.net",
|
|
41
|
+
name: "Test User",
|
|
42
|
+
lid: "15559990000@lid",
|
|
43
|
+
},
|
|
44
|
+
async sendMessage(jid, content) {
|
|
45
|
+
const streamLike = [content.image, content.video, content.audio, content.document].find((value) => value && typeof value.on === "function");
|
|
46
|
+
if (streamLike) {
|
|
47
|
+
await new Promise((resolve) => {
|
|
48
|
+
streamLike.on("open", () => {
|
|
49
|
+
streamLike.destroy();
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
streamLike.on("error", () => resolve());
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
fakeSocket.sentMessages.push({ jid, content });
|
|
56
|
+
return { key: { id: `fake-msg-${fakeSocket.sentMessages.length}` } };
|
|
57
|
+
},
|
|
58
|
+
async chatModify() {
|
|
59
|
+
return undefined;
|
|
60
|
+
},
|
|
61
|
+
async groupMetadata(jid) {
|
|
62
|
+
return { subject: `Group ${jid}` };
|
|
63
|
+
},
|
|
64
|
+
async updateMediaMessage() {
|
|
65
|
+
return undefined;
|
|
66
|
+
},
|
|
67
|
+
end() {
|
|
68
|
+
listeners.clear();
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
default: vi.fn(() => fakeSocket),
|
|
73
|
+
DisconnectReason: { loggedOut: 401, connectionReplaced: 440 },
|
|
74
|
+
fetchLatestBaileysVersion: vi.fn(async () => ({ version: [2, 3000, 0] })),
|
|
75
|
+
downloadMediaMessage: vi.fn(),
|
|
76
|
+
getContentType: vi.fn(),
|
|
77
|
+
initAuthCreds: vi.fn(() => ({})),
|
|
78
|
+
BufferJSON: {
|
|
79
|
+
replacer: (_key, value) => value,
|
|
80
|
+
reviver: (_key, value) => value,
|
|
81
|
+
},
|
|
82
|
+
proto: {
|
|
83
|
+
Message: {
|
|
84
|
+
AppStateSyncKeyData: {
|
|
85
|
+
fromObject: (value) => value,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
__fakeSocket: fakeSocket,
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
function parseToolJson(result) {
|
|
93
|
+
const text = result.content?.find((entry) => entry.type === "text")?.text || "";
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(text);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return text;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
describe("send_file security", () => {
|
|
102
|
+
const originalAllowedDir = process.env.ALLOWED_SEND_DIR;
|
|
103
|
+
const originalMaxSize = process.env.MAX_SEND_FILE_SIZE;
|
|
104
|
+
let tempDir;
|
|
105
|
+
let server;
|
|
106
|
+
let client;
|
|
107
|
+
let closeWhatsApp;
|
|
108
|
+
beforeAll(async () => {
|
|
109
|
+
await setupTestDb();
|
|
110
|
+
seedTestDb();
|
|
111
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "send-file-test-"));
|
|
112
|
+
process.env.ALLOWED_SEND_DIR = tempDir;
|
|
113
|
+
process.env.MAX_SEND_FILE_SIZE = "67108864";
|
|
114
|
+
process.env.MIN_SEND_INTERVAL_MS = "0";
|
|
115
|
+
process.env.SEND_JITTER_MS = "0";
|
|
116
|
+
const whatsapp = await import("../whatsapp.js");
|
|
117
|
+
closeWhatsApp = whatsapp.closeWhatsApp;
|
|
118
|
+
await whatsapp.initWhatsApp();
|
|
119
|
+
const baileys = await import("@whiskeysockets/baileys");
|
|
120
|
+
baileys.__fakeSocket.ev.emitBatch({
|
|
121
|
+
"connection.update": { connection: "open" },
|
|
122
|
+
});
|
|
123
|
+
server = new McpServer({ name: "whatsapp-test", version: "1.0.0" });
|
|
124
|
+
const { registerTools } = await import("../tools.js");
|
|
125
|
+
registerTools(server);
|
|
126
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
127
|
+
client = new Client({ name: "whatsapp-test-client", version: "1.0.0" });
|
|
128
|
+
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
|
|
129
|
+
});
|
|
130
|
+
afterAll(async () => {
|
|
131
|
+
await Promise.all([client?.close(), server?.close()]);
|
|
132
|
+
await closeWhatsApp?.();
|
|
133
|
+
closeTestDb();
|
|
134
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
135
|
+
if (originalAllowedDir === undefined) {
|
|
136
|
+
delete process.env.ALLOWED_SEND_DIR;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
process.env.ALLOWED_SEND_DIR = originalAllowedDir;
|
|
140
|
+
}
|
|
141
|
+
if (originalMaxSize === undefined) {
|
|
142
|
+
delete process.env.MAX_SEND_FILE_SIZE;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
process.env.MAX_SEND_FILE_SIZE = originalMaxSize;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
it("returns preview when confirmed=false and does not send", async () => {
|
|
149
|
+
const filePath = path.join(tempDir, "preview.txt");
|
|
150
|
+
fs.writeFileSync(filePath, "hello");
|
|
151
|
+
const before = (await import("@whiskeysockets/baileys")).__fakeSocket.sentMessages.length;
|
|
152
|
+
const result = (await client.callTool({
|
|
153
|
+
name: "send_file",
|
|
154
|
+
arguments: {
|
|
155
|
+
jid: "15550001111@s.whatsapp.net",
|
|
156
|
+
filePath,
|
|
157
|
+
caption: "preview",
|
|
158
|
+
confirmed: false,
|
|
159
|
+
},
|
|
160
|
+
}));
|
|
161
|
+
const payload = parseToolJson(result);
|
|
162
|
+
const after = (await import("@whiskeysockets/baileys")).__fakeSocket.sentMessages.length;
|
|
163
|
+
expect(result.isError).toBeFalsy();
|
|
164
|
+
expect(payload).toMatchObject({
|
|
165
|
+
preview: true,
|
|
166
|
+
fileName: "preview.txt",
|
|
167
|
+
fileSize: 5,
|
|
168
|
+
recipient: expect.objectContaining({
|
|
169
|
+
jid: "15550001111@s.whatsapp.net",
|
|
170
|
+
}),
|
|
171
|
+
});
|
|
172
|
+
expect(after).toBe(before);
|
|
173
|
+
});
|
|
174
|
+
it("rejects traversal path with Path not allowed", async () => {
|
|
175
|
+
const result = (await client.callTool({
|
|
176
|
+
name: "send_file",
|
|
177
|
+
arguments: {
|
|
178
|
+
jid: "15550001111@s.whatsapp.net",
|
|
179
|
+
filePath: "../../etc/passwd",
|
|
180
|
+
confirmed: true,
|
|
181
|
+
},
|
|
182
|
+
}));
|
|
183
|
+
const text = parseToolJson(result) || "";
|
|
184
|
+
expect(result.isError).toBe(true);
|
|
185
|
+
expect(text).toContain("Path not allowed");
|
|
186
|
+
});
|
|
187
|
+
it("rejects absolute path outside allowlist", async () => {
|
|
188
|
+
const result = (await client.callTool({
|
|
189
|
+
name: "send_file",
|
|
190
|
+
arguments: {
|
|
191
|
+
jid: "15550001111@s.whatsapp.net",
|
|
192
|
+
filePath: "/home/user/.ssh/id_rsa",
|
|
193
|
+
confirmed: true,
|
|
194
|
+
},
|
|
195
|
+
}));
|
|
196
|
+
const text = parseToolJson(result) || "";
|
|
197
|
+
expect(result.isError).toBe(true);
|
|
198
|
+
expect(text).toContain("Path not allowed");
|
|
199
|
+
});
|
|
200
|
+
it("rejects file larger than MAX_SEND_FILE_SIZE", async () => {
|
|
201
|
+
process.env.MAX_SEND_FILE_SIZE = "4";
|
|
202
|
+
const filePath = path.join(tempDir, "too-large.txt");
|
|
203
|
+
fs.writeFileSync(filePath, "12345");
|
|
204
|
+
const result = (await client.callTool({
|
|
205
|
+
name: "send_file",
|
|
206
|
+
arguments: {
|
|
207
|
+
jid: "15550001111@s.whatsapp.net",
|
|
208
|
+
filePath,
|
|
209
|
+
confirmed: true,
|
|
210
|
+
},
|
|
211
|
+
}));
|
|
212
|
+
const text = parseToolJson(result) || "";
|
|
213
|
+
expect(result.isError).toBe(true);
|
|
214
|
+
expect(text).toContain("File too large");
|
|
215
|
+
process.env.MAX_SEND_FILE_SIZE = "67108864";
|
|
216
|
+
});
|
|
217
|
+
it("sends successfully when confirmed=true with allowed path under size limit", async () => {
|
|
218
|
+
const filePath = path.join(tempDir, "ok.txt");
|
|
219
|
+
fs.writeFileSync(filePath, "ok");
|
|
220
|
+
const result = (await client.callTool({
|
|
221
|
+
name: "send_file",
|
|
222
|
+
arguments: {
|
|
223
|
+
jid: "15550001111@s.whatsapp.net",
|
|
224
|
+
filePath,
|
|
225
|
+
caption: "ok",
|
|
226
|
+
confirmed: true,
|
|
227
|
+
},
|
|
228
|
+
}));
|
|
229
|
+
const payload = parseToolJson(result);
|
|
230
|
+
expect(result.isError).toBeFalsy();
|
|
231
|
+
expect(payload).toMatchObject({
|
|
232
|
+
success: true,
|
|
233
|
+
to: "15550001111@s.whatsapp.net",
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
//# sourceMappingURL=send-file.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send-file.test.js","sourceRoot":"","sources":["../../src/__tests__/send-file.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/smoke.test.ts"],"names":[],"mappings":""}
|