@chainlesschain/personal-data-hub 0.3.6 → 0.3.7

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.
Files changed (22) hide show
  1. package/__tests__/adapters/social-kuaishou-adb-api-client.test.js +432 -0
  2. package/__tests__/adapters/social-kuaishou-adb-collector.test.js +276 -0
  3. package/__tests__/adapters/social-kuaishou-adb-cookies-extension.test.js +141 -0
  4. package/__tests__/adapters/social-kuaishou-adb-snapshot-builder.test.js +178 -0
  5. package/__tests__/adapters/social-toutiao-adb-api-client.test.js +537 -0
  6. package/__tests__/adapters/social-toutiao-adb-collector.test.js +285 -0
  7. package/__tests__/adapters/social-toutiao-adb-cookies-extension.test.js +163 -0
  8. package/__tests__/adapters/social-toutiao-adb-snapshot-builder.test.js +196 -0
  9. package/__tests__/adapters/social-xiaohongshu-adb-sign-provider-injection.test.js +351 -0
  10. package/lib/adapters/social-kuaishou-adb/api-client.js +397 -0
  11. package/lib/adapters/social-kuaishou-adb/collector.js +196 -0
  12. package/lib/adapters/social-kuaishou-adb/cookies-extension.js +261 -0
  13. package/lib/adapters/social-kuaishou-adb/index.js +53 -0
  14. package/lib/adapters/social-kuaishou-adb/snapshot-builder.js +145 -0
  15. package/lib/adapters/social-toutiao-adb/api-client.js +377 -0
  16. package/lib/adapters/social-toutiao-adb/collector.js +200 -0
  17. package/lib/adapters/social-toutiao-adb/cookies-extension.js +266 -0
  18. package/lib/adapters/social-toutiao-adb/index.js +52 -0
  19. package/lib/adapters/social-toutiao-adb/snapshot-builder.js +148 -0
  20. package/lib/adapters/social-xiaohongshu-adb/api-client.js +36 -5
  21. package/lib/adapters/social-xiaohongshu-adb/collector.js +102 -51
  22. package/package.json +5 -1
@@ -36,7 +36,12 @@ async function collect(bridge, opts = {}) {
36
36
  );
37
37
  }
38
38
  const now = opts.now || Date.now;
39
- const client = opts.apiClient || new XhsApiClient({ now });
39
+ // Phase 6b: signProvider opt desktop wiring injects XhsSignBridge for
40
+ // ~100% X-S hit rate; cli wiring leaves undefined → client falls back
41
+ // to in-process best-effort md5 (~60% GET / <30% POST).
42
+ const signProvider = opts.signProvider || undefined;
43
+ const client =
44
+ opts.apiClient || new XhsApiClient({ now, signProvider });
40
45
  const limits = opts.limits || {};
41
46
 
42
47
  const cookieResult = await bridge.invoke("xhs.cookies");
@@ -54,67 +59,108 @@ async function collect(bridge, opts = {}) {
54
59
  }
55
60
  const { cookie, a1, diagnostic: cookieDiagnostic } = cookieResult;
56
61
 
