@botmem/apple-bridge 0.35.25

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/dist/cli.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Botmem Apple Bridge CLI.
4
+ *
5
+ * Usage:
6
+ * npx @botmem/apple-bridge --token=<token> [--server=wss://botmem.xyz/imsg-tunnel]
7
+ */
8
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Botmem Apple Bridge CLI.
4
+ *
5
+ * Usage:
6
+ * npx @botmem/apple-bridge --token=<token> [--server=wss://botmem.xyz/imsg-tunnel]
7
+ */
8
+ import { Command } from 'commander';
9
+ import { runPreflight, DEFAULT_DB_PATH } from './preflight.js';
10
+ import { ImsgDatabase } from './db.js';
11
+ import { RpcHandler } from './rpc-handler.js';
12
+ import { TunnelClient } from './tunnel.js';
13
+ const DEFAULT_SERVER = 'wss://botmem.xyz/imsg-tunnel';
14
+ const program = new Command();
15
+ function timestamp() {
16
+ return new Date().toISOString();
17
+ }
18
+ function log(message = '') {
19
+ console.log(message ? ` ${timestamp()} ${message}` : '');
20
+ }
21
+ function error(message = '') {
22
+ console.error(message ? ` ${timestamp()} ${message}` : '');
23
+ }
24
+ program
25
+ .name('apple-bridge')
26
+ .description('Botmem Apple Bridge — syncs local Apple data securely')
27
+ .requiredOption('--token <token>', 'Bridge token from your Botmem dashboard')
28
+ .option('--server <url>', 'Botmem server URL', DEFAULT_SERVER)
29
+ .option('--db <path>', 'Path to chat.db', DEFAULT_DB_PATH)
30
+ .action(async (opts) => {
31
+ console.log('\n BOTMEM APPLE BRIDGE\n');
32
+ // ── Preflight ─────────────────────────────────────────────────────────
33
+ log('Checking prerequisites...');
34
+ const preflight = runPreflight(opts.db);
35
+ if (!preflight.ok) {
36
+ error('PREFLIGHT FAILED:');
37
+ for (const err of preflight.errors) {
38
+ for (const line of err.split('\n')) {
39
+ error(line);
40
+ }
41
+ }
42
+ process.exit(1);
43
+ }
44
+ log(`iMessage database: ${preflight.dbPath}`);
45
+ log(`Chats found: ${preflight.chatCount ?? 'unknown'}`);
46
+ // ── Open database ─────────────────────────────────────────────────────
47
+ const db = new ImsgDatabase(opts.db);
48
+ const rpcHandler = new RpcHandler(db);
49
+ // ── Connect tunnel ────────────────────────────────────────────────────
50
+ console.log('');
51
+ log(`Connecting to ${opts.server}...`);
52
+ const tunnel = new TunnelClient({
53
+ serverUrl: opts.server,
54
+ token: opts.token,
55
+ rpcHandler,
56
+ });
57
+ tunnel.on('status', (status) => {
58
+ const icon = {
59
+ connecting: '...',
60
+ authenticating: '...',
61
+ connected: 'OK',
62
+ disconnected: '--',
63
+ error: '!!',
64
+ }[status] || '??';
65
+ log(`[${icon}] ${status}`);
66
+ });
67
+ tunnel.on('log', (msg) => {
68
+ log(msg);
69
+ });
70
+ tunnel.on('fatal', (msg) => {
71
+ console.error('');
72
+ error(`FATAL: ${msg}`);
73
+ console.error('');
74
+ db.close();
75
+ process.exit(1);
76
+ });
77
+ tunnel.connect();
78
+ // ── Graceful shutdown ─────────────────────────────────────────────────
79
+ const shutdown = () => {
80
+ console.log('');
81
+ log('Shutting down...');
82
+ tunnel.destroy();
83
+ db.close();
84
+ process.exit(0);
85
+ };
86
+ process.on('SIGINT', shutdown);
87
+ process.on('SIGTERM', shutdown);
88
+ });
89
+ program.parse();
90
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export interface AppleContact {
2
+ id: string;
3
+ displayName?: string;
4
+ givenName?: string;
5
+ familyName?: string;
6
+ middleName?: string;
7
+ nickname?: string;
8
+ organization?: string;
9
+ jobTitle?: string;
10
+ birthday?: string;
11
+ emails: string[];
12
+ phones: string[];
13
+ imageAvailable?: boolean;
14
+ }
15
+ export declare function listAppleContacts(): Promise<AppleContact[]>;
@@ -0,0 +1,136 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { mkdir, rm, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ const SWIFT_HELPER = String.raw `
6
+ import Contacts
7
+ import Foundation
8
+
9
+ struct ContactPayload: Codable {
10
+ let id: String
11
+ let displayName: String?
12
+ let givenName: String?
13
+ let familyName: String?
14
+ let middleName: String?
15
+ let nickname: String?
16
+ let organization: String?
17
+ let jobTitle: String?
18
+ let birthday: String?
19
+ let emails: [String]
20
+ let phones: [String]
21
+ let imageAvailable: Bool
22
+ }
23
+
24
+ let store = CNContactStore()
25
+ let keys: [CNKeyDescriptor] = [
26
+ CNContactIdentifierKey as CNKeyDescriptor,
27
+ CNContactGivenNameKey as CNKeyDescriptor,
28
+ CNContactFamilyNameKey as CNKeyDescriptor,
29
+ CNContactMiddleNameKey as CNKeyDescriptor,
30
+ CNContactNicknameKey as CNKeyDescriptor,
31
+ CNContactOrganizationNameKey as CNKeyDescriptor,
32
+ CNContactJobTitleKey as CNKeyDescriptor,
33
+ CNContactBirthdayKey as CNKeyDescriptor,
34
+ CNContactEmailAddressesKey as CNKeyDescriptor,
35
+ CNContactPhoneNumbersKey as CNKeyDescriptor,
36
+ CNContactImageDataAvailableKey as CNKeyDescriptor
37
+ ]
38
+
39
+ let semaphore = DispatchSemaphore(value: 0)
40
+ var granted = false
41
+ var authError: Error?
42
+ store.requestAccess(for: .contacts) { ok, error in
43
+ granted = ok
44
+ authError = error
45
+ semaphore.signal()
46
+ }
47
+ semaphore.wait()
48
+
49
+ if !granted {
50
+ let message = authError?.localizedDescription ?? "Contacts permission was denied"
51
+ FileHandle.standardError.write(Data(message.utf8))
52
+ exit(2)
53
+ }
54
+
55
+ func clean(_ value: String) -> String? {
56
+ let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
57
+ return trimmed.isEmpty ? nil : trimmed
58
+ }
59
+
60
+ func birthdayString(_ date: DateComponents?) -> String? {
61
+ guard let date else { return nil }
62
+ var parts: [String] = []
63
+ if let year = date.year { parts.append(String(format: "%04d", year)) }
64
+ if let month = date.month { parts.append(String(format: "%02d", month)) }
65
+ if let day = date.day { parts.append(String(format: "%02d", day)) }
66
+ return parts.isEmpty ? nil : parts.joined(separator: "-")
67
+ }
68
+
69
+ let request = CNContactFetchRequest(keysToFetch: keys)
70
+ var contacts: [ContactPayload] = []
71
+
72
+ try store.enumerateContacts(with: request) { contact, _ in
73
+ let names = [contact.givenName, contact.middleName, contact.familyName]
74
+ .compactMap(clean)
75
+ .joined(separator: " ")
76
+ let displayName = clean(names) ?? clean(contact.nickname) ?? clean(contact.organizationName)
77
+ contacts.append(ContactPayload(
78
+ id: contact.identifier,
79
+ displayName: displayName,
80
+ givenName: clean(contact.givenName),
81
+ familyName: clean(contact.familyName),
82
+ middleName: clean(contact.middleName),
83
+ nickname: clean(contact.nickname),
84
+ organization: clean(contact.organizationName),
85
+ jobTitle: clean(contact.jobTitle),
86
+ birthday: birthdayString(contact.birthday),
87
+ emails: contact.emailAddresses.compactMap { clean(String($0.value)) },
88
+ phones: contact.phoneNumbers.compactMap { clean($0.value.stringValue) },
89
+ imageAvailable: contact.imageDataAvailable
90
+ ))
91
+ }
92
+
93
+ let data = try JSONEncoder().encode(contacts)
94
+ FileHandle.standardOutput.write(data)
95
+ `;
96
+ export async function listAppleContacts() {
97
+ const dir = await mkdir(join(tmpdir(), `botmem-apple-contacts-${process.pid}`), {
98
+ recursive: true,
99
+ }).then(() => join(tmpdir(), `botmem-apple-contacts-${process.pid}`));
100
+ const helperPath = join(dir, 'contacts-helper.swift');
101
+ await writeFile(helperPath, SWIFT_HELPER, 'utf8');
102
+ try {
103
+ const stdout = await runSwift(helperPath);
104
+ const parsed = JSON.parse(stdout);
105
+ return Array.isArray(parsed) ? parsed : [];
106
+ }
107
+ finally {
108
+ await rm(dir, { recursive: true, force: true });
109
+ }
110
+ }
111
+ function runSwift(helperPath) {
112
+ return new Promise((resolve, reject) => {
113
+ const child = spawn('xcrun', ['swift', helperPath], {
114
+ stdio: ['ignore', 'pipe', 'pipe'],
115
+ });
116
+ let stdout = '';
117
+ let stderr = '';
118
+ child.stdout.setEncoding('utf8');
119
+ child.stderr.setEncoding('utf8');
120
+ child.stdout.on('data', (chunk) => {
121
+ stdout += chunk;
122
+ });
123
+ child.stderr.on('data', (chunk) => {
124
+ stderr += chunk;
125
+ });
126
+ child.on('error', reject);
127
+ child.on('close', (code) => {
128
+ if (code === 0) {
129
+ resolve(stdout);
130
+ return;
131
+ }
132
+ reject(new Error(stderr.trim() || `Apple Contacts helper exited with code ${code}`));
133
+ });
134
+ });
135
+ }
136
+ //# sourceMappingURL=contacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contacts.js","sourceRoot":"","sources":["../src/contacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAiBjC,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0F9B,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,yBAAyB,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE;QAC9E,SAAS,EAAE,IAAI;KAChB,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,yBAAyB,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACtE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAC;IACtD,MAAM,SAAS,CAAC,UAAU,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAY,CAAC;QAC7C,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,MAAyB,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,UAAkB;IAClC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE;YAClD,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QACH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACjC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACjC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YAChC,MAAM,IAAI,KAAK,CAAC;QAClB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YAChC,MAAM,IAAI,KAAK,CAAC;QAClB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,OAAO,CAAC,MAAM,CAAC,CAAC;gBAChB,OAAO;YACT,CAAC;YACD,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,0CAA0C,IAAI,EAAE,CAAC,CAAC,CAAC;QACvF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * End-to-end encryption for the Apple bridge tunnel.
3
+ *
4
+ * Protocol:
5
+ * 1. Both sides generate ephemeral X25519 key pairs
6
+ * 2. Exchange public keys during auth handshake
7
+ * 3. Derive shared secret via ECDH
8
+ * 4. Derive AES-256-GCM key via HKDF-SHA256
9
+ * 5. Every frame encrypted with unique random IV
10
+ *
11
+ * Wire format (binary): [12-byte IV][ciphertext][16-byte auth tag]
12
+ */
13
+ import { type KeyObject } from 'node:crypto';
14
+ export interface KeyPair {
15
+ publicKey: KeyObject;
16
+ privateKey: KeyObject;
17
+ }
18
+ /** Generate an ephemeral X25519 key pair for ECDH. */
19
+ export declare function generateKeyPair(): KeyPair;
20
+ /** Export public key to raw 32-byte Buffer for wire transfer. */
21
+ export declare function exportPublicKey(key: KeyObject): Buffer;
22
+ /** Import raw 32-byte public key from wire. */
23
+ export declare function importPublicKey(raw: Buffer): KeyObject;
24
+ /** Derive a shared AES-256 key from local private + remote public via ECDH + HKDF. */
25
+ export declare function deriveSessionKey(localPrivate: KeyObject, remotePublic: KeyObject): Buffer;
26
+ /** Encrypt plaintext with AES-256-GCM. Returns [IV (12) | ciphertext | tag (16)]. */
27
+ export declare function encrypt(key: Buffer, plaintext: string): Buffer;
28
+ /** Decrypt AES-256-GCM payload. Input: [IV (12) | ciphertext | tag (16)]. */
29
+ export declare function decrypt(key: Buffer, payload: Buffer): string;
30
+ /** Encrypt a JSON-serializable object. */
31
+ export declare function encryptJson(key: Buffer, data: unknown): Buffer;
32
+ /** Decrypt a payload and parse as JSON. */
33
+ export declare function decryptJson<T = unknown>(key: Buffer, payload: Buffer): T;
package/dist/crypto.js ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * End-to-end encryption for the Apple bridge tunnel.
3
+ *
4
+ * Protocol:
5
+ * 1. Both sides generate ephemeral X25519 key pairs
6
+ * 2. Exchange public keys during auth handshake
7
+ * 3. Derive shared secret via ECDH
8
+ * 4. Derive AES-256-GCM key via HKDF-SHA256
9
+ * 5. Every frame encrypted with unique random IV
10
+ *
11
+ * Wire format (binary): [12-byte IV][ciphertext][16-byte auth tag]
12
+ */
13
+ import { generateKeyPairSync, diffieHellman, hkdfSync, randomBytes, createCipheriv, createDecipheriv, createPublicKey, } from 'node:crypto';
14
+ /** Generate an ephemeral X25519 key pair for ECDH. */
15
+ export function generateKeyPair() {
16
+ const { publicKey, privateKey } = generateKeyPairSync('x25519');
17
+ return { publicKey, privateKey };
18
+ }
19
+ /** Export public key to raw 32-byte Buffer for wire transfer. */
20
+ export function exportPublicKey(key) {
21
+ // DER format for X25519 public key: 12-byte header + 32-byte key
22
+ const der = key.export({ type: 'spki', format: 'der' });
23
+ return Buffer.from(der.subarray(12));
24
+ }
25
+ /** Import raw 32-byte public key from wire. */
26
+ export function importPublicKey(raw) {
27
+ // Wrap raw 32 bytes in X25519 SPKI DER header
28
+ const header = Buffer.from('302a300506032b656e032100', 'hex');
29
+ const der = Buffer.concat([header, raw]);
30
+ return createPublicKey({ key: der, format: 'der', type: 'spki' });
31
+ }
32
+ /** Derive a shared AES-256 key from local private + remote public via ECDH + HKDF. */
33
+ export function deriveSessionKey(localPrivate, remotePublic) {
34
+ const sharedSecret = diffieHellman({
35
+ privateKey: localPrivate,
36
+ publicKey: remotePublic,
37
+ });
38
+ const salt = Buffer.from('botmem-imsg-tunnel-v1', 'utf-8');
39
+ const info = Buffer.from('aes-256-gcm-session-key', 'utf-8');
40
+ const derived = hkdfSync('sha256', sharedSecret, salt, info, 32);
41
+ return Buffer.from(derived);
42
+ }
43
+ // ── Symmetric Encryption ────────────────────────────────────────────────────
44
+ const IV_LENGTH = 12;
45
+ const TAG_LENGTH = 16;
46
+ /** Encrypt plaintext with AES-256-GCM. Returns [IV (12) | ciphertext | tag (16)]. */
47
+ export function encrypt(key, plaintext) {
48
+ const iv = randomBytes(IV_LENGTH);
49
+ const cipher = createCipheriv('aes-256-gcm', key, iv);
50
+ const encrypted = Buffer.concat([cipher.update(plaintext, 'utf-8'), cipher.final()]);
51
+ const tag = cipher.getAuthTag();
52
+ return Buffer.concat([iv, encrypted, tag]);
53
+ }
54
+ /** Decrypt AES-256-GCM payload. Input: [IV (12) | ciphertext | tag (16)]. */
55
+ export function decrypt(key, payload) {
56
+ if (payload.length < IV_LENGTH + TAG_LENGTH) {
57
+ throw new Error('Encrypted payload too short');
58
+ }
59
+ const iv = payload.subarray(0, IV_LENGTH);
60
+ const tag = payload.subarray(payload.length - TAG_LENGTH);
61
+ const ciphertext = payload.subarray(IV_LENGTH, payload.length - TAG_LENGTH);
62
+ const decipher = createDecipheriv('aes-256-gcm', key, iv);
63
+ decipher.setAuthTag(tag);
64
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
65
+ return decrypted.toString('utf-8');
66
+ }
67
+ // ── Convenience ─────────────────────────────────────────────────────────────
68
+ /** Encrypt a JSON-serializable object. */
69
+ export function encryptJson(key, data) {
70
+ return encrypt(key, JSON.stringify(data));
71
+ }
72
+ /** Decrypt a payload and parse as JSON. */
73
+ export function decryptJson(key, payload) {
74
+ return JSON.parse(decrypt(key, payload));
75
+ }
76
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
package/dist/db.d.ts ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * SQLite query layer for ~/Library/Messages/chat.db.
3
+ *
4
+ * Reads the iMessage database in read-only mode and returns data
5
+ * matching the JSON-RPC types expected by the Botmem iMessage connector.
6
+ *
7
+ * Core Data timestamp conversion:
8
+ * macOS stores dates as nanoseconds since 2001-01-01T00:00:00Z.
9
+ * Unix epoch offset: 978307200 seconds.
10
+ * Formula: new Date((date / 1e9 + 978307200) * 1000)
11
+ */
12
+ export interface Chat {
13
+ id: number;
14
+ name: string;
15
+ identifier: string;
16
+ guid?: string;
17
+ service: string;
18
+ last_message_at: string;
19
+ participants?: string[];
20
+ is_group?: boolean;
21
+ }
22
+ export interface Attachment {
23
+ filename?: string;
24
+ mime_type?: string;
25
+ transfer_name?: string;
26
+ }
27
+ export interface Reaction {
28
+ sender?: string;
29
+ type?: string;
30
+ }
31
+ export interface Message {
32
+ id: number;
33
+ chat_id: number;
34
+ guid: string;
35
+ sender: string;
36
+ is_from_me: boolean;
37
+ text: string;
38
+ created_at: string;
39
+ attachments: Attachment[];
40
+ reactions: Reaction[];
41
+ chat_identifier: string;
42
+ chat_name: string;
43
+ participants: string[];
44
+ is_group: boolean;
45
+ reply_to_guid?: string;
46
+ }
47
+ export declare class ImsgDatabase {
48
+ private db;
49
+ constructor(dbPath: string);
50
+ close(): void;
51
+ /** List chats sorted by most recent message. */
52
+ chatsList(limit?: number): Chat[];
53
+ /** Get message history for a chat with optional time-based pagination. */
54
+ messagesHistory(chatId: number, opts?: {
55
+ limit?: number;
56
+ start?: string;
57
+ end?: string;
58
+ }): Message[];
59
+ private getChatParticipants;
60
+ private getChatMeta;
61
+ private getAttachmentsForMessages;
62
+ }
package/dist/db.js ADDED
@@ -0,0 +1,270 @@
1
+ /**
2
+ * SQLite query layer for ~/Library/Messages/chat.db.
3
+ *
4
+ * Reads the iMessage database in read-only mode and returns data
5
+ * matching the JSON-RPC types expected by the Botmem iMessage connector.
6
+ *
7
+ * Core Data timestamp conversion:
8
+ * macOS stores dates as nanoseconds since 2001-01-01T00:00:00Z.
9
+ * Unix epoch offset: 978307200 seconds.
10
+ * Formula: new Date((date / 1e9 + 978307200) * 1000)
11
+ */
12
+ import { createRequire } from 'node:module';
13
+ const require = createRequire(import.meta.url);
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ const Database = require('better-sqlite3');
16
+ // ── Constants ───────────────────────────────────────────────────────────────
17
+ /** Seconds between 2001-01-01 and 1970-01-01 (Unix epoch). */
18
+ const CORE_DATA_EPOCH_OFFSET = 978307200;
19
+ /** Convert Core Data nanosecond timestamp to ISO 8601 string. */
20
+ function coreDataToISO(nanos) {
21
+ if (!nanos || nanos === 0)
22
+ return new Date(0).toISOString();
23
+ const unixSeconds = nanos / 1_000_000_000 + CORE_DATA_EPOCH_OFFSET;
24
+ return new Date(unixSeconds * 1000).toISOString();
25
+ }
26
+ // ── Database ────────────────────────────────────────────────────────────────
27
+ export class ImsgDatabase {
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ db;
30
+ constructor(dbPath) {
31
+ this.db = new Database(dbPath, { readonly: true, fileMustExist: true });
32
+ // WAL mode for concurrent reads while Messages.app writes
33
+ this.db.pragma('journal_mode = WAL');
34
+ }
35
+ close() {
36
+ this.db.close();
37
+ }
38
+ /** List chats sorted by most recent message. */
39
+ chatsList(limit) {
40
+ const sql = `
41
+ SELECT
42
+ c.ROWID as id,
43
+ COALESCE(c.display_name, '') as name,
44
+ c.guid as identifier,
45
+ c.guid,
46
+ COALESCE(c.service_name, 'iMessage') as service,
47
+ MAX(m.date) as last_message_date
48
+ FROM chat c
49
+ LEFT JOIN chat_message_join cmj ON cmj.chat_id = c.ROWID
50
+ LEFT JOIN message m ON m.ROWID = cmj.message_id
51
+ GROUP BY c.ROWID
52
+ ORDER BY last_message_date DESC
53
+ ${limit ? 'LIMIT ?' : ''}
54
+ `;
55
+ const rows = limit ? this.db.prepare(sql).all(limit) : this.db.prepare(sql).all();
56
+ return rows.map((row) => {
57
+ const chatId = row.id;
58
+ const participants = this.getChatParticipants(chatId);
59
+ const isGroup = participants.length > 1;
60
+ return {
61
+ id: chatId,
62
+ name: row.name || (isGroup ? 'Group Chat' : participants[0] || 'Unknown'),
63
+ identifier: row.identifier,
64
+ guid: row.guid,
65
+ service: row.service,
66
+ last_message_at: coreDataToISO(row.last_message_date),
67
+ participants,
68
+ is_group: isGroup,
69
+ };
70
+ });
71
+ }
72
+ /** Get message history for a chat with optional time-based pagination. */
73
+ messagesHistory(chatId, opts) {
74
+ // Get chat metadata once
75
+ const chatMeta = this.getChatMeta(chatId);
76
+ let sql = `
77
+ SELECT
78
+ m.ROWID as id,
79
+ m.guid,
80
+ m.text,
81
+ m.attributedBody,
82
+ m.date,
83
+ m.is_from_me,
84
+ m.cache_roomnames,
85
+ m.associated_message_guid,
86
+ m.associated_message_type,
87
+ h.id as handle_id
88
+ FROM message m
89
+ JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
90
+ LEFT JOIN handle h ON h.ROWID = m.handle_id
91
+ WHERE cmj.chat_id = ?
92
+ `;
93
+ const params = [chatId];
94
+ if (opts?.start) {
95
+ const startNanos = isoToCoreData(opts.start);
96
+ sql += ' AND m.date >= ?';
97
+ params.push(startNanos);
98
+ }
99
+ if (opts?.end) {
100
+ const endNanos = isoToCoreData(opts.end);
101
+ sql += ' AND m.date <= ?';
102
+ params.push(endNanos);
103
+ }
104
+ sql += ' ORDER BY m.date ASC';
105
+ if (opts?.limit) {
106
+ sql += ' LIMIT ?';
107
+ params.push(opts.limit);
108
+ }
109
+ const rows = this.db.prepare(sql).all(...params);
110
+ const participants = this.getChatParticipants(chatId);
111
+ const messageIds = rows.map((row) => row.id);
112
+ const attachmentsByMessageId = this.getAttachmentsForMessages(messageIds);
113
+ return rows.map((row) => {
114
+ const msgId = row.id;
115
+ const attachments = attachmentsByMessageId.get(msgId) || [];
116
+ const text = row.text ||
117
+ extractAttributedBodyText(row.attributedBody) ||
118
+ '';
119
+ return {
120
+ id: msgId,
121
+ chat_id: chatId,
122
+ guid: row.guid || `imsg-local-${msgId}`,
123
+ sender: row.handle_id || '',
124
+ is_from_me: row.is_from_me === 1,
125
+ text,
126
+ created_at: coreDataToISO(row.date),
127
+ attachments,
128
+ // Reactions are not used by the Botmem ingestion pipeline. Fetching them
129
+ // per message requires an unindexed suffix LIKE scan over the message
130
+ // table, which makes large chats time out.
131
+ reactions: [],
132
+ chat_identifier: chatMeta.identifier,
133
+ chat_name: chatMeta.name,
134
+ participants,
135
+ is_group: participants.length > 1,
136
+ reply_to_guid: row.associated_message_guid || undefined,
137
+ };
138
+ });
139
+ }
140
+ // ── Private helpers ─────────────────────────────────────────────────────
141
+ getChatParticipants(chatId) {
142
+ const sql = `
143
+ SELECT h.id
144
+ FROM chat_handle_join chj
145
+ JOIN handle h ON h.ROWID = chj.handle_id
146
+ WHERE chj.chat_id = ?
147
+ `;
148
+ const rows = this.db.prepare(sql).all(chatId);
149
+ return rows.map((r) => r.id);
150
+ }
151
+ getChatMeta(chatId) {
152
+ const sql = `
153
+ SELECT COALESCE(display_name, '') as name, guid as identifier
154
+ FROM chat WHERE ROWID = ?
155
+ `;
156
+ const row = this.db.prepare(sql).get(chatId);
157
+ return row || { name: 'Unknown', identifier: '' };
158
+ }
159
+ getAttachmentsForMessages(messageIds) {
160
+ const byMessageId = new Map();
161
+ if (messageIds.length === 0)
162
+ return byMessageId;
163
+ const chunkSize = 900;
164
+ for (let i = 0; i < messageIds.length; i += chunkSize) {
165
+ const chunk = messageIds.slice(i, i + chunkSize);
166
+ const placeholders = chunk.map(() => '?').join(',');
167
+ const sql = `
168
+ SELECT
169
+ maj.message_id,
170
+ a.filename,
171
+ a.mime_type,
172
+ a.transfer_name
173
+ FROM message_attachment_join maj
174
+ JOIN attachment a ON a.ROWID = maj.attachment_id
175
+ WHERE maj.message_id IN (${placeholders})
176
+ `;
177
+ const rows = this.db.prepare(sql).all(...chunk);
178
+ for (const row of rows) {
179
+ const messageId = row.message_id;
180
+ const attachments = byMessageId.get(messageId) || [];
181
+ attachments.push({
182
+ filename: row.filename || undefined,
183
+ mime_type: row.mime_type || undefined,
184
+ transfer_name: row.transfer_name || undefined,
185
+ });
186
+ byMessageId.set(messageId, attachments);
187
+ }
188
+ }
189
+ return byMessageId;
190
+ }
191
+ }
192
+ // ── Helpers ─────────────────────────────────────────────────────────────────
193
+ function isoToCoreData(iso) {
194
+ const unixMs = new Date(iso).getTime();
195
+ const unixSeconds = unixMs / 1000;
196
+ return (unixSeconds - CORE_DATA_EPOCH_OFFSET) * 1_000_000_000;
197
+ }
198
+ function extractAttributedBodyText(body) {
199
+ if (!body?.length)
200
+ return '';
201
+ const nsString = Buffer.from('NSString', 'utf8');
202
+ const marker = Buffer.from([0x95, 0x84, 0x01, 0x2b]);
203
+ let searchFrom = 0;
204
+ while (searchFrom < body.length) {
205
+ const stringClassAt = body.indexOf(nsString, searchFrom);
206
+ if (stringClassAt === -1)
207
+ return '';
208
+ const markerAt = body.indexOf(marker, stringClassAt + nsString.length);
209
+ if (markerAt === -1)
210
+ return '';
211
+ const lengthAt = markerAt + marker.length;
212
+ const parsed = readArchivedStringLength(body, lengthAt);
213
+ if (!parsed) {
214
+ searchFrom = stringClassAt + nsString.length;
215
+ continue;
216
+ }
217
+ const { length, offset } = parsed;
218
+ const start = lengthAt + offset;
219
+ const end = start + length;
220
+ if (length <= 0 || end > body.length) {
221
+ searchFrom = stringClassAt + nsString.length;
222
+ continue;
223
+ }
224
+ const text = body.subarray(start, end).toString('utf8').trim();
225
+ if (isLikelyMessageText(text))
226
+ return text;
227
+ searchFrom = stringClassAt + nsString.length;
228
+ }
229
+ return '';
230
+ }
231
+ function readArchivedStringLength(body, offset) {
232
+ const first = body[offset];
233
+ if (first === undefined)
234
+ return null;
235
+ if (first < 0x80)
236
+ return { length: first, offset: 1 };
237
+ const byteCount = first & 0x7f;
238
+ if (byteCount <= 0 || byteCount > 4 || offset + byteCount >= body.length)
239
+ return null;
240
+ let length = 0;
241
+ for (let i = 1; i <= byteCount; i++) {
242
+ length = (length << 8) + body[offset + i];
243
+ }
244
+ return { length, offset: 1 + byteCount };
245
+ }
246
+ function isLikelyMessageText(text) {
247
+ if (!text)
248
+ return false;
249
+ if (text.includes('\u0000'))
250
+ return false;
251
+ const blocked = new Set([
252
+ 'NSString',
253
+ 'NSMutableString',
254
+ 'NSAttributedString',
255
+ 'NSMutableAttributedString',
256
+ 'NSObject',
257
+ 'NSDictionary',
258
+ 'NSNumber',
259
+ 'NSValue',
260
+ 'NSData',
261
+ 'NSMutableData',
262
+ 'NSKeyedArchiver',
263
+ ]);
264
+ if (blocked.has(text))
265
+ return false;
266
+ if (text.startsWith('__kIM'))
267
+ return false;
268
+ return /[\p{L}\p{N}]/u.test(text);
269
+ }
270
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { ImsgDatabase } from './db.js';
2
+ export type { Chat, Message, Attachment, Reaction } from './db.js';
3
+ export { RpcHandler } from './rpc-handler.js';
4
+ export { TunnelClient } from './tunnel.js';
5
+ export type { TunnelOptions, TunnelStatus } from './tunnel.js';
6
+ export { generateKeyPair, exportPublicKey, importPublicKey, deriveSessionKey, encrypt, decrypt, encryptJson, decryptJson, } from './crypto.js';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // Public API for programmatic use
2
+ export { ImsgDatabase } from './db.js';
3
+ export { RpcHandler } from './rpc-handler.js';
4
+ export { TunnelClient } from './tunnel.js';
5
+ export { generateKeyPair, exportPublicKey, importPublicKey, deriveSessionKey, encrypt, decrypt, encryptJson, decryptJson, } from './crypto.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,OAAO,EACP,OAAO,EACP,WAAW,EACX,WAAW,GACZ,MAAM,aAAa,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Pre-flight checks for the Apple bridge.
3
+ * Verifies: macOS, chat.db readable, SQLite works.
4
+ */
5
+ export declare const DEFAULT_DB_PATH: string;
6
+ export interface PreflightResult {
7
+ ok: boolean;
8
+ dbPath: string;
9
+ chatCount?: number;
10
+ errors: string[];
11
+ }
12
+ export declare function runPreflight(dbPath?: string): PreflightResult;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Pre-flight checks for the Apple bridge.
3
+ * Verifies: macOS, chat.db readable, SQLite works.
4
+ */
5
+ import { accessSync, constants } from 'node:fs';
6
+ import { homedir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ import { createRequire } from 'node:module';
9
+ const require = createRequire(import.meta.url);
10
+ export const DEFAULT_DB_PATH = join(homedir(), 'Library/Messages/chat.db');
11
+ export function runPreflight(dbPath = DEFAULT_DB_PATH) {
12
+ const errors = [];
13
+ // 1. macOS check
14
+ if (process.platform !== 'darwin') {
15
+ errors.push(`This tool only runs on macOS (detected: ${process.platform}).` +
16
+ '\niMessage data is only available on Apple devices.');
17
+ return { ok: false, dbPath, errors };
18
+ }
19
+ // 2. File exists and readable
20
+ try {
21
+ accessSync(dbPath, constants.R_OK);
22
+ }
23
+ catch {
24
+ errors.push(`Cannot read ${dbPath}` +
25
+ '\n\nTo fix this, grant Full Disk Access to your terminal:' +
26
+ '\n 1. Open System Settings → Privacy & Security → Full Disk Access' +
27
+ '\n 2. Click the + button and add your terminal app (Terminal, iTerm2, etc.)' +
28
+ '\n 3. Restart your terminal and try again');
29
+ return { ok: false, dbPath, errors };
30
+ }
31
+ // 3. SQLite can open and query the DB
32
+ try {
33
+ // Dynamic import to avoid requiring better-sqlite3 at module level
34
+ const Database = require('better-sqlite3');
35
+ const db = new Database(dbPath, { readonly: true, fileMustExist: true });
36
+ try {
37
+ const row = db.prepare('SELECT count(*) as cnt FROM chat').get();
38
+ db.close();
39
+ return { ok: true, dbPath, chatCount: row.cnt, errors: [] };
40
+ }
41
+ finally {
42
+ try {
43
+ db.close();
44
+ }
45
+ catch {
46
+ /* already closed */
47
+ }
48
+ }
49
+ }
50
+ catch (err) {
51
+ const msg = err instanceof Error ? err.message : String(err);
52
+ if (msg.includes('SQLITE_CANTOPEN') || msg.includes('unable to open')) {
53
+ errors.push(`Cannot open ${dbPath} as SQLite database.` +
54
+ '\nThe file may be locked or corrupted. Try closing Messages.app and retrying.');
55
+ }
56
+ else {
57
+ errors.push(`SQLite error: ${msg}`);
58
+ }
59
+ return { ok: false, dbPath, errors };
60
+ }
61
+ }
62
+ //# sourceMappingURL=preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,0BAA0B,CAAC,CAAC;AAS3E,MAAM,UAAU,YAAY,CAAC,SAAiB,eAAe;IAC3D,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,iBAAiB;IACjB,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CACT,2CAA2C,OAAO,CAAC,QAAQ,IAAI;YAC7D,qDAAqD,CACxD,CAAC;QACF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACvC,CAAC;IAED,8BAA8B;IAC9B,IAAI,CAAC;QACH,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,IAAI,CACT,eAAe,MAAM,EAAE;YACrB,2DAA2D;YAC3D,qEAAqE;YACrE,8EAA8E;YAC9E,4CAA4C,CAC/C,CAAC;QACF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACvC,CAAC;IAED,sCAAsC;IACtC,IAAI,CAAC;QACH,mEAAmE;QAEnE,MAAM,QAAQ,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC3C,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACzE,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC,GAAG,EAE7D,CAAC;YACF,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QAC9D,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC;gBACH,EAAE,CAAC,KAAK,EAAE,CAAC;YACb,CAAC;YAAC,MAAM,CAAC;gBACP,oBAAoB;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,IAAI,GAAG,CAAC,QAAQ,CAAC,iBAAiB,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACtE,MAAM,CAAC,IAAI,CACT,eAAe,MAAM,sBAAsB;gBACzC,+EAA+E,CAClF,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACvC,CAAC;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * JSON-RPC 2.0 request handler.
3
+ * Dispatches incoming requests to the SQLite query layer.
4
+ */
5
+ import type { ImsgDatabase } from './db.js';
6
+ interface JsonRpcRequest {
7
+ jsonrpc: '2.0';
8
+ id: number;
9
+ method: string;
10
+ params?: Record<string, unknown>;
11
+ }
12
+ interface JsonRpcResponse {
13
+ jsonrpc: '2.0';
14
+ id: number;
15
+ result?: unknown;
16
+ error?: {
17
+ code: number;
18
+ message: string;
19
+ };
20
+ }
21
+ export declare class RpcHandler {
22
+ private db;
23
+ constructor(db: ImsgDatabase);
24
+ handle(request: JsonRpcRequest): Promise<JsonRpcResponse>;
25
+ }
26
+ export {};
@@ -0,0 +1,62 @@
1
+ /**
2
+ * JSON-RPC 2.0 request handler.
3
+ * Dispatches incoming requests to the SQLite query layer.
4
+ */
5
+ import { listAppleContacts } from './contacts.js';
6
+ export class RpcHandler {
7
+ db;
8
+ constructor(db) {
9
+ this.db = db;
10
+ }
11
+ async handle(request) {
12
+ const { id, method, params } = request;
13
+ try {
14
+ switch (method) {
15
+ case 'chats.list': {
16
+ const limit = params?.limit;
17
+ const chats = this.db.chatsList(limit);
18
+ return { jsonrpc: '2.0', id, result: { chats } };
19
+ }
20
+ case 'messages.history': {
21
+ const chatId = params?.chat_id;
22
+ if (chatId === undefined || chatId === null) {
23
+ return {
24
+ jsonrpc: '2.0',
25
+ id,
26
+ error: { code: -32602, message: 'Missing required param: chat_id' },
27
+ };
28
+ }
29
+ const opts = {
30
+ limit: params?.limit,
31
+ start: params?.start,
32
+ end: params?.end,
33
+ };
34
+ const messages = this.db.messagesHistory(chatId, opts);
35
+ return { jsonrpc: '2.0', id, result: { messages } };
36
+ }
37
+ case 'contacts.list': {
38
+ const contacts = await listAppleContacts();
39
+ return { jsonrpc: '2.0', id, result: { contacts } };
40
+ }
41
+ case 'ping': {
42
+ return { jsonrpc: '2.0', id, result: { pong: true, ts: Date.now() } };
43
+ }
44
+ default:
45
+ return {
46
+ jsonrpc: '2.0',
47
+ id,
48
+ error: { code: -32601, message: `Method not found: ${method}` },
49
+ };
50
+ }
51
+ }
52
+ catch (err) {
53
+ const msg = err instanceof Error ? err.message : String(err);
54
+ return {
55
+ jsonrpc: '2.0',
56
+ id,
57
+ error: { code: -32000, message: msg },
58
+ };
59
+ }
60
+ }
61
+ }
62
+ //# sourceMappingURL=rpc-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-handler.js","sourceRoot":"","sources":["../src/rpc-handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAgBlD,MAAM,OAAO,UAAU;IACD;IAApB,YAAoB,EAAgB;QAAhB,OAAE,GAAF,EAAE,CAAc;IAAG,CAAC;IAExC,KAAK,CAAC,MAAM,CAAC,OAAuB;QAClC,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAEvC,IAAI,CAAC;YACH,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,YAAY,CAAC,CAAC,CAAC;oBAClB,MAAM,KAAK,GAAG,MAAM,EAAE,KAA2B,CAAC;oBAClD,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;oBACvC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC;gBACnD,CAAC;gBAED,KAAK,kBAAkB,CAAC,CAAC,CAAC;oBACxB,MAAM,MAAM,GAAG,MAAM,EAAE,OAAiB,CAAC;oBACzC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;wBAC5C,OAAO;4BACL,OAAO,EAAE,KAAK;4BACd,EAAE;4BACF,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,iCAAiC,EAAE;yBACpE,CAAC;oBACJ,CAAC;oBACD,MAAM,IAAI,GAAG;wBACX,KAAK,EAAE,MAAM,EAAE,KAA2B;wBAC1C,KAAK,EAAE,MAAM,EAAE,KAA2B;wBAC1C,GAAG,EAAE,MAAM,EAAE,GAAyB;qBACvC,CAAC;oBACF,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;oBACvD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAC;gBACtD,CAAC;gBAED,KAAK,eAAe,CAAC,CAAC,CAAC;oBACrB,MAAM,QAAQ,GAAG,MAAM,iBAAiB,EAAE,CAAC;oBAC3C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAC;gBACtD,CAAC;gBAED,KAAK,MAAM,CAAC,CAAC,CAAC;oBACZ,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC;gBACxE,CAAC;gBAED;oBACE,OAAO;wBACL,OAAO,EAAE,KAAK;wBACd,EAAE;wBACF,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,qBAAqB,MAAM,EAAE,EAAE;qBAChE,CAAC;YACN,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,EAAE;gBACF,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE;aACtC,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * WebSocket tunnel client.
3
+ *
4
+ * Connects to the Botmem server, performs ECDH key exchange,
5
+ * then relays encrypted JSON-RPC requests to the local RPC handler.
6
+ */
7
+ import { EventEmitter } from 'node:events';
8
+ import type { RpcHandler } from './rpc-handler.js';
9
+ export interface TunnelOptions {
10
+ serverUrl: string;
11
+ token: string;
12
+ rpcHandler: RpcHandler;
13
+ }
14
+ export type TunnelStatus = 'connecting' | 'authenticating' | 'connected' | 'disconnected' | 'error';
15
+ export declare class TunnelClient extends EventEmitter {
16
+ private opts;
17
+ private ws;
18
+ private sessionKey;
19
+ private reconnectAttempt;
20
+ private reconnectTimer;
21
+ private heartbeatTimer;
22
+ private heartbeatTimeout;
23
+ private destroyed;
24
+ private _status;
25
+ constructor(opts: TunnelOptions);
26
+ get status(): TunnelStatus;
27
+ /** Start the tunnel connection. */
28
+ connect(): void;
29
+ /** Gracefully disconnect. */
30
+ destroy(): void;
31
+ private performHandshake;
32
+ private handleAuthResponse;
33
+ private handleEncryptedMessage;
34
+ private startHeartbeat;
35
+ private scheduleReconnect;
36
+ private cleanup;
37
+ private setStatus;
38
+ }
package/dist/tunnel.js ADDED
@@ -0,0 +1,203 @@
1
+ /**
2
+ * WebSocket tunnel client.
3
+ *
4
+ * Connects to the Botmem server, performs ECDH key exchange,
5
+ * then relays encrypted JSON-RPC requests to the local RPC handler.
6
+ */
7
+ import WebSocket from 'ws';
8
+ import { EventEmitter } from 'node:events';
9
+ import { generateKeyPair, exportPublicKey, importPublicKey, deriveSessionKey, encryptJson, decryptJson, } from './crypto.js';
10
+ const MAX_BACKOFF_MS = 30_000;
11
+ const HEARTBEAT_INTERVAL_MS = 30_000;
12
+ const HEARTBEAT_TIMEOUT_MS = 10_000;
13
+ export class TunnelClient extends EventEmitter {
14
+ opts;
15
+ ws = null;
16
+ sessionKey = null;
17
+ reconnectAttempt = 0;
18
+ reconnectTimer = null;
19
+ heartbeatTimer = null;
20
+ heartbeatTimeout = null;
21
+ destroyed = false;
22
+ _status = 'disconnected';
23
+ constructor(opts) {
24
+ super();
25
+ this.opts = opts;
26
+ }
27
+ get status() {
28
+ return this._status;
29
+ }
30
+ /** Start the tunnel connection. */
31
+ connect() {
32
+ if (this.destroyed)
33
+ return;
34
+ this.setStatus('connecting');
35
+ const ws = new WebSocket(this.opts.serverUrl, {
36
+ headers: { 'User-Agent': 'botmem-apple-bridge/0.1' },
37
+ });
38
+ this.ws = ws;
39
+ ws.on('open', () => {
40
+ this.setStatus('authenticating');
41
+ this.performHandshake(ws);
42
+ });
43
+ ws.on('message', (data, isBinary) => {
44
+ if (this._status === 'authenticating') {
45
+ // Auth response is JSON text
46
+ this.handleAuthResponse(ws, typeof data === 'string' ? data : data.toString('utf-8'));
47
+ }
48
+ else if (this._status === 'connected') {
49
+ // All post-auth messages are encrypted binary
50
+ if (isBinary || Buffer.isBuffer(data)) {
51
+ this.handleEncryptedMessage(ws, Buffer.isBuffer(data) ? data : Buffer.from(data));
52
+ }
53
+ }
54
+ });
55
+ ws.on('pong', () => {
56
+ if (this.heartbeatTimeout) {
57
+ clearTimeout(this.heartbeatTimeout);
58
+ this.heartbeatTimeout = null;
59
+ }
60
+ });
61
+ ws.on('close', (code, reason) => {
62
+ this.cleanup();
63
+ if (!this.destroyed) {
64
+ this.emit('log', `Disconnected (code=${code}, reason=${reason?.toString() || 'none'})`);
65
+ this.setStatus('disconnected');
66
+ this.scheduleReconnect();
67
+ }
68
+ });
69
+ ws.on('error', (err) => {
70
+ this.emit('log', `WebSocket error: ${err.message}`);
71
+ // 'close' will fire after this
72
+ });
73
+ }
74
+ /** Gracefully disconnect. */
75
+ destroy() {
76
+ this.destroyed = true;
77
+ this.cleanup();
78
+ if (this.ws) {
79
+ this.ws.close(1000, 'Bridge shutting down');
80
+ this.ws = null;
81
+ }
82
+ if (this.reconnectTimer) {
83
+ clearTimeout(this.reconnectTimer);
84
+ this.reconnectTimer = null;
85
+ }
86
+ this.setStatus('disconnected');
87
+ }
88
+ // ── Handshake ───────────────────────────────────────────────────────────
89
+ performHandshake(ws) {
90
+ const keyPair = generateKeyPair();
91
+ const publicKeyRaw = exportPublicKey(keyPair.publicKey);
92
+ // Store private key for deriving session key after server responds
93
+ ws._ecdhPrivate =
94
+ keyPair.privateKey;
95
+ ws.send(JSON.stringify({
96
+ event: 'auth',
97
+ data: {
98
+ token: this.opts.token,
99
+ publicKey: publicKeyRaw.toString('base64'),
100
+ },
101
+ }));
102
+ }
103
+ handleAuthResponse(ws, raw) {
104
+ try {
105
+ const msg = JSON.parse(raw);
106
+ if (msg.event !== 'auth')
107
+ return;
108
+ if (!msg.data.ok) {
109
+ const reason = msg.data.reason || 'unknown';
110
+ this.emit('log', `Auth failed: ${reason}`);
111
+ // Permanent auth failures — don't reconnect
112
+ this.destroyed = true;
113
+ this.setStatus('error');
114
+ this.emit('fatal', `Authentication failed: ${reason}. Check your bridge token.`);
115
+ ws.close(4401, 'Auth failed');
116
+ return;
117
+ }
118
+ if (!msg.data.publicKey || !ws._ecdhPrivate) {
119
+ this.emit('log', 'Auth response missing public key');
120
+ this.setStatus('error');
121
+ ws.close(4400, 'Missing public key');
122
+ return;
123
+ }
124
+ // Derive session key
125
+ const serverPubRaw = Buffer.from(msg.data.publicKey, 'base64');
126
+ const serverPub = importPublicKey(serverPubRaw);
127
+ this.sessionKey = deriveSessionKey(ws._ecdhPrivate, serverPub);
128
+ // Clean up private key from ws object
129
+ delete ws._ecdhPrivate;
130
+ this.reconnectAttempt = 0;
131
+ this.setStatus('connected');
132
+ this.startHeartbeat(ws);
133
+ this.emit('log', 'Tunnel connected — encrypted session established');
134
+ }
135
+ catch (err) {
136
+ this.emit('log', `Auth response parse error: ${err instanceof Error ? err.message : err}`);
137
+ }
138
+ }
139
+ // ── Encrypted message handling ──────────────────────────────────────────
140
+ async handleEncryptedMessage(ws, encrypted) {
141
+ if (!this.sessionKey)
142
+ return;
143
+ try {
144
+ const request = decryptJson(this.sessionKey, encrypted);
145
+ // Dispatch to RPC handler
146
+ const startedAt = Date.now();
147
+ const response = await this.opts.rpcHandler.handle(request);
148
+ const elapsedMs = Date.now() - startedAt;
149
+ if (elapsedMs >= 1000) {
150
+ this.emit('log', `RPC ${request.method} (id=${request.id}) completed in ${elapsedMs}ms`);
151
+ }
152
+ // Encrypt and send response
153
+ const encryptedResponse = encryptJson(this.sessionKey, response);
154
+ if (ws.readyState === WebSocket.OPEN) {
155
+ ws.send(encryptedResponse);
156
+ }
157
+ }
158
+ catch (err) {
159
+ this.emit('log', `Failed to handle message: ${err instanceof Error ? err.message : err}`);
160
+ }
161
+ }
162
+ // ── Heartbeat ───────────────────────────────────────────────────────────
163
+ startHeartbeat(ws) {
164
+ this.heartbeatTimer = setInterval(() => {
165
+ if (ws.readyState !== WebSocket.OPEN)
166
+ return;
167
+ ws.ping();
168
+ this.heartbeatTimeout = setTimeout(() => {
169
+ this.emit('log', 'Heartbeat timeout — closing connection');
170
+ ws.terminate();
171
+ }, HEARTBEAT_TIMEOUT_MS);
172
+ }, HEARTBEAT_INTERVAL_MS);
173
+ }
174
+ // ── Reconnection ────────────────────────────────────────────────────────
175
+ scheduleReconnect() {
176
+ if (this.destroyed)
177
+ return;
178
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempt), MAX_BACKOFF_MS);
179
+ this.reconnectAttempt++;
180
+ this.emit('log', `Reconnecting in ${(delay / 1000).toFixed(1)}s (attempt ${this.reconnectAttempt})`);
181
+ this.reconnectTimer = setTimeout(() => {
182
+ this.reconnectTimer = null;
183
+ this.connect();
184
+ }, delay);
185
+ }
186
+ // ── Helpers ─────────────────────────────────────────────────────────────
187
+ cleanup() {
188
+ this.sessionKey = null;
189
+ if (this.heartbeatTimer) {
190
+ clearInterval(this.heartbeatTimer);
191
+ this.heartbeatTimer = null;
192
+ }
193
+ if (this.heartbeatTimeout) {
194
+ clearTimeout(this.heartbeatTimeout);
195
+ this.heartbeatTimeout = null;
196
+ }
197
+ }
198
+ setStatus(status) {
199
+ this._status = status;
200
+ this.emit('status', status);
201
+ }
202
+ }
203
+ //# sourceMappingURL=tunnel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel.js","sourceRoot":"","sources":["../src/tunnel.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@botmem/apple-bridge",
3
+ "version": "0.35.25",
4
+ "description": "Apple bridge for Botmem — reads local Contacts and iMessages through an encrypted WebSocket tunnel",
5
+ "type": "module",
6
+ "bin": {
7
+ "apple-bridge": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsc --watch",
14
+ "clean": "rm -rf dist"
15
+ },
16
+ "dependencies": {
17
+ "better-sqlite3": "^12.9.0",
18
+ "commander": "^14.0.3",
19
+ "ws": "^8.20.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/better-sqlite3": "^7.6.12",
23
+ "@types/node": "^25.6.0",
24
+ "@types/ws": "^8.5.13",
25
+ "typescript": "^5.7.0"
26
+ },
27
+ "engines": {
28
+ "node": ">=20"
29
+ },
30
+ "os": [
31
+ "darwin"
32
+ ],
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "license": "MIT"
40
+ }