fca-phantom 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.
Files changed (121) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +534 -0
  3. package/index.js +35 -0
  4. package/package.json +101 -0
  5. package/phantom/core/builder/bootstrap.js +334 -0
  6. package/phantom/core/builder/config.js +78 -0
  7. package/phantom/core/builder/forge.js +113 -0
  8. package/phantom/core/builder/ignite.js +386 -0
  9. package/phantom/core/builder/options.js +61 -0
  10. package/phantom/core/engine.js +71 -0
  11. package/phantom/core/reactor.js +2 -0
  12. package/phantom/datastore/appState.js +2 -0
  13. package/phantom/datastore/appStateBackup.js +34 -0
  14. package/phantom/datastore/models/cipher/e2ee.js +48 -0
  15. package/phantom/datastore/models/cipher/vault.js +153 -0
  16. package/phantom/datastore/models/index.js +3 -0
  17. package/phantom/datastore/models/matrix/auth.js +151 -0
  18. package/phantom/datastore/models/matrix/cache.js +3 -0
  19. package/phantom/datastore/models/matrix/checker.js +2 -0
  20. package/phantom/datastore/models/matrix/clients.js +2 -0
  21. package/phantom/datastore/models/matrix/constants.js +2 -0
  22. package/phantom/datastore/models/matrix/credentials.js +2 -0
  23. package/phantom/datastore/models/matrix/cycle.js +2 -0
  24. package/phantom/datastore/models/matrix/gate.js +282 -0
  25. package/phantom/datastore/models/matrix/ghost.js +332 -0
  26. package/phantom/datastore/models/matrix/headers.js +193 -0
  27. package/phantom/datastore/models/matrix/heartbeat.js +298 -0
  28. package/phantom/datastore/models/matrix/identity.js +235 -0
  29. package/phantom/datastore/models/matrix/logger.js +271 -0
  30. package/phantom/datastore/models/matrix/monitor.js +2 -0
  31. package/phantom/datastore/models/matrix/net.js +316 -0
  32. package/phantom/datastore/models/matrix/response.js +193 -0
  33. package/phantom/datastore/models/matrix/revive.js +255 -0
  34. package/phantom/datastore/models/matrix/signals.js +2 -0
  35. package/phantom/datastore/models/matrix/store.js +263 -0
  36. package/phantom/datastore/models/matrix/telemetry.js +272 -0
  37. package/phantom/datastore/models/matrix/tools.js +93 -0
  38. package/phantom/datastore/models/matrix/transform/cookieParser.js +2 -0
  39. package/phantom/datastore/models/matrix/transform/cookies.js +114 -0
  40. package/phantom/datastore/models/matrix/transform/index.js +203 -0
  41. package/phantom/datastore/models/matrix/validator.js +157 -0
  42. package/phantom/datastore/models/types/index.d.ts +498 -0
  43. package/phantom/datastore/schema.js +167 -0
  44. package/phantom/datastore/session.js +129 -0
  45. package/phantom/datastore/threads.js +22 -0
  46. package/phantom/datastore/users.js +26 -0
  47. package/phantom/dispatch/addExternalModule.js +239 -0
  48. package/phantom/dispatch/addUserToGroup.js +161 -0
  49. package/phantom/dispatch/changeAdminStatus.js +142 -0
  50. package/phantom/dispatch/changeArchivedStatus.js +135 -0
  51. package/phantom/dispatch/changeAvatar.js +123 -0
  52. package/phantom/dispatch/changeBio.js +86 -0
  53. package/phantom/dispatch/changeBlockedStatus.js +86 -0
  54. package/phantom/dispatch/changeGroupImage.js +145 -0
  55. package/phantom/dispatch/changeThreadColor.js +172 -0
  56. package/phantom/dispatch/changeThreadEmoji.js +130 -0
  57. package/phantom/dispatch/comment.js +136 -0
  58. package/phantom/dispatch/createAITheme.js +333 -0
  59. package/phantom/dispatch/createNewGroup.js +99 -0
  60. package/phantom/dispatch/createPoll.js +148 -0
  61. package/phantom/dispatch/deleteMessage.js +131 -0
  62. package/phantom/dispatch/deleteThread.js +155 -0
  63. package/phantom/dispatch/e2ee.js +101 -0
  64. package/phantom/dispatch/editMessage.js +158 -0
  65. package/phantom/dispatch/emoji.js +143 -0
  66. package/phantom/dispatch/fetchThemeData.js +233 -0
  67. package/phantom/dispatch/follow.js +111 -0
  68. package/phantom/dispatch/forwardMessage.js +110 -0
  69. package/phantom/dispatch/friend.js +189 -0
  70. package/phantom/dispatch/gcmember.js +138 -0
  71. package/phantom/dispatch/gcname.js +131 -0
  72. package/phantom/dispatch/gcrule.js +111 -0
  73. package/phantom/dispatch/getAccess.js +109 -0
  74. package/phantom/dispatch/getBotInfo.js +81 -0
  75. package/phantom/dispatch/getBotInitialData.js +110 -0
  76. package/phantom/dispatch/getFriendsList.js +118 -0
  77. package/phantom/dispatch/getMessage.js +199 -0
  78. package/phantom/dispatch/getTheme.js +199 -0
  79. package/phantom/dispatch/getThemeInfo.js +160 -0
  80. package/phantom/dispatch/getThreadHistory.js +139 -0
  81. package/phantom/dispatch/getThreadInfo.js +153 -0
  82. package/phantom/dispatch/getThreadList.js +132 -0
  83. package/phantom/dispatch/getThreadPictures.js +93 -0
  84. package/phantom/dispatch/getUserID.js +147 -0
  85. package/phantom/dispatch/getUserInfo.js +513 -0
  86. package/phantom/dispatch/getUserInfoV2.js +146 -0
  87. package/phantom/dispatch/handleMessageRequest.js +50 -0
  88. package/phantom/dispatch/httpGet.js +63 -0
  89. package/phantom/dispatch/httpPost.js +89 -0
  90. package/phantom/dispatch/httpPostFormData.js +69 -0
  91. package/phantom/dispatch/listenMqtt.js +1236 -0
  92. package/phantom/dispatch/listenSpeed.js +179 -0
  93. package/phantom/dispatch/logout.js +93 -0
  94. package/phantom/dispatch/markAsDelivered.js +92 -0
  95. package/phantom/dispatch/markAsRead.js +119 -0
  96. package/phantom/dispatch/markAsReadAll.js +215 -0
  97. package/phantom/dispatch/markAsSeen.js +70 -0
  98. package/phantom/dispatch/mqttDeltaValue.js +278 -0
  99. package/phantom/dispatch/muteThread.js +253 -0
  100. package/phantom/dispatch/nickname.js +132 -0
  101. package/phantom/dispatch/notes.js +263 -0
  102. package/phantom/dispatch/pinMessage.js +238 -0
  103. package/phantom/dispatch/produceMetaTheme.js +335 -0
  104. package/phantom/dispatch/realtime.js +291 -0
  105. package/phantom/dispatch/removeUserFromGroup.js +248 -0
  106. package/phantom/dispatch/resolvePhotoUrl.js +217 -0
  107. package/phantom/dispatch/searchForThread.js +258 -0
  108. package/phantom/dispatch/sendMessage.js +354 -0
  109. package/phantom/dispatch/sendMessageMqtt.js +249 -0
  110. package/phantom/dispatch/sendTypingIndicator.js +206 -0
  111. package/phantom/dispatch/setMessageReaction.js +188 -0
  112. package/phantom/dispatch/setMessageReactionMqtt.js +248 -0
  113. package/phantom/dispatch/setThreadTheme.js +330 -0
  114. package/phantom/dispatch/setThreadThemeMqtt.js +207 -0
  115. package/phantom/dispatch/share.js +200 -0
  116. package/phantom/dispatch/shareContact.js +216 -0
  117. package/phantom/dispatch/stickers.js +395 -0
  118. package/phantom/dispatch/story.js +240 -0
  119. package/phantom/dispatch/theme.js +296 -0
  120. package/phantom/dispatch/unfriend.js +199 -0
  121. package/phantom/dispatch/unsendMessage.js +124 -0
