surya-sahil-fca 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.
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+
3
+ // Sanitize header value to remove invalid characters
4
+ function sanitizeHeaderValue(value) {
5
+ if (value === null || value === undefined) return "";
6
+ let str = String(value);
7
+
8
+ // Remove array-like strings (e.g., "["performAutoLogin"]")
9
+ // This handles cases where arrays were accidentally stringified
10
+ if (str.trim().startsWith("[") && str.trim().endsWith("]")) {
11
+ // Try to detect if it's a stringified array and remove it
12
+ try {
13
+ const parsed = JSON.parse(str);
14
+ if (Array.isArray(parsed)) {
15
+ // If it's an array, return empty string (invalid header value)
16
+ return "";
17
+ }
18
+ } catch {
19
+ // Not valid JSON, continue with normal sanitization
20
+ }
21
+ }
22
+
23
+ // Remove invalid characters for HTTP headers:
24
+ // - Control characters (0x00-0x1F, except HTAB 0x09)
25
+ // - DEL character (0x7F)
26
+ // - Newlines and carriage returns
27
+ // - Square brackets (often indicate array stringification issues)
28
+ str = str.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F\r\n\[\]]/g, "").trim();
29
+
30
+ return str;
31
+ }
32
+
33
+ // Sanitize header name to ensure it's valid
34
+ function sanitizeHeaderName(name) {
35
+ if (!name || typeof name !== "string") return "";
36
+ // Remove invalid characters for HTTP header names
37
+ return name.replace(/[^\x21-\x7E]/g, "").trim();
38
+ }
39
+
40
+ function getHeaders(url, options, ctx, customHeader) {
41
+ const u = new URL(url);
42
+ const ua = options?.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36";
43
+ const referer = options?.referer || "https://www.facebook.com/";
44
+ const origin = referer.replace(/\/+$/, "");
45
+ const contentType = options?.contentType || "application/x-www-form-urlencoded";
46
+ const acceptLang = options?.acceptLanguage || "en-US,en;q=0.9,vi;q=0.8";
47
+ const headers = {
48
+ Host: sanitizeHeaderValue(u.host),
49
+ Origin: sanitizeHeaderValue(origin),
50
+ Referer: sanitizeHeaderValue(referer),
51
+ "User-Agent": sanitizeHeaderValue(ua),
52
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,application/json;q=0.8,*/*;q=0.7",
53
+ "Accept-Language": sanitizeHeaderValue(acceptLang),
54
+ "Accept-Encoding": "gzip, deflate, br",
55
+ "Content-Type": sanitizeHeaderValue(contentType),
56
+ Connection: "keep-alive",
57
+ DNT: "1",
58
+ "Upgrade-Insecure-Requests": "1",
59
+ "sec-ch-ua": "\"Chromium\";v=\"139\", \"Not;A=Brand\";v=\"24\", \"Google Chrome\";v=\"139\"",
60
+ "sec-ch-ua-mobile": "?0",
61
+ "sec-ch-ua-platform": "\"Windows\"",
62
+ "sec-ch-ua-arch": "\"x86\"",
63
+ "sec-ch-ua-bitness": "\"64\"",
64
+ "sec-ch-ua-full-version-list": "\"Chromium\";v=\"139.0.0.0\", \"Not;A=Brand\";v=\"24.0.0.0\", \"Google Chrome\";v=\"139.0.0.0\"",
65
+ "sec-ch-ua-platform-version": "\"15.0.0\"",
66
+ "Sec-Fetch-Site": "same-origin",
67
+ "Sec-Fetch-Mode": "cors",
68
+ "Sec-Fetch-Dest": "empty",
69
+ "X-Requested-With": "XMLHttpRequest",
70
+ Pragma: "no-cache",
71
+ "Cache-Control": "no-cache"
72
+ };
73
+ if (ctx?.region) {
74
+ const regionValue = sanitizeHeaderValue(ctx.region);
75
+ if (regionValue) headers["X-MSGR-Region"] = regionValue;
76
+ }
77
+ if (customHeader && typeof customHeader === "object") {
78
+ // Filter customHeader to only include valid HTTP header values (strings, numbers, booleans)
79
+ // Exclude functions, objects, arrays, and other non-serializable values
80
+ for (const [key, value] of Object.entries(customHeader)) {
81
+ // Skip null, undefined, functions, objects, and arrays
82
+ if (value === null || value === undefined || typeof value === "function") {
83
+ continue;
84
+ }
85
+ if (typeof value === "object") {
86
+ // Arrays are objects in JavaScript, so check for arrays explicitly
87
+ if (Array.isArray(value)) {
88
+ continue;
89
+ }
90
+ // Skip plain objects (but allow null which is already handled above)
91
+ continue;
92
+ }
93
+ // Only allow strings, numbers, and booleans - convert to string and sanitize
94
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
95
+ const sanitizedKey = sanitizeHeaderName(key);
96
+ const sanitizedValue = sanitizeHeaderValue(value);
97
+ if (sanitizedKey && sanitizedValue !== "") {
98
+ headers[sanitizedKey] = sanitizedValue;
99
+ }
100
+ }
101
+ }
102
+ }
103
+ // Final pass: sanitize all header values to ensure no invalid characters
104
+ const sanitizedHeaders = {};
105
+ for (const [key, value] of Object.entries(headers)) {
106
+ const sanitizedKey = sanitizeHeaderName(key);
107
+ const sanitizedValue = sanitizeHeaderValue(value);
108
+ if (sanitizedKey && sanitizedValue !== "") {
109
+ sanitizedHeaders[sanitizedKey] = sanitizedValue;
110
+ }
111
+ }
112
+ return sanitizedHeaders;
113
+ }
114
+
115
+ module.exports = { getHeaders };
@@ -0,0 +1,305 @@
1
+ const axios = require("axios");
2
+ const { CookieJar } = require("tough-cookie");
3
+ const { wrapper } = require("axios-cookiejar-support");
4
+ const FormData = require("form-data");
5
+ const { HttpsProxyAgent } = require("https-proxy-agent");
6
+ const { Readable } = require("stream");
7
+
8
+ const headersMod = require("./headers");
9
+ const getHeaders = headersMod.getHeaders || headersMod;
10
+ const formatMod = require("./format");
11
+ const getType = formatMod.getType || formatMod;
12
+ const constMod = require("./constants");
13
+ const getFrom = constMod.getFrom || constMod;
14
+
15
+ // Sanitize header value to remove invalid characters
16
+ function sanitizeHeaderValue(value) {
17
+ if (value === null || value === undefined) return "";
18
+ const str = String(value);
19
+ // Remove invalid characters for HTTP headers:
20
+ // - Control characters (0x00-0x1F, except HTAB 0x09)
21
+ // - DEL character (0x7F)
22
+ // - Newlines and carriage returns
23
+ return str.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F\r\n]/g, "").trim();
24
+ }
25
+
26
+ // Sanitize header name to ensure it's valid
27
+ function sanitizeHeaderName(name) {
28
+ if (!name || typeof name !== "string") return "";
29
+ // Remove invalid characters for HTTP header names
30
+ return name.replace(/[^\x21-\x7E]/g, "").trim();
31
+ }
32
+
33
+ // Sanitize all headers in an object
34
+ function sanitizeHeaders(headers) {
35
+ if (!headers || typeof headers !== "object") return {};
36
+ const sanitized = {};
37
+ for (const [key, value] of Object.entries(headers)) {
38
+ const sanitizedKey = sanitizeHeaderName(key);
39
+ if (!sanitizedKey) continue;
40
+
41
+ // Handle arrays - skip them entirely
42
+ if (Array.isArray(value)) continue;
43
+
44
+ // Handle objects - skip them
45
+ if (value !== null && typeof value === "object") continue;
46
+
47
+ // Handle functions - skip them
48
+ if (typeof value === "function") continue;
49
+
50
+ // Check if string value looks like a stringified array (e.g., "["performAutoLogin"]")
51
+ if (typeof value === "string") {
52
+ const trimmed = value.trim();
53
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
54
+ // Try to parse as JSON array - if successful, skip this header
55
+ try {
56
+ const parsed = JSON.parse(trimmed);
57
+ if (Array.isArray(parsed)) {
58
+ continue; // Skip stringified arrays
59
+ }
60
+ } catch {
61
+ // Not valid JSON array, continue with normal sanitization
62
+ }
63
+ }
64
+ }
65
+
66
+ // Sanitize the value
67
+ const sanitizedValue = sanitizeHeaderValue(value);
68
+ if (sanitizedValue !== "") {
69
+ sanitized[sanitizedKey] = sanitizedValue;
70
+ }
71
+ }
72
+ return sanitized;
73
+ }
74
+
75
+ const jar = new CookieJar();
76
+ const client = wrapper(axios.create({
77
+ jar,
78
+ withCredentials: true,
79
+ timeout: 60000,
80
+ validateStatus: s => s >= 200 && s < 600,
81
+ maxRedirects: 5,
82
+ maxContentLength: Infinity,
83
+ maxBodyLength: Infinity
84
+ }));
85
+
86
+ const delay = ms => new Promise(r => setTimeout(r, ms));
87
+
88
+ async function requestWithRetry(fn, retries = 3, baseDelay = 1000) {
89
+ let lastError;
90
+ for (let i = 0; i < retries; i++) {
91
+ try {
92
+ return await fn();
93
+ } catch (e) {
94
+ lastError = e;
95
+
96
+ // Handle ERR_INVALID_CHAR and other header errors - don't retry, return error immediately
97
+ if (e?.code === "ERR_INVALID_CHAR" || (e?.message && e.message.includes("Invalid character in header"))) {
98
+ const err = new Error("Invalid header content detected. Request aborted to prevent crash.");
99
+ err.error = "Invalid header content";
100
+ err.originalError = e;
101
+ err.code = "ERR_INVALID_CHAR";
102
+ return Promise.reject(err);
103
+ }
104
+
105
+ // Don't retry on client errors (4xx) except 429 (rate limit)
106
+ const status = e?.response?.status || e?.statusCode || 0;
107
+ if (status >= 400 && status < 500 && status !== 429) {
108
+ return e.response || Promise.reject(e);
109
+ }
110
+ // Don't retry on last attempt
111
+ if (i === retries - 1) {
112
+ return e.response || Promise.reject(e);
113
+ }
114
+ // Exponential backoff with jitter
115
+ const backoffDelay = Math.min(
116
+ baseDelay * Math.pow(2, i) + Math.floor(Math.random() * 200),
117
+ 30000 // Max 30 seconds
118
+ );
119
+ await delay(backoffDelay);
120
+ }
121
+ }
122
+ // Return error instead of throwing to prevent uncaught exception
123
+ const finalError = lastError || new Error("Request failed after retries");
124
+ return Promise.reject(finalError);
125
+ }
126
+
127
+ function cfg(base = {}) {
128
+ const { reqJar, headers, params, agent, timeout } = base;
129
+ return {
130
+ headers: sanitizeHeaders(headers),
131
+ params,
132
+ jar: reqJar || jar,
133
+ withCredentials: true,
134
+ timeout: timeout || 60000,
135
+ httpAgent: agent || client.defaults.httpAgent,
136
+ httpsAgent: agent || client.defaults.httpsAgent,
137
+ proxy: false,
138
+ validateStatus: s => s >= 200 && s < 600
139
+ };
140
+ }
141
+
142
+ function toStringVal(v) {
143
+ if (v === undefined || v === null) return "";
144
+ if (typeof v === "bigint") return v.toString();
145
+ if (typeof v === "boolean") return v ? "true" : "false";
146
+ return String(v);
147
+ }
148
+
149
+ function isStream(v) {
150
+ return v && typeof v === "object" && typeof v.pipe === "function" && typeof v.on === "function";
151
+ }
152
+
153
+ function isBlobLike(v) {
154
+ return v && typeof v.arrayBuffer === "function" && (typeof v.type === "string" || typeof v.name === "string");
155
+ }
156
+
157
+ function isPairArrayList(arr) {
158
+ return Array.isArray(arr) && arr.length > 0 && arr.every(x => Array.isArray(x) && x.length === 2 && typeof x[0] === "string");
159
+ }
160
+
161
+ function cleanGet(url) {
162
+ return requestWithRetry(() => client.get(url, cfg()), 3, 1000);
163
+ }
164
+
165
+ function get(url, reqJar, qs, options, ctx, customHeader) {
166
+ const headers = getHeaders(url, options, ctx, customHeader);
167
+ return requestWithRetry(() => client.get(url, cfg({ reqJar, headers, params: qs })), 3, 1000);
168
+ }
169
+
170
+ function post(url, reqJar, form, options, ctx, customHeader) {
171
+ const headers = getHeaders(url, options, ctx, customHeader);
172
+ const ct = String(headers["Content-Type"] || headers["content-type"] || "application/x-www-form-urlencoded").toLowerCase();
173
+ let data;
174
+ if (ct.includes("json")) {
175
+ data = JSON.stringify(form || {});
176
+ headers["Content-Type"] = "application/json";
177
+ } else {
178
+ const p = new URLSearchParams();
179
+ if (form && typeof form === "object") {
180
+ for (const k of Object.keys(form)) {
181
+ let v = form[k];
182
+ if (isPairArrayList(v)) {
183
+ for (const [kk, vv] of v) p.append(`${k}[${kk}]`, toStringVal(vv));
184
+ continue;
185
+ }
186
+ if (Array.isArray(v)) {
187
+ for (const x of v) {
188
+ if (Array.isArray(x) && x.length === 2 && typeof x[1] !== "object") p.append(k, toStringVal(x[1]));
189
+ else p.append(k, toStringVal(x));
190
+ }
191
+ continue;
192
+ }
193
+ if (getType(v) === "Object") v = JSON.stringify(v);
194
+ p.append(k, toStringVal(v));
195
+ }
196
+ }
197
+ data = p.toString();
198
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
199
+ }
200
+ return requestWithRetry(() => client.post(url, data, cfg({ reqJar, headers })), 3, 1000);
201
+ }
202
+
203
+ async function postFormData(url, reqJar, form, qs, options, ctx) {
204
+ const fd = new FormData();
205
+ if (form && typeof form === "object") {
206
+ for (const k of Object.keys(form)) {
207
+ const v = form[k];
208
+ if (v === undefined || v === null) continue;
209
+ if (isPairArrayList(v)) {
210
+ for (const [kk, vv] of v) fd.append(`${k}[${kk}]`, typeof vv === "object" && !Buffer.isBuffer(vv) && !isStream(vv) ? JSON.stringify(vv) : vv);
211
+ continue;
212
+ }
213
+ if (Array.isArray(v)) {
214
+ for (const x of v) {
215
+ if (Array.isArray(x) && x.length === 2 && x[1] && typeof x[1] === "object" && !Buffer.isBuffer(x[1]) && !isStream(x[1])) {
216
+ fd.append(k, x[0], x[1]);
217
+ } else if (Array.isArray(x) && x.length === 2 && typeof x[1] !== "object") {
218
+ fd.append(k, toStringVal(x[1]));
219
+ } else if (x && typeof x === "object" && "value" in x && "options" in x) {
220
+ fd.append(k, x.value, x.options || {});
221
+ } else if (isStream(x) || Buffer.isBuffer(x) || typeof x === "string") {
222
+ fd.append(k, x);
223
+ } else if (isBlobLike(x)) {
224
+ const buf = Buffer.from(await x.arrayBuffer());
225
+ fd.append(k, buf, { filename: x.name || k, contentType: x.type || undefined });
226
+ } else {
227
+ fd.append(k, JSON.stringify(x));
228
+ }
229
+ }
230
+ continue;
231
+ }
232
+ if (v && typeof v === "object" && "value" in v && "options" in v) {
233
+ fd.append(k, v.value, v.options || {});
234
+ continue;
235
+ }
236
+ if (isStream(v) || Buffer.isBuffer(v) || typeof v === "string") {
237
+ fd.append(k, v);
238
+ continue;
239
+ }
240
+ if (isBlobLike(v)) {
241
+ const buf = Buffer.from(await v.arrayBuffer());
242
+ fd.append(k, buf, { filename: v.name || k, contentType: v.type || undefined });
243
+ continue;
244
+ }
245
+ if (typeof v === "number" || typeof v === "boolean") {
246
+ fd.append(k, toStringVal(v));
247
+ continue;
248
+ }
249
+ fd.append(k, JSON.stringify(v));
250
+ }
251
+ }
252
+ const headers = { ...getHeaders(url, options, ctx), ...fd.getHeaders() };
253
+ return requestWithRetry(() => client.post(url, fd, cfg({ reqJar, headers, params: qs })), 3, 1000);
254
+ }
255
+
256
+ function makeDefaults(html, userID, ctx) {
257
+ let reqCounter = 1;
258
+ const revision = getFrom(html || "", 'revision":', ",") || getFrom(html || "", '"client_revision":', ",") || "";
259
+ function mergeWithDefaults(obj) {
260
+ const base = {
261
+ av: userID,
262
+ __user: userID,
263
+ __req: (reqCounter++).toString(36),
264
+ __rev: revision,
265
+ __a: 1
266
+ };
267
+ if (ctx?.fb_dtsg) base.fb_dtsg = ctx.fb_dtsg;
268
+ if (ctx?.jazoest) base.jazoest = ctx.jazoest;
269
+ if (!obj) return base;
270
+ for (const k of Object.keys(obj)) if (!(k in base)) base[k] = obj[k];
271
+ return base;
272
+ }
273
+ return {
274
+ get: (url, j, qs, ctxx, customHeader = {}) =>
275
+ get(url, j, mergeWithDefaults(qs), ctx?.globalOptions, ctxx || ctx, customHeader),
276
+ post: (url, j, form, ctxx, customHeader = {}) =>
277
+ post(url, j, mergeWithDefaults(form), ctx?.globalOptions, ctxx || ctx, customHeader),
278
+ postFormData: (url, j, form, qs, ctxx) =>
279
+ postFormData(url, j, mergeWithDefaults(form), mergeWithDefaults(qs), ctx?.globalOptions, ctxx || ctx)
280
+ };
281
+ }
282
+
283
+ function setProxy(proxyUrl) {
284
+ if (!proxyUrl) {
285
+ client.defaults.httpAgent = undefined;
286
+ client.defaults.httpsAgent = undefined;
287
+ client.defaults.proxy = false;
288
+ return;
289
+ }
290
+ const agent = new HttpsProxyAgent(proxyUrl);
291
+ client.defaults.httpAgent = agent;
292
+ client.defaults.httpsAgent = agent;
293
+ client.defaults.proxy = false;
294
+ }
295
+
296
+ module.exports = {
297
+ cleanGet,
298
+ get,
299
+ post,
300
+ postFormData,
301
+ jar,
302
+ setProxy,
303
+ makeDefaults,
304
+ client
305
+ };