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.
- package/LICENSE +58 -0
- package/README.md +534 -0
- package/index.js +35 -0
- package/package.json +101 -0
- package/phantom/core/builder/bootstrap.js +334 -0
- package/phantom/core/builder/config.js +78 -0
- package/phantom/core/builder/forge.js +113 -0
- package/phantom/core/builder/ignite.js +386 -0
- package/phantom/core/builder/options.js +61 -0
- package/phantom/core/engine.js +71 -0
- package/phantom/core/reactor.js +2 -0
- package/phantom/datastore/appState.js +2 -0
- package/phantom/datastore/appStateBackup.js +34 -0
- package/phantom/datastore/models/cipher/e2ee.js +48 -0
- package/phantom/datastore/models/cipher/vault.js +153 -0
- package/phantom/datastore/models/index.js +3 -0
- package/phantom/datastore/models/matrix/auth.js +151 -0
- package/phantom/datastore/models/matrix/cache.js +3 -0
- package/phantom/datastore/models/matrix/checker.js +2 -0
- package/phantom/datastore/models/matrix/clients.js +2 -0
- package/phantom/datastore/models/matrix/constants.js +2 -0
- package/phantom/datastore/models/matrix/credentials.js +2 -0
- package/phantom/datastore/models/matrix/cycle.js +2 -0
- package/phantom/datastore/models/matrix/gate.js +282 -0
- package/phantom/datastore/models/matrix/ghost.js +332 -0
- package/phantom/datastore/models/matrix/headers.js +193 -0
- package/phantom/datastore/models/matrix/heartbeat.js +298 -0
- package/phantom/datastore/models/matrix/identity.js +235 -0
- package/phantom/datastore/models/matrix/logger.js +271 -0
- package/phantom/datastore/models/matrix/monitor.js +2 -0
- package/phantom/datastore/models/matrix/net.js +316 -0
- package/phantom/datastore/models/matrix/response.js +193 -0
- package/phantom/datastore/models/matrix/revive.js +255 -0
- package/phantom/datastore/models/matrix/signals.js +2 -0
- package/phantom/datastore/models/matrix/store.js +263 -0
- package/phantom/datastore/models/matrix/telemetry.js +272 -0
- package/phantom/datastore/models/matrix/tools.js +93 -0
- package/phantom/datastore/models/matrix/transform/cookieParser.js +2 -0
- package/phantom/datastore/models/matrix/transform/cookies.js +114 -0
- package/phantom/datastore/models/matrix/transform/index.js +203 -0
- package/phantom/datastore/models/matrix/validator.js +157 -0
- package/phantom/datastore/models/types/index.d.ts +498 -0
- package/phantom/datastore/schema.js +167 -0
- package/phantom/datastore/session.js +129 -0
- package/phantom/datastore/threads.js +22 -0
- package/phantom/datastore/users.js +26 -0
- package/phantom/dispatch/addExternalModule.js +239 -0
- package/phantom/dispatch/addUserToGroup.js +161 -0
- package/phantom/dispatch/changeAdminStatus.js +142 -0
- package/phantom/dispatch/changeArchivedStatus.js +135 -0
- package/phantom/dispatch/changeAvatar.js +123 -0
- package/phantom/dispatch/changeBio.js +86 -0
- package/phantom/dispatch/changeBlockedStatus.js +86 -0
- package/phantom/dispatch/changeGroupImage.js +145 -0
- package/phantom/dispatch/changeThreadColor.js +172 -0
- package/phantom/dispatch/changeThreadEmoji.js +130 -0
- package/phantom/dispatch/comment.js +136 -0
- package/phantom/dispatch/createAITheme.js +333 -0
- package/phantom/dispatch/createNewGroup.js +99 -0
- package/phantom/dispatch/createPoll.js +148 -0
- package/phantom/dispatch/deleteMessage.js +131 -0
- package/phantom/dispatch/deleteThread.js +155 -0
- package/phantom/dispatch/e2ee.js +101 -0
- package/phantom/dispatch/editMessage.js +158 -0
- package/phantom/dispatch/emoji.js +143 -0
- package/phantom/dispatch/fetchThemeData.js +233 -0
- package/phantom/dispatch/follow.js +111 -0
- package/phantom/dispatch/forwardMessage.js +110 -0
- package/phantom/dispatch/friend.js +189 -0
- package/phantom/dispatch/gcmember.js +138 -0
- package/phantom/dispatch/gcname.js +131 -0
- package/phantom/dispatch/gcrule.js +111 -0
- package/phantom/dispatch/getAccess.js +109 -0
- package/phantom/dispatch/getBotInfo.js +81 -0
- package/phantom/dispatch/getBotInitialData.js +110 -0
- package/phantom/dispatch/getFriendsList.js +118 -0
- package/phantom/dispatch/getMessage.js +199 -0
- package/phantom/dispatch/getTheme.js +199 -0
- package/phantom/dispatch/getThemeInfo.js +160 -0
- package/phantom/dispatch/getThreadHistory.js +139 -0
- package/phantom/dispatch/getThreadInfo.js +153 -0
- package/phantom/dispatch/getThreadList.js +132 -0
- package/phantom/dispatch/getThreadPictures.js +93 -0
- package/phantom/dispatch/getUserID.js +147 -0
- package/phantom/dispatch/getUserInfo.js +513 -0
- package/phantom/dispatch/getUserInfoV2.js +146 -0
- package/phantom/dispatch/handleMessageRequest.js +50 -0
- package/phantom/dispatch/httpGet.js +63 -0
- package/phantom/dispatch/httpPost.js +89 -0
- package/phantom/dispatch/httpPostFormData.js +69 -0
- package/phantom/dispatch/listenMqtt.js +1236 -0
- package/phantom/dispatch/listenSpeed.js +179 -0
- package/phantom/dispatch/logout.js +93 -0
- package/phantom/dispatch/markAsDelivered.js +92 -0
- package/phantom/dispatch/markAsRead.js +119 -0
- package/phantom/dispatch/markAsReadAll.js +215 -0
- package/phantom/dispatch/markAsSeen.js +70 -0
- package/phantom/dispatch/mqttDeltaValue.js +278 -0
- package/phantom/dispatch/muteThread.js +253 -0
- package/phantom/dispatch/nickname.js +132 -0
- package/phantom/dispatch/notes.js +263 -0
- package/phantom/dispatch/pinMessage.js +238 -0
- package/phantom/dispatch/produceMetaTheme.js +335 -0
- package/phantom/dispatch/realtime.js +291 -0
- package/phantom/dispatch/removeUserFromGroup.js +248 -0
- package/phantom/dispatch/resolvePhotoUrl.js +217 -0
- package/phantom/dispatch/searchForThread.js +258 -0
- package/phantom/dispatch/sendMessage.js +354 -0
- package/phantom/dispatch/sendMessageMqtt.js +249 -0
- package/phantom/dispatch/sendTypingIndicator.js +206 -0
- package/phantom/dispatch/setMessageReaction.js +188 -0
- package/phantom/dispatch/setMessageReactionMqtt.js +248 -0
- package/phantom/dispatch/setThreadTheme.js +330 -0
- package/phantom/dispatch/setThreadThemeMqtt.js +207 -0
- package/phantom/dispatch/share.js +200 -0
- package/phantom/dispatch/shareContact.js +216 -0
- package/phantom/dispatch/stickers.js +395 -0
- package/phantom/dispatch/story.js +240 -0
- package/phantom/dispatch/theme.js +296 -0
- package/phantom/dispatch/unfriend.js +199 -0
- 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
|
+
};
|