57
- // fetchMe no X-S required
58
- const me = await client.fetchMe(cookie);
59
- if (!me) {
60
- // Cookie expired or web_session missing — write empty snapshot
61
- // (build requires userId, use sentinel "0" + emit 0 events).
62
+ // Phase 6b: warm up the sign bridge with the captured cookie BEFORE
63
+ // calling any X-S endpoint. warmUp is idempotent (no-op when already
64
+ // warm). NullSignProvider.warmUp doesn't exist (only on the abstract
65
+ // base + ElectronWebSignBridge), so we feature-detect.
66
+ if (signProvider && typeof signProvider.warmUp === "function") {
67
+ try {
68
+ await signProvider.warmUp(cookie);
69
+ } catch (e) {
70
+ // Bridge warm-up failed (timeout / xhs.com 403 / IPC error).
71
+ // Fall through — api-client will use in-process fallback. Surface
72
+ // the reason via lastErrorMessage so UI can hint "Electron bridge
73
+ // unavailable, command-line precision degraded".
74
+ client._setLastError(
75
+ -98,
76
+ `signProvider warm-up failed: ${e && e.message ? e.message : String(e)}`,
77
+ );
78
+ }
79
+ }
80
+
81
+ try {
82
+ // fetchMe — no X-S required
83
+ const me = await client.fetchMe(cookie);
84
+ if (!me) {
85
+ // Cookie expired or web_session missing — write empty snapshot
86
+ // (build requires userId, use sentinel "0" + emit 0 events).
87
+ const snapshot = buildSnapshot({
88
+ userId: "unknown-user",
89
+ nickname: opts.displayName,
90
+ snapshottedAt: now(),
91
+ });
92
+ const snapshotPath = writeSnapshotJson(snapshot, { dir: opts.stagingDir });
93
+ return {
94
+ snapshotPath,
95
+ userId: null,
96
+ nickname: null,
97
+ eventCounts: { note: 0, liked: 0, follow: 0, total: 0 },
98
+ lastErrorCode: client.lastErrorCode,
99
+ lastErrorMessage: client.lastErrorMessage,
100
+ cookieDiagnostic: cookieDiagnostic || null,
101
+ meFetchFailed: true,
102
+ signProviderUsed: signProvider
103
+ ? signProvider.constructor.name
104
+ : "none",
105
+ signProviderHits: client._bridgeHits,
106
+ signProviderFallbacks: client._fallbackHits,
107
+ };
108
+ }
109
+
110
+ // Parallel 3 endpoints — partial failure tolerated; bridge-signed
111
+ // requests should hit ~100% while fallback hits ~60% GET / <30% POST.
112
+ const [notes, liked, follows] = await Promise.all([
113
+ client.fetchNotes(cookie, a1, me.userId, {
114
+ limit: Number.isInteger(limits.note) ? limits.note : undefined,
115
+ }),
116
+ client.fetchLiked(cookie, a1, {
117
+ limit: Number.isInteger(limits.liked) ? limits.liked : undefined,
118
+ }),
119
+ client.fetchFollows(cookie, a1, me.userId, {
120
+ limit: Number.isInteger(limits.follow) ? limits.follow : undefined,
121
+ }),
122
+ ]);
123
+
62
124
  const snapshot = buildSnapshot({
63
- userId: "unknown-user",
64
- nickname: opts.displayName,
125
+ userId: me.userId,
126
+ nickname: opts.displayName || me.nickname,
127
+ notes,
128
+ liked,
129
+ follows,
65
130
  snapshottedAt: now(),
66
131
  });
67
132
  const snapshotPath = writeSnapshotJson(snapshot, { dir: opts.stagingDir });
133
+
68
134
  return {
69
135
  snapshotPath,
70
- userId: null,
71
- nickname: null,
72
- eventCounts: { note: 0, liked: 0, follow: 0, total: 0 },
136
+ userId: me.userId,
137
+ nickname: me.nickname,
138
+ eventCounts: {
139
+ note: notes.length,
140
+ liked: liked.length,
141
+ follow: follows.length,
142
+ total: snapshot.events.length,
143
+ },
73
144
  lastErrorCode: client.lastErrorCode,
74
145
  lastErrorMessage: client.lastErrorMessage,
75
146
  cookieDiagnostic: cookieDiagnostic || null,
76
- meFetchFailed: true,
147
+ meFetchFailed: false,
148
+ signProviderUsed: signProvider ? signProvider.constructor.name : "none",
149
+ signProviderHits: client._bridgeHits,
150
+ signProviderFallbacks: client._fallbackHits,
77
151
  };
152
+ } finally {
153
+ // Always release the WebContentsView heap (~30-50MB) — even on
154
+ // throw. shutdown is idempotent so collectAndSync's outer cleanup
155
+ // calling it again is safe.
156
+ if (signProvider && typeof signProvider.shutdown === "function") {
157
+ try {
158
+ await signProvider.shutdown();
159
+ } catch (_e) {
160
+ // Best-effort — shutdown errors don't block sync result.
161
+ }
162
+ }
78
163
  }
79
-
80
- // Parallel 3 endpoints — partial failure tolerated (~60% X-S hit rate)
81
- const [notes, liked, follows] = await Promise.all([
82
- client.fetchNotes(cookie, a1, me.userId, {
83
- limit: Number.isInteger(limits.note) ? limits.note : undefined,
84
- }),
85
- client.fetchLiked(cookie, a1, {
86
- limit: Number.isInteger(limits.liked) ? limits.liked : undefined,
87
- }),
88
- client.fetchFollows(cookie, a1, me.userId, {
89
- limit: Number.isInteger(limits.follow) ? limits.follow : undefined,
90
- }),
91
- ]);
92
-
93
- const snapshot = buildSnapshot({
94
- userId: me.userId,
95
- nickname: opts.displayName || me.nickname,
96
- notes,
97
- liked,
98
- follows,
99
- snapshottedAt: now(),
100
- });
101
- const snapshotPath = writeSnapshotJson(snapshot, { dir: opts.stagingDir });
102
-
103
- return {
104
- snapshotPath,
105
- userId: me.userId,
106
- nickname: me.nickname,
107
- eventCounts: {
108
- note: notes.length,
109
- liked: liked.length,
110
- follow: follows.length,
111
- total: snapshot.events.length,
112
- },
113
- lastErrorCode: client.lastErrorCode,
114
- lastErrorMessage: client.lastErrorMessage,
115
- cookieDiagnostic: cookieDiagnostic || null,
116
- meFetchFailed: false,
117
- };
118
164
  }
