@chainlesschain/personal-data-hub 0.4.29 → 0.4.30
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/lib/prompt-builder.js +15 -1
- package/package.json +4 -1
- package/__tests__/adapter-guide.test.js +0 -47
- package/__tests__/adapter-spec.test.js +0 -78
- package/__tests__/adapters/ai-chat-cookie-capture-spec.test.js +0 -211
- package/__tests__/adapters/ai-chat-health-checker.test.js +0 -262
- package/__tests__/adapters/ai-chat-history.test.js +0 -396
- package/__tests__/adapters/ai-chat-http-client.test.js +0 -242
- package/__tests__/adapters/ai-chat-vendors.test.js +0 -874
- package/__tests__/adapters/alipay-bill-adapter.test.js +0 -538
- package/__tests__/adapters/apple-health.test.js +0 -95
- package/__tests__/adapters/bank-family.test.js +0 -125
- package/__tests__/adapters/biz-tianyancha.test.js +0 -159
- package/__tests__/adapters/browser-history-chrome.test.js +0 -377
- package/__tests__/adapters/browser-history-edge.test.js +0 -159
- package/__tests__/adapters/car-mercedesme.test.js +0 -74
- package/__tests__/adapters/doc-baidu-netdisk.test.js +0 -102
- package/__tests__/adapters/doc-camscanner.test.js +0 -147
- package/__tests__/adapters/doc-platforms.test.js +0 -177
- package/__tests__/adapters/edu-huawei-learning-live.test.js +0 -198
- package/__tests__/adapters/edu-zuoyebang-live.test.js +0 -226
- package/__tests__/adapters/email-adapter-snapshot.test.js +0 -237
- package/__tests__/adapters/email-adapter.test.js +0 -742
- package/__tests__/adapters/email-classifier.test.js +0 -347
- package/__tests__/adapters/email-imap-session.test.js +0 -334
- package/__tests__/adapters/email-parser.test.js +0 -244
- package/__tests__/adapters/email-pdf-extractor.test.js +0 -529
- package/__tests__/adapters/email-providers.test.js +0 -84
- package/__tests__/adapters/email-retry-progress.test.js +0 -294
- package/__tests__/adapters/email-templates.test.js +0 -822
- package/__tests__/adapters/family-23-collectors-scaffold.test.js +0 -182
- package/__tests__/adapters/finance-alipay-live.test.js +0 -258
- package/__tests__/adapters/finance-dcep.test.js +0 -74
- package/__tests__/adapters/fitness-joyrun.test.js +0 -82
- package/__tests__/adapters/game-genshin-live.test.js +0 -238
- package/__tests__/adapters/game-genshin-scaffold.test.js +0 -108
- package/__tests__/adapters/game-honor-of-kings-live.test.js +0 -230
- package/__tests__/adapters/git-activity.test.js +0 -222
- package/__tests__/adapters/gov-12123.test.js +0 -103
- package/__tests__/adapters/gov-ixiamen.test.js +0 -150
- package/__tests__/adapters/gov-tax.test.js +0 -135
- package/__tests__/adapters/health-meiyou.test.js +0 -125
- package/__tests__/adapters/local-files.test.js +0 -264
- package/__tests__/adapters/local-im-pc.test.js +0 -154
- package/__tests__/adapters/messaging-whatsapp.test.js +0 -289
- package/__tests__/adapters/music-kugou.test.js +0 -187
- package/__tests__/adapters/music-qq.test.js +0 -112
- package/__tests__/adapters/netease-music-live.test.js +0 -244
- package/__tests__/adapters/netease-music.test.js +0 -74
- package/__tests__/adapters/pc-local-discovery.test.js +0 -141
- package/__tests__/adapters/qq-pc-direct-read.test.js +0 -227
- package/__tests__/adapters/reading-family.test.js +0 -108
- package/__tests__/adapters/recruit-boss.test.js +0 -180
- package/__tests__/adapters/shell-history.test.js +0 -180
- package/__tests__/adapters/shopping-base.test.js +0 -179
- package/__tests__/adapters/shopping-dianping.test.js +0 -239
- package/__tests__/adapters/social-bilibili-adb-api-client.test.js +0 -721
- package/__tests__/adapters/social-bilibili-adb-chromium-cookies-reader.test.js +0 -346
- package/__tests__/adapters/social-bilibili-adb-collector.test.js +0 -284
- package/__tests__/adapters/social-bilibili-adb-cookies-extension.test.js +0 -343
- package/__tests__/adapters/social-bilibili-adb-snapshot-builder.test.js +0 -296
- package/__tests__/adapters/social-csdn.test.js +0 -175
- package/__tests__/adapters/social-dongchedi.test.js +0 -165
- package/__tests__/adapters/social-douyin-adb-aweme-detail.test.js +0 -165
- package/__tests__/adapters/social-douyin-adb-collector.test.js +0 -254
- package/__tests__/adapters/social-douyin-adb-db-extension.test.js +0 -114
- package/__tests__/adapters/social-douyin-adb-im-db-parser.test.js +0 -304
- package/__tests__/adapters/social-douyin-adb-snapshot-builder.test.js +0 -216
- package/__tests__/adapters/social-douyin-adb-usage-profile.test.js +0 -229
- package/__tests__/adapters/social-douyin-adb-watch-history.test.js +0 -269
- package/__tests__/adapters/social-kuaishou-adb-api-client.test.js +0 -496
- package/__tests__/adapters/social-kuaishou-adb-collector.test.js +0 -276
- package/__tests__/adapters/social-kuaishou-adb-cookies-extension.test.js +0 -152
- package/__tests__/adapters/social-kuaishou-adb-snapshot-builder.test.js +0 -178
- package/__tests__/adapters/social-toutiao-adb-account-reader.test.js +0 -135
- package/__tests__/adapters/social-toutiao-adb-api-client.test.js +0 -626
- package/__tests__/adapters/social-toutiao-adb-article.test.js +0 -155
- package/__tests__/adapters/social-toutiao-adb-collector.test.js +0 -378
- package/__tests__/adapters/social-toutiao-adb-cookies-extension.test.js +0 -193
- package/__tests__/adapters/social-toutiao-adb-snapshot-builder.test.js +0 -196
- package/__tests__/adapters/social-toutiao-kuaishou-scaffold.test.js +0 -311
- package/__tests__/adapters/social-weibo-adb-api-client.test.js +0 -362
- package/__tests__/adapters/social-weibo-adb-collector.test.js +0 -201
- package/__tests__/adapters/social-weibo-adb-cookies-extension.test.js +0 -167
- package/__tests__/adapters/social-weibo-adb-snapshot-builder.test.js +0 -189
- package/__tests__/adapters/social-xiaohongshu-adb-api-client.test.js +0 -431
- package/__tests__/adapters/social-xiaohongshu-adb-collector.test.js +0 -207
- package/__tests__/adapters/social-xiaohongshu-adb-cookies-extension.test.js +0 -0
- package/__tests__/adapters/social-xiaohongshu-adb-sign-provider-injection.test.js +0 -351
- package/__tests__/adapters/social-xiaohongshu-adb-sign.test.js +0 -130
- package/__tests__/adapters/social-xiaohongshu-adb-snapshot-builder.test.js +0 -200
- package/__tests__/adapters/social-zhihu.test.js +0 -246
- package/__tests__/adapters/system-data-adapter.test.js +0 -443
- package/__tests__/adapters/system-data-android-ingest.test.js +0 -144
- package/__tests__/adapters/system-data-android.test.js +0 -519
- package/__tests__/adapters/system-data-disclosure.test.js +0 -153
- package/__tests__/adapters/travel-12306.test.js +0 -512
- package/__tests__/adapters/travel-amap.test.js +0 -219
- package/__tests__/adapters/travel-baidu-map.test.js +0 -305
- package/__tests__/adapters/travel-base.test.js +0 -205
- package/__tests__/adapters/travel-ctrip.test.js +0 -377
- package/__tests__/adapters/travel-didi-consumer.test.js +0 -66
- package/__tests__/adapters/travel-didi.test.js +0 -204
- package/__tests__/adapters/travel-tencent-map.test.js +0 -207
- package/__tests__/adapters/travel-tongcheng.test.js +0 -289
- package/__tests__/adapters/video-platforms.test.js +0 -152
- package/__tests__/adapters/video-xigua.test.js +0 -106
- package/__tests__/adapters/vscode.test.js +0 -299
- package/__tests__/adapters/wechat-bootstrap.test.js +0 -240
- package/__tests__/adapters/wechat-env-probe.test.js +0 -162
- package/__tests__/adapters/wechat-frida-agent.test.js +0 -322
- package/__tests__/adapters/wechat-frida-integration.test.js +0 -149
- package/__tests__/adapters/wechat-frida-key-provider.test.js +0 -188
- package/__tests__/adapters/wechat-md5-key-provider.test.js +0 -101
- package/__tests__/adapters/wechat-pc-direct-read.test.js +0 -365
- package/__tests__/adapters/wechat-pc-group-topic.test.js +0 -63
- package/__tests__/adapters/wechat-pc-v4-sidecar.test.js +0 -72
- package/__tests__/adapters/weread.test.js +0 -123
- package/__tests__/adapters/wework-pc.test.js +0 -124
- package/__tests__/adapters/win-recent.test.js +0 -192
- package/__tests__/analysis-skills.test.js +0 -754
- package/__tests__/analysis.test.js +0 -1845
- package/__tests__/audio-ximalaya-snapshot.test.js +0 -279
- package/__tests__/batch.test.js +0 -133
- package/__tests__/bridges-cc-kg.test.js +0 -231
- package/__tests__/bridges-cc-llm.test.js +0 -191
- package/__tests__/bridges-cc-rag.test.js +0 -162
- package/__tests__/categories.test.js +0 -92
- package/__tests__/e2e/ai-chat-cross-source-journey.test.js +0 -213
- package/__tests__/e2e/full-user-journey.test.js +0 -188
- package/__tests__/e2e/local-data-adapters-cli.e2e.test.js +0 -146
- package/__tests__/entity-resolver-ingest-hook.test.js +0 -177
- package/__tests__/entity-resolver-stages.test.js +0 -411
- package/__tests__/entity-resolver-vault.test.js +0 -249
- package/__tests__/entity-resolver.test.js +0 -526
- package/__tests__/fitness-keep-snapshot.test.js +0 -224
- package/__tests__/fixtures/entity-resolver-200-mock.json +0 -96
- package/__tests__/ids.test.js +0 -45
- package/__tests__/integration/ai-chat-history-registry.test.js +0 -228
- package/__tests__/integration/aichat-wizard-end-to-end.test.js +0 -282
- package/__tests__/integration/cross-adapter-pipelines.test.js +0 -396
- package/__tests__/integration/local-data-adapters-pipeline.test.js +0 -373
- package/__tests__/integration/social-bilibili-pipeline.test.js +0 -261
- package/__tests__/integration/wechat-bootstrap-end-to-end.test.js +0 -390
- package/__tests__/key-providers.test.js +0 -126
- package/__tests__/kg-derive.test.js +0 -219
- package/__tests__/llm-client.test.js +0 -122
- package/__tests__/longtail-adapters.test.js +0 -281
- package/__tests__/messaging-qq-snapshot.test.js +0 -294
- package/__tests__/mobile-extractor-encrypted.test.js +0 -460
- package/__tests__/mobile-extractor.test.js +0 -288
- package/__tests__/mock-adapter.test.js +0 -93
- package/__tests__/prompt-builder.test.js +0 -249
- package/__tests__/query-parser.test.js +0 -365
- package/__tests__/rag-derive.test.js +0 -169
- package/__tests__/registry-readiness.test.js +0 -292
- package/__tests__/registry.test.js +0 -420
- package/__tests__/salvage-ingest.test.js +0 -97
- package/__tests__/schemas.test.js +0 -331
- package/__tests__/shopping-adapters.test.js +0 -392
- package/__tests__/shopping-eleme-snapshot.test.js +0 -454
- package/__tests__/shopping-pinduoduo-snapshot.test.js +0 -484
- package/__tests__/shopping-snapshot.test.js +0 -438
- package/__tests__/shopping-vipshop-snapshot.test.js +0 -425
- package/__tests__/shopping-xianyu-snapshot.test.js +0 -451
- package/__tests__/sidecar-contacts-cross-validate.test.js +0 -186
- package/__tests__/sidecar-supervisor.test.js +0 -128
- package/__tests__/sign-providers.test.js +0 -62
- package/__tests__/social-adapters.test.js +0 -280
- package/__tests__/social-bilibili-snapshot.test.js +0 -278
- package/__tests__/social-douban-snapshot.test.js +0 -351
- package/__tests__/social-douyin-im-direct-read.test.js +0 -377
- package/__tests__/social-douyin-salvage-collector.test.js +0 -98
- package/__tests__/social-douyin-salvage-mapper.test.js +0 -90
- package/__tests__/social-douyin-snapshot.test.js +0 -256
- package/__tests__/social-kuaishou-snapshot.test.js +0 -362
- package/__tests__/social-toutiao-snapshot.test.js +0 -366
- package/__tests__/social-weibo-snapshot.test.js +0 -234
- package/__tests__/social-weibo-sqlite-device.test.js +0 -174
- package/__tests__/social-xiaohongshu-snapshot.test.js +0 -232
- package/__tests__/sqlite-leaf-salvage.test.js +0 -97
- package/__tests__/travel-adapters.test.js +0 -483
- package/__tests__/travel-maps-snapshot.test.js +0 -426
- package/__tests__/vault-driver-error.test.js +0 -74
- package/__tests__/vault-search-helpers.test.js +0 -104
- package/__tests__/vault-search.test.js +0 -423
- package/__tests__/vault.test.js +0 -767
- package/__tests__/wechat-adapter.test.js +0 -594
- package/__tests__/whatsapp-adapter.test.js +0 -138
- package/scripts/_make-fixture-all.js +0 -126
- package/scripts/_make-fixture-contacts.js +0 -84
- package/scripts/evaluate-entity-resolver.js +0 -213
- package/scripts/run-native-tests-sandbox.sh +0 -55
- package/scripts/smoke-phase-5-5.js +0 -196
- package/scripts/smoke-phase-5-7.js +0 -181
- package/scripts/smoke-system-data-contacts.js +0 -309
- package/scripts/smoke-system-data.js +0 -312
- package/vitest.config.js +0 -88
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WeChat Phase 12.6.7-10 end-to-end integration test.
|
|
3
|
-
*
|
|
4
|
-
* Exercises the full chain WITHOUT any real adb / Frida / device:
|
|
5
|
-
*
|
|
6
|
-
* env-probe (injected facts)
|
|
7
|
-
* ↓
|
|
8
|
-
* bootstrap.js (KeyProvider choice + adapter ctor)
|
|
9
|
-
* ↓
|
|
10
|
-
* AdapterRegistry (real, in-memory)
|
|
11
|
-
* ↓
|
|
12
|
-
* wechat-accounts.json persistence (real fs, temp dir)
|
|
13
|
-
* ↓
|
|
14
|
-
* list / unregister flow
|
|
15
|
-
*
|
|
16
|
-
* Three scenarios:
|
|
17
|
-
* A. md5 happy path — pre-WeChat-8 device:
|
|
18
|
-
* probe="md5" → wechatDataPath provided → register OK →
|
|
19
|
-
* registry has "wechat" adapter → persisted row chosenKeyProvider="md5"
|
|
20
|
-
* → unregister → row removed + registry empty
|
|
21
|
-
* B. frida happy path — rooted 8.0+ device:
|
|
22
|
-
* probe="frida" + root + frida-server up → register OK →
|
|
23
|
-
* chosenKeyProvider="frida" → persisted row reflects choice
|
|
24
|
-
* C. unsupported path — 8.0+ without root:
|
|
25
|
-
* probe="unsupported" → bootstrap rejects → no registry change,
|
|
26
|
-
* no row written, ok:false with reasons surfaced
|
|
27
|
-
* D. idempotent re-register — same uin twice:
|
|
28
|
-
* first registration with wechatDataPath A, second with B →
|
|
29
|
-
* single row remains, wechatDataPath=B (replaces, doesn't dupe)
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
"use strict";
|
|
33
|
-
|
|
34
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
35
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
36
|
-
import { tmpdir } from "node:os";
|
|
37
|
-
import { join } from "node:path";
|
|
38
|
-
|
|
39
|
-
const {
|
|
40
|
-
bootstrapWechatAdapter,
|
|
41
|
-
} = require("../../lib/adapters/wechat/bootstrap");
|
|
42
|
-
const { AdapterRegistry } = require("../../lib/registry");
|
|
43
|
-
const { LocalVault } = require("../../lib/vault");
|
|
44
|
-
const { InMemoryKeyProvider } = require("../../lib/key-providers");
|
|
45
|
-
const { generateKeyHex } = require("../../lib/key-providers");
|
|
46
|
-
|
|
47
|
-
// Mirror of the hub-side store helpers so the integration test exercises
|
|
48
|
-
// the same persistence shape both desktop + cli wirings use.
|
|
49
|
-
const { readFileSync, writeFileSync, existsSync } = require("node:fs");
|
|
50
|
-
function loadWechatAccounts(filePath) {
|
|
51
|
-
try {
|
|
52
|
-
if (!existsSync(filePath)) return [];
|
|
53
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
54
|
-
const parsed = JSON.parse(raw);
|
|
55
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
56
|
-
} catch (_e) {
|
|
57
|
-
return [];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function saveWechatAccounts(filePath, accounts) {
|
|
61
|
-
writeFileSync(filePath, JSON.stringify(accounts, null, 2), {
|
|
62
|
-
encoding: "utf-8",
|
|
63
|
-
mode: 0o600,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function mkProbe(overrides = {}) {
|
|
68
|
-
return {
|
|
69
|
-
ok: true,
|
|
70
|
-
suggestedKeyProvider: "md5",
|
|
71
|
-
reasons: ["WeChat 7.0.22 (< 8.0) — legacy MD5(IMEI+UIN) path supported"],
|
|
72
|
-
device: { reachable: true, serial: "INTEG_TEST", abi: "arm64-v8a" },
|
|
73
|
-
root: { detected: false, magiskInstalled: false },
|
|
74
|
-
frida: { serverRunning: false, port: null },
|
|
75
|
-
wechat: { installed: true, versionName: "7.0.22", majorVersion: 7 },
|
|
76
|
-
warnings: [],
|
|
77
|
-
...overrides,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Replay the wiring's registerWechatAdapter() inline — mirroring the
|
|
82
|
-
// closure on the real hub object — so the integration test exercises
|
|
83
|
-
// the exact code path the IPC/WS layer drives in production.
|
|
84
|
-
async function registerWechatViaHub({ registry, hubDir, opts }) {
|
|
85
|
-
const r = await bootstrapWechatAdapter(opts);
|
|
86
|
-
if (!r.ok) return r;
|
|
87
|
-
|
|
88
|
-
if (registry.has(r.adapter.name)) registry.unregister(r.adapter.name);
|
|
89
|
-
registry.register(r.adapter);
|
|
90
|
-
|
|
91
|
-
const accountsPath = join(hubDir, "wechat-accounts.json");
|
|
92
|
-
const accounts = loadWechatAccounts(accountsPath);
|
|
93
|
-
const next = accounts.filter(
|
|
94
|
-
(c) => !(c.account && c.account.uin === opts.account.uin),
|
|
95
|
-
);
|
|
96
|
-
next.push({
|
|
97
|
-
account: { uin: opts.account.uin },
|
|
98
|
-
dbPath: opts.dbPath || null,
|
|
99
|
-
wechatDataPath: opts.wechatDataPath || null,
|
|
100
|
-
chosenKeyProvider: r.keyProvider && r.keyProvider.name,
|
|
101
|
-
registeredAt: Date.now(),
|
|
102
|
-
lastSyncAt: null,
|
|
103
|
-
});
|
|
104
|
-
saveWechatAccounts(accountsPath, next);
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
ok: true,
|
|
108
|
-
name: r.adapter.name,
|
|
109
|
-
chosenKeyProvider: r.keyProvider.name,
|
|
110
|
-
probe: r.probe,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function unregisterWechatViaHub({ registry, hubDir, uin }) {
|
|
115
|
-
const accountsPath = join(hubDir, "wechat-accounts.json");
|
|
116
|
-
const accounts = loadWechatAccounts(accountsPath);
|
|
117
|
-
const target = accounts.find((c) => c.account && c.account.uin === uin);
|
|
118
|
-
const next = accounts.filter(
|
|
119
|
-
(c) => !(c.account && c.account.uin === uin),
|
|
120
|
-
);
|
|
121
|
-
saveWechatAccounts(accountsPath, next);
|
|
122
|
-
if (target && registry.has("wechat")) registry.unregister("wechat");
|
|
123
|
-
return { ok: true, removed: !!target, uin };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function listWechatViaHub({ hubDir }) {
|
|
127
|
-
return loadWechatAccounts(join(hubDir, "wechat-accounts.json")).map((row) => ({
|
|
128
|
-
uin: row.account ? row.account.uin : null,
|
|
129
|
-
dbPath: row.dbPath || null,
|
|
130
|
-
hasWechatDataPath: !!row.wechatDataPath,
|
|
131
|
-
chosenKeyProvider: row.chosenKeyProvider || null,
|
|
132
|
-
registeredAt: row.registeredAt || null,
|
|
133
|
-
}));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
describe("WeChat Phase 12.6.7-10 — end-to-end integration", () => {
|
|
137
|
-
let hubDir;
|
|
138
|
-
let dataDir;
|
|
139
|
-
let registry;
|
|
140
|
-
|
|
141
|
-
beforeEach(() => {
|
|
142
|
-
hubDir = mkdtempSync(join(tmpdir(), "pdh-wechat-integ-"));
|
|
143
|
-
dataDir = mkdtempSync(join(tmpdir(), "pdh-wechat-data-"));
|
|
144
|
-
|
|
145
|
-
// A real registry without vault/sinks — we don't sync, just
|
|
146
|
-
// register/unregister. Phase 12.6.7 boundary: bootstrap doesn't
|
|
147
|
-
// touch the registry, the wiring does (replicated above).
|
|
148
|
-
const vault = new LocalVault({
|
|
149
|
-
path: join(hubDir, "vault.db"),
|
|
150
|
-
key: generateKeyHex(),
|
|
151
|
-
});
|
|
152
|
-
vault.open();
|
|
153
|
-
registry = new AdapterRegistry({ vault });
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
afterEach(() => {
|
|
157
|
-
try { rmSync(hubDir, { recursive: true, force: true }); } catch (_e) {}
|
|
158
|
-
try { rmSync(dataDir, { recursive: true, force: true }); } catch (_e) {}
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe("A. md5 happy path — pre-WeChat-8 device", () => {
|
|
162
|
-
it("probe → register → adapter in registry + row persisted with md5 provider", async () => {
|
|
163
|
-
const r = await registerWechatViaHub({
|
|
164
|
-
registry,
|
|
165
|
-
hubDir,
|
|
166
|
-
opts: {
|
|
167
|
-
account: { uin: "1234567890" },
|
|
168
|
-
wechatDataPath: dataDir,
|
|
169
|
-
_probe: mkProbe(),
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// Bootstrap chain succeeded
|
|
174
|
-
expect(r.ok).toBe(true);
|
|
175
|
-
expect(r.chosenKeyProvider).toBe("md5");
|
|
176
|
-
expect(r.name).toBe("wechat");
|
|
177
|
-
expect(r.probe.suggestedKeyProvider).toBe("md5");
|
|
178
|
-
|
|
179
|
-
// Registry picked up the adapter
|
|
180
|
-
expect(registry.has("wechat")).toBe(true);
|
|
181
|
-
const adapter = registry.get("wechat");
|
|
182
|
-
expect(adapter.account.uin).toBe("1234567890");
|
|
183
|
-
|
|
184
|
-
// Persistence reflects choice
|
|
185
|
-
const list = listWechatViaHub({ hubDir });
|
|
186
|
-
expect(list).toHaveLength(1);
|
|
187
|
-
expect(list[0]).toMatchObject({
|
|
188
|
-
uin: "1234567890",
|
|
189
|
-
chosenKeyProvider: "md5",
|
|
190
|
-
hasWechatDataPath: true,
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("unregister removes row + drops registry entry", async () => {
|
|
195
|
-
await registerWechatViaHub({
|
|
196
|
-
registry,
|
|
197
|
-
hubDir,
|
|
198
|
-
opts: {
|
|
199
|
-
account: { uin: "1234567890" },
|
|
200
|
-
wechatDataPath: dataDir,
|
|
201
|
-
_probe: mkProbe(),
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
expect(registry.has("wechat")).toBe(true);
|
|
205
|
-
|
|
206
|
-
const ur = await unregisterWechatViaHub({
|
|
207
|
-
registry,
|
|
208
|
-
hubDir,
|
|
209
|
-
uin: "1234567890",
|
|
210
|
-
});
|
|
211
|
-
expect(ur).toMatchObject({ ok: true, removed: true });
|
|
212
|
-
expect(registry.has("wechat")).toBe(false);
|
|
213
|
-
expect(listWechatViaHub({ hubDir })).toEqual([]);
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
describe("B. frida happy path — rooted 8.0+ device", () => {
|
|
218
|
-
it("probe='frida' + root yields FridaKeyProvider in persisted row", async () => {
|
|
219
|
-
const r = await registerWechatViaHub({
|
|
220
|
-
registry,
|
|
221
|
-
hubDir,
|
|
222
|
-
opts: {
|
|
223
|
-
account: { uin: "wxid_alice" },
|
|
224
|
-
_probe: mkProbe({
|
|
225
|
-
suggestedKeyProvider: "frida",
|
|
226
|
-
wechat: { installed: true, versionName: "8.0.50", majorVersion: 8 },
|
|
227
|
-
root: { detected: true, magiskInstalled: true },
|
|
228
|
-
frida: { serverRunning: true, port: 27042 },
|
|
229
|
-
reasons: ["WeChat 8.0.50 — Frida hook on libwcdb.so"],
|
|
230
|
-
}),
|
|
231
|
-
},
|
|
232
|
-
});
|
|
233
|
-
expect(r.ok).toBe(true);
|
|
234
|
-
expect(r.chosenKeyProvider).toBe("frida");
|
|
235
|
-
expect(registry.has("wechat")).toBe(true);
|
|
236
|
-
|
|
237
|
-
const list = listWechatViaHub({ hubDir });
|
|
238
|
-
expect(list[0].chosenKeyProvider).toBe("frida");
|
|
239
|
-
// Frida path doesn't require wechatDataPath
|
|
240
|
-
expect(list[0].hasWechatDataPath).toBe(false);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
describe("C. unsupported path", () => {
|
|
245
|
-
it("8.0+ without root → no registry change, no row, ok:false with reasons", async () => {
|
|
246
|
-
const r = await registerWechatViaHub({
|
|
247
|
-
registry,
|
|
248
|
-
hubDir,
|
|
249
|
-
opts: {
|
|
250
|
-
account: { uin: "wxid_bob" },
|
|
251
|
-
_probe: mkProbe({
|
|
252
|
-
ok: false,
|
|
253
|
-
suggestedKeyProvider: "unsupported",
|
|
254
|
-
reasons: [
|
|
255
|
-
"WeChat 8.0.50 requires root for SQLCipher key extraction",
|
|
256
|
-
],
|
|
257
|
-
wechat: { installed: true, versionName: "8.0.50", majorVersion: 8 },
|
|
258
|
-
root: { detected: false, magiskInstalled: false },
|
|
259
|
-
}),
|
|
260
|
-
},
|
|
261
|
-
});
|
|
262
|
-
expect(r.ok).toBe(false);
|
|
263
|
-
expect(r.reason).toBe("ENV_UNSUPPORTED");
|
|
264
|
-
expect(r.probe.reasons.join(" ")).toMatch(/requires root/);
|
|
265
|
-
|
|
266
|
-
expect(registry.has("wechat")).toBe(false);
|
|
267
|
-
expect(listWechatViaHub({ hubDir })).toEqual([]);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it("md5 path missing wechatDataPath → ok:false MD5_NEEDS_WECHAT_DATA_PATH", async () => {
|
|
271
|
-
const r = await registerWechatViaHub({
|
|
272
|
-
registry,
|
|
273
|
-
hubDir,
|
|
274
|
-
opts: {
|
|
275
|
-
account: { uin: "1234567890" },
|
|
276
|
-
_probe: mkProbe(),
|
|
277
|
-
// wechatDataPath intentionally omitted
|
|
278
|
-
},
|
|
279
|
-
});
|
|
280
|
-
expect(r.ok).toBe(false);
|
|
281
|
-
expect(r.reason).toBe("MD5_NEEDS_WECHAT_DATA_PATH");
|
|
282
|
-
expect(registry.has("wechat")).toBe(false);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
describe("D. idempotent re-register", () => {
|
|
287
|
-
it("same uin twice → single row, latest wechatDataPath wins", async () => {
|
|
288
|
-
const dataA = mkdtempSync(join(tmpdir(), "pdh-wechat-dataA-"));
|
|
289
|
-
const dataB = mkdtempSync(join(tmpdir(), "pdh-wechat-dataB-"));
|
|
290
|
-
try {
|
|
291
|
-
await registerWechatViaHub({
|
|
292
|
-
registry,
|
|
293
|
-
hubDir,
|
|
294
|
-
opts: {
|
|
295
|
-
account: { uin: "1234567890" },
|
|
296
|
-
wechatDataPath: dataA,
|
|
297
|
-
_probe: mkProbe(),
|
|
298
|
-
},
|
|
299
|
-
});
|
|
300
|
-
await registerWechatViaHub({
|
|
301
|
-
registry,
|
|
302
|
-
hubDir,
|
|
303
|
-
opts: {
|
|
304
|
-
account: { uin: "1234567890" },
|
|
305
|
-
wechatDataPath: dataB,
|
|
306
|
-
_probe: mkProbe(),
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
const list = listWechatViaHub({ hubDir });
|
|
311
|
-
expect(list).toHaveLength(1);
|
|
312
|
-
expect(list[0].uin).toBe("1234567890");
|
|
313
|
-
expect(registry.has("wechat")).toBe(true);
|
|
314
|
-
|
|
315
|
-
// Adapter's _dbPath is null in both calls (we didn't pass dbPath),
|
|
316
|
-
// but the persisted row uses the latest wechatDataPath.
|
|
317
|
-
const raw = readFileSync(
|
|
318
|
-
join(hubDir, "wechat-accounts.json"),
|
|
319
|
-
"utf-8",
|
|
320
|
-
);
|
|
321
|
-
const persisted = JSON.parse(raw);
|
|
322
|
-
expect(persisted[0].wechatDataPath).toBe(dataB);
|
|
323
|
-
} finally {
|
|
324
|
-
try { rmSync(dataA, { recursive: true, force: true }); } catch (_e) {}
|
|
325
|
-
try { rmSync(dataB, { recursive: true, force: true }); } catch (_e) {}
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it("two distinct uins coexist as separate rows", async () => {
|
|
330
|
-
await registerWechatViaHub({
|
|
331
|
-
registry,
|
|
332
|
-
hubDir,
|
|
333
|
-
opts: { account: { uin: "alice" }, wechatDataPath: dataDir, _probe: mkProbe() },
|
|
334
|
-
});
|
|
335
|
-
await registerWechatViaHub({
|
|
336
|
-
registry,
|
|
337
|
-
hubDir,
|
|
338
|
-
opts: { account: { uin: "bob" }, wechatDataPath: dataDir, _probe: mkProbe() },
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
const list = listWechatViaHub({ hubDir });
|
|
342
|
-
expect(list.map((r) => r.uin).sort()).toEqual(["alice", "bob"]);
|
|
343
|
-
// Single registry slot named "wechat" — second register replaces first
|
|
344
|
-
// adapter instance, but registry still has exactly one entry. This is
|
|
345
|
-
// the v0.5 limit: the registry namespaces by adapter.name not by uin.
|
|
346
|
-
// The persisted accounts file is the source of truth for "which uins
|
|
347
|
-
// can sync"; bootstrap re-runs at sync time per account.
|
|
348
|
-
expect(registry.has("wechat")).toBe(true);
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
describe("override semantics (Phase 12.6.7 §18.10)", () => {
|
|
353
|
-
it("keyProviderOverride='frida' wins over probe='md5'", async () => {
|
|
354
|
-
const r = await registerWechatViaHub({
|
|
355
|
-
registry,
|
|
356
|
-
hubDir,
|
|
357
|
-
opts: {
|
|
358
|
-
account: { uin: "wxid_force" },
|
|
359
|
-
keyProviderOverride: "frida",
|
|
360
|
-
_probe: mkProbe(), // suggests md5
|
|
361
|
-
},
|
|
362
|
-
});
|
|
363
|
-
expect(r.ok).toBe(true);
|
|
364
|
-
expect(r.chosenKeyProvider).toBe("frida");
|
|
365
|
-
// Probe transparency: original suggestion still surfaces unchanged
|
|
366
|
-
expect(r.probe.suggestedKeyProvider).toBe("md5");
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
it("keyProviderOverride='md5' wins over probe='frida'", async () => {
|
|
370
|
-
const r = await registerWechatViaHub({
|
|
371
|
-
registry,
|
|
372
|
-
hubDir,
|
|
373
|
-
opts: {
|
|
374
|
-
account: { uin: "1234567890" },
|
|
375
|
-
wechatDataPath: dataDir,
|
|
376
|
-
keyProviderOverride: "md5",
|
|
377
|
-
_probe: mkProbe({
|
|
378
|
-
suggestedKeyProvider: "frida",
|
|
379
|
-
wechat: { installed: true, versionName: "8.0.50", majorVersion: 8 },
|
|
380
|
-
root: { detected: true, magiskInstalled: true },
|
|
381
|
-
frida: { serverRunning: true, port: 27042 },
|
|
382
|
-
}),
|
|
383
|
-
},
|
|
384
|
-
});
|
|
385
|
-
expect(r.ok).toBe(true);
|
|
386
|
-
expect(r.chosenKeyProvider).toBe("md5");
|
|
387
|
-
expect(r.probe.suggestedKeyProvider).toBe("frida");
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
});
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
4
|
-
|
|
5
|
-
const fs = require("node:fs");
|
|
6
|
-
const os = require("node:os");
|
|
7
|
-
const path = require("node:path");
|
|
8
|
-
const {
|
|
9
|
-
generateKeyHex,
|
|
10
|
-
isValidKeyHex,
|
|
11
|
-
InMemoryKeyProvider,
|
|
12
|
-
FileKeyProvider,
|
|
13
|
-
KEY_HEX_LEN,
|
|
14
|
-
} = require("../lib/key-providers");
|
|
15
|
-
|
|
16
|
-
describe("generateKeyHex / isValidKeyHex", () => {
|
|
17
|
-
it("generateKeyHex returns 64 hex chars", () => {
|
|
18
|
-
const k = generateKeyHex();
|
|
19
|
-
expect(typeof k).toBe("string");
|
|
20
|
-
expect(k.length).toBe(KEY_HEX_LEN);
|
|
21
|
-
expect(/^[0-9a-f]+$/.test(k)).toBe(true);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("generateKeyHex returns unique keys", () => {
|
|
25
|
-
const set = new Set();
|
|
26
|
-
for (let i = 0; i < 50; i++) set.add(generateKeyHex());
|
|
27
|
-
expect(set.size).toBe(50);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("isValidKeyHex accepts valid 64-hex strings", () => {
|
|
31
|
-
expect(isValidKeyHex(generateKeyHex())).toBe(true);
|
|
32
|
-
expect(isValidKeyHex("A".repeat(64))).toBe(true);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("isValidKeyHex rejects wrong length, non-hex, non-string", () => {
|
|
36
|
-
expect(isValidKeyHex("a".repeat(63))).toBe(false);
|
|
37
|
-
expect(isValidKeyHex("a".repeat(65))).toBe(false);
|
|
38
|
-
expect(isValidKeyHex("z".repeat(64))).toBe(false);
|
|
39
|
-
expect(isValidKeyHex(null)).toBe(false);
|
|
40
|
-
expect(isValidKeyHex(undefined)).toBe(false);
|
|
41
|
-
expect(isValidKeyHex(Buffer.alloc(32))).toBe(false);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
describe("InMemoryKeyProvider", () => {
|
|
46
|
-
it("get returns null for unknown name", async () => {
|
|
47
|
-
const p = new InMemoryKeyProvider();
|
|
48
|
-
expect(await p.get("missing")).toBeNull();
|
|
49
|
-
expect(await p.has("missing")).toBe(false);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("set then get round-trip", async () => {
|
|
53
|
-
const p = new InMemoryKeyProvider();
|
|
54
|
-
const k = generateKeyHex();
|
|
55
|
-
await p.set("vault:abc", k);
|
|
56
|
-
expect(await p.has("vault:abc")).toBe(true);
|
|
57
|
-
expect(await p.get("vault:abc")).toBe(k);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("set validates hex shape", async () => {
|
|
61
|
-
const p = new InMemoryKeyProvider();
|
|
62
|
-
await expect(p.set("bad", "notatall")).rejects.toThrow(/hex/);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("del removes the entry", async () => {
|
|
66
|
-
const p = new InMemoryKeyProvider();
|
|
67
|
-
await p.set("vault:abc", generateKeyHex());
|
|
68
|
-
await p.del("vault:abc");
|
|
69
|
-
expect(await p.has("vault:abc")).toBe(false);
|
|
70
|
-
expect(await p.get("vault:abc")).toBeNull();
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe("FileKeyProvider", () => {
|
|
75
|
-
let tmpDir;
|
|
76
|
-
|
|
77
|
-
beforeEach(() => {
|
|
78
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pdh-keytest-"));
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
afterEach(() => {
|
|
82
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("set creates a file under dir; get reads it back", async () => {
|
|
86
|
-
const p = new FileKeyProvider(tmpDir);
|
|
87
|
-
const k = generateKeyHex();
|
|
88
|
-
await p.set("vault:test", k);
|
|
89
|
-
expect(await p.has("vault:test")).toBe(true);
|
|
90
|
-
expect(await p.get("vault:test")).toBe(k);
|
|
91
|
-
// colons get sanitized to underscores in filename
|
|
92
|
-
expect(fs.existsSync(path.join(tmpDir, "vault_test.key"))).toBe(true);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("get returns null for unknown name (does not create file)", async () => {
|
|
96
|
-
const p = new FileKeyProvider(tmpDir);
|
|
97
|
-
expect(await p.get("never-set")).toBeNull();
|
|
98
|
-
expect(fs.readdirSync(tmpDir).length).toBe(0);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("get throws on corrupted stored file", async () => {
|
|
102
|
-
const p = new FileKeyProvider(tmpDir);
|
|
103
|
-
fs.writeFileSync(path.join(tmpDir, "vault_corrupt.key"), "not-real-hex");
|
|
104
|
-
await expect(p.get("vault:corrupt")).rejects.toThrow(/valid hex/);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("del removes the file", async () => {
|
|
108
|
-
const p = new FileKeyProvider(tmpDir);
|
|
109
|
-
await p.set("vault:bye", generateKeyHex());
|
|
110
|
-
await p.del("vault:bye");
|
|
111
|
-
expect(await p.has("vault:bye")).toBe(false);
|
|
112
|
-
expect(fs.existsSync(path.join(tmpDir, "vault_bye.key"))).toBe(false);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("constructor creates missing dir recursively", () => {
|
|
116
|
-
const nested = path.join(tmpDir, "a", "b", "c");
|
|
117
|
-
const p = new FileKeyProvider(nested);
|
|
118
|
-
expect(p.dir).toBe(nested);
|
|
119
|
-
expect(fs.existsSync(nested)).toBe(true);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it("set validates hex shape", async () => {
|
|
123
|
-
const p = new FileKeyProvider(tmpDir);
|
|
124
|
-
await expect(p.set("bad", "xx")).rejects.toThrow(/hex/);
|
|
125
|
-
});
|
|
126
|
-
});
|