@@ -0,0 +1,263 @@
1
+ "use strict";
2
+
3
+ class LRUNode {
4
+ constructor(key, value, ttl) {
5
+ this.key = key;
6
+ this.value = value;
7
+ this.exp = ttl > 0 ? Date.now() + ttl : Infinity;
8
+ this.hits = 0;
9
+ this.prev = null;
10
+ this.next = null;
11
+ }
12
+ isExpired() { return Date.now() > this.exp; }
13
+ }
14
+
15
+ class DataStore {
16
+ constructor(opts = {}) {
17
+ this._maxSize = opts.maxSize || 1000;
18
+ this._defaultTTL= opts.ttl || 0;
19
+ this._map = new Map();
20
+ this._head = null;
21
+ this._tail = null;
22
+ this._hits = 0;
23
+ this._misses = 0;
24
+ this._evictions = 0;
25
+ this._namespace = opts.namespace || "default";
26
+ this._listeners = new Map();
27
+ this._cleanupInterval = null;
28
+ if (opts.autoClean !== false) {
29
+ this._cleanupInterval = setInterval(() => this._sweep(), 60_000);
30
+ this._cleanupInterval.unref?.();
31
+ }
32
+ }
33
+
34
+ _unlink(node) {
35
+ if (node.prev) node.prev.next = node.next; else this._head = node.next;
36
+ if (node.next) node.next.prev = node.prev; else this._tail = node.prev;
37
+ node.prev = node.next = null;
38
+ }
39
+
40
+ _prepend(node) {
41
+ node.next = this._head;
42
+ node.prev = null;
43
+ if (this._head) this._head.prev = node;
44
+ this._head = node;
45
+ if (!this._tail) this._tail = node;
46
+ }
47
+
48
+ _evict() {
49
+ while (this._map.size >= this._maxSize && this._tail) {
50
+ const lru = this._tail;
51
+ this._unlink(lru);
52
+ this._map.delete(lru.key);
53
+ this._evictions++;
54
+ this._emit("evict", lru.key, lru.value);
55
+ }
56
+ }
57
+
58
+ _sweep() {
59
+ const now = Date.now();
60
+ for (const [k, node] of this._map) {
61
+ if (node.isExpired()) {
62
+ this._unlink(node);
63
+ this._map.delete(k);
64
+ this._emit("expire", k, node.value);
65
+ }
66
+ }
67
+ }
68
+
69
+ _emit(event, ...args) {
70
+ const fns = this._listeners.get(event);
71
+ if (fns) fns.forEach(fn => { try { fn(...args); } catch (_) {} });
72
+ }
73
+
74
+ on(event, fn) {
75
+ if (!this._listeners.has(event)) this._listeners.set(event, []);
76
+ this._listeners.get(event).push(fn);
77
+ return this;
78
+ }
79
+
80
+ set(key, value, ttl = null) {
81
+ const k = String(key);
82
+ const resolvedTTL = ttl != null ? ttl : this._defaultTTL;
83
+ if (this._map.has(k)) {
84
+ const node = this._map.get(k);
85
+ node.value = value;
86
+ node.exp = resolvedTTL > 0 ? Date.now() + resolvedTTL : Infinity;
87
+ node.hits = 0;
88
+ this._unlink(node);
89
+ this._prepend(node);
90
+ return this;
91
+ }
92
+ this._evict();
93
+ const node = new LRUNode(k, value, resolvedTTL);
94
+ this._map.set(k, node);
95
+ this._prepend(node);
96
+ this._emit("set", k, value);
97
+ return this;
98
+ }
99
+
100
+ get(key) {
101
+ const k = String(key);
102
+ const node = this._map.get(k);
103
+ if (!node) { this._misses++; return undefined; }
104
+ if (node.isExpired()) {
105
+ this._unlink(node);
106
+ this._map.delete(k);
107
+ this._misses++;
108
+ this._emit("expire", k, node.value);
109
+ return undefined;
110
+ }
111
+ this._hits++;
112
+ node.hits++;
113
+ this._unlink(node);
114
+ this._prepend(node);
115
+ return node.value;
116
+ }
117
+
118
+ getOrSet(key, factory, ttl = null) {
119
+ const existing = this.get(key);
120
+ if (existing !== undefined) return existing;
121
+ const value = typeof factory === "function" ? factory() : factory;
122
+ this.set(key, value, ttl);
123
+ return value;
124
+ }
125
+
126
+ async getOrSetAsync(key, asyncFactory, ttl = null) {
127
+ const existing = this.get(key);
128
+ if (existing !== undefined) return existing;
129
+ const value = await asyncFactory();
130
+ this.set(key, value, ttl);
131
+ return value;
132
+ }
133
+
134
+ has(key) { return this.get(String(key)) !== undefined; }
135
+
136
+ del(key) {
137
+ const k = String(key);
138
+ const node = this._map.get(k);
139
+ if (!node) return false;
140
+ this._unlink(node);
141
+ this._map.delete(k);
142
+ this._emit("del", k, node.value);
143
+ return true;
144
+ }
145
+
146
+ touch(key, ttl = null) {
147
+ const node = this._map.get(String(key));
148
+ if (!node || node.isExpired()) return false;
149
+ const resolvedTTL = ttl != null ? ttl : this._defaultTTL;
150
+ node.exp = resolvedTTL > 0 ? Date.now() + resolvedTTL : Infinity;
151
+ return true;
152
+ }
153
+
154
+ mget(keys) {
155
+ return keys.map(k => this.get(k));
156
+ }
157
+
158
+ mset(entries, ttl = null) {
159
+ for (const [k, v] of entries) this.set(k, v, ttl);
160
+ return this;
161
+ }
162
+
163
+ keys() {
164
+ const out = [];
165
+ for (const [k, node] of this._map) {
166
+ if (!node.isExpired()) out.push(k);
167
+ }
168
+ return out;
169
+ }
170
+
171
+ values() {
172
+ const out = [];
173
+ for (const [, node] of this._map) {
174
+ if (!node.isExpired()) out.push(node.value);
175
+ }
176
+ return out;
177
+ }
178
+
179
+ entries() {
180
+ const out = [];
181
+ for (const [k, node] of this._map) {
182
+ if (!node.isExpired()) out.push([k, node.value]);
183
+ }
184
+ return out;
185
+ }
186
+
187
+ clear() {
188
+ this._map.clear();
189
+ this._head = this._tail = null;
190
+ this._emit("clear");
191
+ }
192
+
193
+ destroy() {
194
+ if (this._cleanupInterval) clearInterval(this._cleanupInterval);
195
+ this._map.clear();
196
+ this._listeners.clear();
197
+ }
198
+
199
+ get size() {
200
+ this._sweep();
201
+ return this._map.size;
202
+ }
203
+
204
+ stats() {
205
+ const total = this._hits + this._misses;
206
+ return {
207
+ namespace: this._namespace,
208
+ size: this._map.size,
209
+ maxSize: this._maxSize,
210
+ hits: this._hits,
211
+ misses: this._misses,
212
+ evictions: this._evictions,
213
+ hitRate: total > 0 ? ((this._hits / total) * 100).toFixed(1) + "%" : "N/A",
214
+ };
215
+ }
216
+ }
217
+
218
+ class StoreManager {
219
+ constructor() {
220
+ this._stores = new Map();
221
+ this._default = this._make("default", { maxSize: 1000, ttl: 300_000 });
222
+ }
223
+
224
+ _make(ns, opts) {
225
+ const store = new DataStore({ ...opts, namespace: ns });
226
+ this._stores.set(ns, store);
227
+ return store;
228
+ }
229
+
230
+ ns(namespace, opts = {}) {
231
+ if (this._stores.has(namespace)) return this._stores.get(namespace);
232
+ return this._make(namespace, opts);
233
+ }
234
+
235
+ set(k, v, ttl) { return this._default.set(k, v, ttl); }
236
+ get(k) { return this._default.get(k); }
237
+ has(k) { return this._default.has(k); }
238
+ del(k) { return this._default.del(k); }
239
+ clear() { return this._default.clear(); }
240
+
241
+ allStats() {
242
+ const out = {};
243
+ for (const [ns, store] of this._stores) out[ns] = store.stats();
244
+ return out;
245
+ }
246
+
247
+ destroyAll() {
248
+ for (const store of this._stores.values()) store.destroy();
249
+ this._stores.clear();
250
+ }
251
+ }
252
+
253
+ const globalCache = new DataStore({ maxSize: 1000, ttl: 300_000, namespace: "global" });
254
+ const globalStore = new StoreManager();
255
+
256
+ module.exports = {
257
+ DataStore,
258
+ StoreManager,
259
+ globalCache,
260
+ globalStore,
261
+ DataCache: DataStore,
262
+ SessionCache: DataStore,
263
+ };
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+
3
+ const { warn, log } = require("./logger");
4
+
5
+ const DEFAULT_ALERTS = {
6
+ errorRate: { threshold: 5, unit: "%", label: "Error Rate" },
7
+ avgLatency: { threshold: 2000, unit: "ms", label: "Avg Latency" },
8
+ p95Latency: { threshold: 5000, unit: "ms", label: "P95 Latency" },
9
+ rateLimitHits:{ threshold: 50, unit: "", label: "Rate Limit Hits" },
10
+ };
11
+
12
+ function percentile(sorted, p) {
13
+ if (!sorted.length) return 0;
14
+ const idx = Math.ceil((p / 100) * sorted.length) - 1;
15
+ return sorted[Math.min(idx, sorted.length - 1)];
16
+ }
17
+
18
+ class Telemetry {
19
+ constructor() {
20
+ this._start = Date.now();
21
+ this._timer = null;
22
+ this._alerts = new Map(Object.entries(DEFAULT_ALERTS));
23
+ this._alertCbs= new Map();
24
+
25
+ this.req = {
26
+ total: 0, ok: 0, fail: 0,
27
+ byEndpoint: new Map(),
28
+ };
29
+
30
+ this.errs = {
31
+ total: 0,
32
+ byType: new Map(),
33
+ byCode: new Map(),
34
+ recent: [],
35
+ maxRecent: 200,
36
+ };
37
+
38
+ this.perf = {
39
+ times: [],
40
+ maxTimes: 2000,
41
+ slow: [],
42
+ maxSlow: 100,
43
+ threshold: 4000,
44
+ };
45
+
46
+ this.sess = {
47
+ loginAt: null,
48
+ lastAct: null,
49
+ refreshes: 0,
50
+ reconnects: 0,
51
+ revives: 0,
52
+ };
53
+
54
+ this.gate = {
55
+ hits: 0,
56
+ cooldowns: 0,
57
+ delayed: 0,
58
+ throttled: 0,
59
+ };
60
+
61
+ this._timeSeries = [];
62
+ this._maxSeries = 60;
63
+ this._seriesTimer = setInterval(() => this._snapSeries(), 60_000);
64
+ try { this._seriesTimer.unref(); } catch (_) {}
65
+ }
66
+
67
+ _snapSeries() {
68
+ const m = this.getMetrics();
69
+ this._timeSeries.push({
70
+ at: Date.now(),
71
+ reqs: m.requests.total,
72
+ ok: m.requests.ok,
73
+ fail: m.requests.fail,
74
+ avgMs: m.perf.avgMs,
75
+ errTotal: m.errors.total,
76
+ });
77
+ if (this._timeSeries.length > this._maxSeries) this._timeSeries.shift();
78
+ this._checkAlerts(m);
79
+ }
80
+
81
+ _checkAlerts(metrics) {
82
+ const errRate = metrics.requests.total > 0
83
+ ? (metrics.errors.total / metrics.requests.total) * 100 : 0;
84
+
85
+ const checks = {
86
+ errorRate: errRate,
87
+ avgLatency: metrics.perf.avgMs,
88
+ p95Latency: metrics.perf.p95,
89
+ rateLimitHits:this.gate.hits,
90
+ };
91
+
92
+ for (const [key, cfg] of this._alerts) {
93
+ const val = checks[key] ?? 0;
94
+ if (val > cfg.threshold) {
95
+ const msg = `ALERT: ${cfg.label} = ${val.toFixed ? val.toFixed(1) : val}${cfg.unit} (threshold: ${cfg.threshold}${cfg.unit})`;
96
+ warn("Telemetry", msg);
97
+ const cb = this._alertCbs.get(key);
98
+ if (cb) try { cb({ key, value: val, threshold: cfg.threshold, metric: metrics }); } catch (_) {}
99
+ }
100
+ }
101
+ }
102
+
103
+ setAlert(key, threshold, unit = "", label = "", cb = null) {
104
+ this._alerts.set(key, { threshold, unit, label: label || key });
105
+ if (cb) this._alertCbs.set(key, cb);
106
+ }
107
+
108
+ onAlert(key, cb) { this._alertCbs.set(key, cb); }
109
+
110
+ record(endpoint, ok, ms, err = null) {
111
+ this.req.total++;
112
+ ok ? this.req.ok++ : this.req.fail++;
113
+
114
+ const ep = this.req.byEndpoint.get(endpoint) || {
115
+ total: 0, ok: 0, fail: 0, ms: [], avgMs: 0,
116
+ };
117
+ ep.total++; ok ? ep.ok++ : ep.fail++;
118
+ ep.ms.push(ms);
119
+ if (ep.ms.length > 200) ep.ms.shift();
120
+ ep.avgMs = ep.ms.reduce((a, b) => a + b, 0) / ep.ms.length;
121
+ this.req.byEndpoint.set(endpoint, ep);
122
+
123
+ this.perf.times.push(ms);
124
+ if (this.perf.times.length > this.perf.maxTimes) this.perf.times.shift();
125
+
126
+ if (ms > this.perf.threshold) {
127
+ this.perf.slow.push({ endpoint, ms, at: Date.now() });
128
+ if (this.perf.slow.length > this.perf.maxSlow) this.perf.slow.shift();
129
+ }
130
+
131
+ if (err) this._recordErr(err, endpoint);
132
+ this.sess.lastAct = Date.now();
133
+ }
134
+
135
+ _recordErr(err, ctx = "") {
136
+ this.errs.total++;
137
+ const type = err.errorType || err.name || "Unknown";
138
+ const code = err.errorCode || err.code || "N/A";
139
+ this.errs.byType.set(type, (this.errs.byType.get(type) || 0) + 1);
140
+ this.errs.byCode.set(code, (this.errs.byCode.get(code) || 0) + 1);
141
+ this.errs.recent.push({ type, code, msg: err.message, ctx, at: Date.now() });
142
+ if (this.errs.recent.length > this.errs.maxRecent) this.errs.recent.shift();
143
+ }
144
+
145
+ trackGate(type) {
146
+ this.gate.hits++;
147
+ if (type === "cooldown") this.gate.cooldowns++;
148
+ else if (type === "delayed") this.gate.delayed++;
149
+ else if (type === "throttled") this.gate.throttled++;
150
+ }
151
+
152
+ trackRefresh() { this.sess.refreshes++; }
153
+ trackReconnect(){ this.sess.reconnects++; }
154
+ trackRevive() { this.sess.revives++; }
155
+ markLogin() { this.sess.loginAt = Date.now(); }
156
+
157
+ _computePerf() {
158
+ if (!this.perf.times.length) return { avgMs: 0, p50: 0, p75: 0, p95: 0, p99: 0, max: 0 };
159
+ const sorted = [...this.perf.times].sort((a, b) => a - b);
160
+ return {
161
+ avgMs: Math.round(sorted.reduce((a, b) => a + b, 0) / sorted.length),
162
+ p50: percentile(sorted, 50),
163
+ p75: percentile(sorted, 75),
164
+ p95: percentile(sorted, 95),
165
+ p99: percentile(sorted, 99),
166
+ max: sorted[sorted.length - 1],
167
+ };
168
+ }
169
+
170
+ getMetrics() {
171
+ const perf = this._computePerf();
172
+ return {
173
+ uptime: Date.now() - this._start,
174
+ sessionMs: this.sess.loginAt ? Date.now() - this.sess.loginAt : 0,
175
+ requests: {
176
+ ...this.req,
177
+ byEndpoint: Object.fromEntries(
178
+ [...this.req.byEndpoint].map(([k, v]) => [k, { ...v, ms: undefined }])
179
+ ),
180
+ successRate: this.req.total
181
+ ? ((this.req.ok / this.req.total) * 100).toFixed(2) + "%"
182
+ : "N/A",
183
+ },
184
+ errors: {
185
+ ...this.errs,
186
+ byType: Object.fromEntries(this.errs.byType),
187
+ byCode: Object.fromEntries(this.errs.byCode),
188
+ },
189
+ perf: {
190
+ ...perf,
191
+ slowRequests: this.perf.slow.slice(-20),
192
+ },
193
+ session: this.sess,
194
+ gate: this.gate,
195
+ };
196
+ }
197
+
198
+ getHealth() {
199
+ const m = this.getMetrics();
200
+ const errRate = m.requests.total > 0 ? (m.errors.total / m.requests.total) * 100 : 0;
201
+ const checks = {
202
+ errorRate: { ok: errRate < 5, label: "Error Rate", value: errRate.toFixed(2) + "%" },
203
+ avgLatency: { ok: m.perf.avgMs < 2000, label: "Avg Latency", value: m.perf.avgMs + "ms" },
204
+ p95Latency: { ok: m.perf.p95 < 5000, label: "P95 Latency", value: m.perf.p95 + "ms" },
205
+ session: { ok: !!m.session.loginAt, label: "Session", value: m.session.loginAt ? "active" : "inactive" },
206
+ rateLimiting:{ ok: m.gate.hits < 100, label: "Rate Limiting",value: m.gate.hits + " hits" },
207
+ };
208
+ const failing = Object.values(checks).filter(c => !c.ok).length;
209
+ return {
210
+ status: failing >= 3 ? "unhealthy" : failing > 0 ? "degraded" : "healthy",
211
+ score: Math.round(((Object.values(checks).length - failing) / Object.values(checks).length) * 100),
212
+ checks,
213
+ at: Date.now(),
214
+ };
215
+ }
216
+
217
+ getTimeSeries() { return [...this._timeSeries]; }
218
+
219
+ topEndpoints(n = 5) {
220
+ return [...this.req.byEndpoint.entries()]
221
+ .sort(([, a], [, b]) => b.total - a.total)
222
+ .slice(0, n)
223
+ .map(([ep, s]) => ({ endpoint: ep, ...s, ms: undefined }));
224
+ }
225
+
226
+ export() {
227
+ return JSON.stringify({
228
+ metrics: this.getMetrics(),
229
+ health: this.getHealth(),
230
+ timeSeries: this.getTimeSeries(),
231
+ top5: this.topEndpoints(5),
232
+ exportedAt: new Date().toISOString(),
233
+ }, null, 2);
234
+ }
235
+
236
+ reset() {
237
+ this.req = { total: 0, ok: 0, fail: 0, byEndpoint: new Map() };
238
+ this.errs = { total: 0, byType: new Map(), byCode: new Map(), recent: [], maxRecent: 200 };
239
+ this.perf = { times: [], maxTimes: 2000, slow: [], maxSlow: 100, threshold: 4000 };
240
+ this.gate = { hits: 0, cooldowns: 0, delayed: 0, throttled: 0 };
241
+ }
242
+
243
+ startReporting(interval = 60_000) {
244
+ if (this._timer) clearInterval(this._timer);
245
+ this._timer = setInterval(() => {
246
+ const m = this.getMetrics();
247
+ const h = this.getHealth();
248
+ log("Telemetry",
249
+ `[${h.status.toUpperCase()}] req:${m.requests.total} ok:${m.requests.ok} ` +
250
+ `fail:${m.requests.fail} avg:${m.perf.avgMs}ms p95:${m.perf.p95}ms errors:${m.errors.total}`
251
+ );
252
+ }, interval);
253
+ try { this._timer.unref(); } catch (_) {}
254
+ }
255
+
256
+ stopReporting() {
257
+ if (this._timer) { clearInterval(this._timer); this._timer = null; }
258
+ }
259
+
260
+ destroy() {
261
+ this.stopReporting();
262
+ if (this._seriesTimer) clearInterval(this._seriesTimer);
263
+ }
264
+ }
265
+
266
+ const globalMonitor = new Telemetry();
267
+
268
+ module.exports = {
269
+ Telemetry,
270
+ globalMonitor,
271
+ ProductionMonitor: Telemetry,
272
+ };
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+
3
+ const cheerio = require("cheerio");
4
+ const util = require("util");
5
+
6
+ async function json(url, jar, params, opts, ctx, customHeader) {
7
+ const net = require("./net");
8
+ const log = require("./logger");
9
+ try {
10
+ const res = await net.get(url, jar, params, opts, ctx, customHeader);
11
+ const $ = cheerio.load(res.body);
12
+ const tags = $('script[type="application/json"]');
13
+ if (!tags.length) { log.warn("Tools", `No JSON blobs found on ${url}`); return []; }
14
+ const out = [];
15
+ tags.each((_, el) => {
16
+ try { const txt = $(el).html(); if (txt) out.push(JSON.parse(txt)); } catch (_) {}
17
+ });
18
+ return out;
19
+ } catch (err) {
20
+ require("./logger").error("Tools", `json() failed for ${url}:`, err);
21
+ throw err;
22
+ }
23
+ }
24
+
25
+ function makeDefaults(pageHtml, accountID, ctx) {
26
+ const net = require("./net");
27
+ const log = require("./logger");
28
+ let seq = 1;
29
+ const rev = log.getFrom(pageHtml, 'revision":', ",");
30
+
31
+ function blend(obj) {
32
+ const base = {
33
+ av: accountID,
34
+ __user: accountID,
35
+ __req: (seq++).toString(36),
36
+ __rev: rev,
37
+ __a: 1,
38
+ ...(ctx && { fb_dtsg: ctx.fb_dtsg, jazoest: ctx.jazoest }),
39
+ };
40
+ if (!obj) return base;
41
+ for (const k in obj) {
42
+ if (Object.prototype.hasOwnProperty.call(obj, k) && !(k in base)) base[k] = obj[k];
43
+ }
44
+ return base;
45
+ }
46
+
47
+ return {
48
+ get: (url, jar, params, ctxx, hdr = {}) => net.get(url, jar, blend(params), ctx?.globalOptions, ctxx || ctx, hdr),
49
+ post: (url, jar, form, ctxx, hdr = {}) => net.post(url, jar, blend(form), ctx?.globalOptions, ctxx || ctx, hdr),
50
+ postFormData: (url, jar, form, params, ctxx) => net.postFormData(url, jar, blend(form), blend(params), ctx?.globalOptions, ctxx || ctx),
51
+ };
52
+ }
53
+
54
+ module.exports = {
55
+ ...require("./net"),
56
+ ...require("./response"),
57
+ ...require("./logger"),
58
+ ...require("./transform"),
59
+ ...require("./headers"),
60
+ ...require("./identity"),
61
+ ...require("./ghost"),
62
+ ...require("./revive"),
63
+ ...require("./gate"),
64
+ ...require("./auth"),
65
+ globalValidator: require("./validator").globalValidator,
66
+ Validator: require("./validator").Validator,
67
+ globalMonitor: require("./telemetry").globalMonitor,
68
+ Telemetry: require("./telemetry").Telemetry,
69
+ ProductionMonitor: require("./telemetry").Telemetry,
70
+ CycleManager: require("./heartbeat").CycleManager,
71
+ TokenRefreshManager: require("./heartbeat").TokenRefreshManager,
72
+ json,
73
+ makeDefaults,
74
+ promisify: (fn) => util.promisify(fn),
75
+ delay: (ms) => new Promise(r => setTimeout(r, ms)),
76
+ phantomBanner() {
77
+ const pkg = (() => { try { return require("../../package.json"); } catch (_) { return {}; } })();
78
+ const v = pkg.version || "2.0.0";
79
+ const nm = pkg.name || "phantom-fca";
80
+ const lines = [
81
+ "",
82
+ " ╔═══════════════════════════════════════╗",
83
+ ` ║ ${nm.padEnd(33)}║`,
84
+ ` ║ Version: ${v.padEnd(31)}║`,
85
+ " ║ Stealth Mode: Active ║",
86
+ " ║ Anti-Detection: Enabled ║",
87
+ " ╚═══════════════════════════════════════╝",
88
+ "",
89
+ ];
90
+ lines.forEach(l => console.log(l));
91
+ },
92
+ startupBanner() { this.phantomBanner(); },
93
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ module.exports = require("./cookies");