119
165
 
120
166
  async function collectAndSync(bridge, registry, opts = {}) {
@@ -147,6 +193,11 @@ async function collectAndSync(bridge, registry, opts = {}) {
147
193
  lastErrorMessage: collectResult.lastErrorMessage,
148
194
  cookieDiagnostic: collectResult.cookieDiagnostic,
149
195
  meFetchFailed: collectResult.meFetchFailed,
196
+ // Phase 6b diagnostic — UI can highlight when bridge upgraded
197
+ // X-S signing from ~60% best-effort to ~100% bridge.
198
+ signProviderUsed: collectResult.signProviderUsed,
199
+ signProviderHits: collectResult.signProviderHits,
200
+ signProviderFallbacks: collectResult.signProviderFallbacks,
150
201
  cleanupFailed,
151
202
  },
152
203
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chainlesschain/personal-data-hub",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Personal Data Hub — UnifiedSchema + validators + KG ingest helpers for the data-back-to-the-individual middleware",
5
5
  "type": "commonjs",
6
6
  "main": "lib/index.js",
@@ -61,6 +61,10 @@
61
61
  "./adapters/social-douyin-adb": "./lib/adapters/social-douyin-adb/index.js",
62
62
  "./adapters/social-xiaohongshu": "./lib/adapters/social-xiaohongshu/index.js",
63
63
  "./adapters/social-xiaohongshu-adb": "./lib/adapters/social-xiaohongshu-adb/index.js",
64
+ "./adapters/social-toutiao": "./lib/adapters/social-toutiao/index.js",
65
+ "./adapters/social-toutiao-adb": "./lib/adapters/social-toutiao-adb/index.js",
66
+ "./adapters/social-kuaishou": "./lib/adapters/social-kuaishou/index.js",
67
+ "./adapters/social-kuaishou-adb": "./lib/adapters/social-kuaishou-adb/index.js",
64
68
  "./adapters/messaging-qq": "./lib/adapters/messaging-qq/index.js",
65
69
  "./adapters/messaging-telegram": "./lib/adapters/messaging-telegram/index.js",
66
70
  "./adapters/messaging-whatsapp": "./lib/adapters/messaging-whatsapp/index.js",