device-passport 1.0.0 → 1.2.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/CHANGELOG.md +33 -0
- package/README.md +110 -2
- package/assets/ego-profile-manager.png +0 -0
- package/dist/adapters/index.cjs +19 -0
- package/dist/adapters/index.d.cts +66 -0
- package/dist/adapters/index.d.mts +66 -0
- package/dist/adapters/index.d.ts +66 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/ego/index.cjs +22 -0
- package/dist/ego/index.d.cts +5 -0
- package/dist/ego/index.d.mts +5 -0
- package/dist/ego/index.d.ts +5 -0
- package/dist/ego/index.mjs +8 -0
- package/dist/evaluator/index.cjs +140 -0
- package/dist/evaluator/index.d.cts +204 -0
- package/dist/evaluator/index.d.mts +204 -0
- package/dist/evaluator/index.d.ts +204 -0
- package/dist/evaluator/index.mjs +119 -0
- package/dist/geo/index.cjs +50 -0
- package/dist/geo/index.d.cts +213 -0
- package/dist/geo/index.d.mts +213 -0
- package/dist/geo/index.d.ts +213 -0
- package/dist/geo/index.mjs +18 -0
- package/dist/index.cjs +110 -3940
- package/dist/index.d.cts +45 -1091
- package/dist/index.d.mts +45 -1091
- package/dist/index.d.ts +45 -1091
- package/dist/index.mjs +11 -3849
- package/dist/shared/device-passport.BGfRcQNh.mjs +565 -0
- package/dist/shared/device-passport.BKtmLzPt.cjs +1266 -0
- package/dist/shared/device-passport.BPQj0qdT.mjs +1238 -0
- package/dist/shared/device-passport.CDS7xrj8.d.ts +469 -0
- package/dist/shared/device-passport.C_F1vBE8.d.cts +79 -0
- package/dist/shared/device-passport.C_F1vBE8.d.mts +79 -0
- package/dist/shared/device-passport.C_F1vBE8.d.ts +79 -0
- package/dist/shared/device-passport.CfLes-lq.d.cts +469 -0
- package/dist/shared/device-passport.Cjxlv8xI.cjs +245 -0
- package/dist/shared/device-passport.CsOWqPIr.d.cts +77 -0
- package/dist/shared/device-passport.CyQtcZeD.mjs +237 -0
- package/dist/shared/device-passport.D7pEnyp2.d.ts +77 -0
- package/dist/shared/device-passport.D9xW6R0B.cjs +1571 -0
- package/dist/shared/device-passport.DirQmSd4.d.cts +47 -0
- package/dist/shared/device-passport.DirQmSd4.d.mts +47 -0
- package/dist/shared/device-passport.DirQmSd4.d.ts +47 -0
- package/dist/shared/device-passport.EK4RX7Xd.cjs +584 -0
- package/dist/shared/device-passport.ZBspe4ZW.d.mts +77 -0
- package/dist/shared/device-passport.dVEqzSEb.d.mts +469 -0
- package/dist/shared/device-passport.gNo82fQ5.mjs +1537 -0
- package/dist/shared/device-passport.kekU28UM.mjs +705 -0
- package/dist/shared/device-passport.qL4tjwc0.cjs +718 -0
- package/dist/storage/index.cjs +10 -0
- package/dist/storage/index.d.cts +97 -0
- package/dist/storage/index.d.mts +97 -0
- package/dist/storage/index.d.ts +97 -0
- package/dist/storage/index.mjs +3 -0
- package/dist/types/index.cjs +2 -0
- package/dist/types/index.d.cts +397 -0
- package/dist/types/index.d.mts +397 -0
- package/dist/types/index.d.ts +397 -0
- package/dist/types/index.mjs +1 -0
- package/package.json +32 -2
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
7
|
+
|
|
8
|
+
const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
9
|
+
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
10
|
+
|
|
11
|
+
class JSONFileStorage {
|
|
12
|
+
filePath;
|
|
13
|
+
pretty;
|
|
14
|
+
autoSave;
|
|
15
|
+
cache = /* @__PURE__ */ new Map();
|
|
16
|
+
isLoaded = false;
|
|
17
|
+
writeQueue = Promise.resolve();
|
|
18
|
+
constructor(filePathOrOptions) {
|
|
19
|
+
if (typeof filePathOrOptions === "string") {
|
|
20
|
+
this.filePath = path__default.resolve(filePathOrOptions);
|
|
21
|
+
this.pretty = true;
|
|
22
|
+
this.autoSave = true;
|
|
23
|
+
} else {
|
|
24
|
+
this.filePath = path__default.resolve(filePathOrOptions.filePath);
|
|
25
|
+
this.pretty = filePathOrOptions.pretty ?? true;
|
|
26
|
+
this.autoSave = filePathOrOptions.autoSave ?? true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Ensure data is loaded from disk into the in-memory cache.
|
|
31
|
+
*/
|
|
32
|
+
ensureLoaded() {
|
|
33
|
+
if (this.isLoaded) return;
|
|
34
|
+
this.isLoaded = true;
|
|
35
|
+
if (!fs__default.existsSync(this.filePath)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const raw = fs__default.readFileSync(this.filePath, "utf-8").trim();
|
|
40
|
+
if (!raw) return;
|
|
41
|
+
const data = JSON.parse(raw);
|
|
42
|
+
if (typeof data === "object" && data !== null) {
|
|
43
|
+
for (const [key, value] of Object.entries(data)) {
|
|
44
|
+
this.cache.set(key, value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
const corruptBackup = `${this.filePath}.corrupt-${Date.now()}`;
|
|
49
|
+
try {
|
|
50
|
+
fs__default.copyFileSync(this.filePath, corruptBackup);
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
throw new Error(
|
|
54
|
+
`[JSONFileStorage] Failed to parse JSON file at ${this.filePath}: ${err.message}. A backup was created at ${corruptBackup}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Atomic flush to disk. Writes to a unique temp file in the same directory,
|
|
60
|
+
* then atomically renames it to target file.
|
|
61
|
+
*/
|
|
62
|
+
flushAtomic() {
|
|
63
|
+
const dir = path__default.dirname(this.filePath);
|
|
64
|
+
if (!fs__default.existsSync(dir)) {
|
|
65
|
+
fs__default.mkdirSync(dir, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
const obj = {};
|
|
68
|
+
for (const [k, v] of this.cache.entries()) {
|
|
69
|
+
obj[k] = v;
|
|
70
|
+
}
|
|
71
|
+
const jsonString = this.pretty ? JSON.stringify(obj, null, 2) : JSON.stringify(obj);
|
|
72
|
+
const nonce = Math.random().toString(36).slice(2, 8);
|
|
73
|
+
const tmpPath = path__default.join(dir, `.${path__default.basename(this.filePath)}.${process.pid}.${Date.now()}.${nonce}.tmp`);
|
|
74
|
+
try {
|
|
75
|
+
fs__default.writeFileSync(tmpPath, jsonString, "utf-8");
|
|
76
|
+
fs__default.renameSync(tmpPath, this.filePath);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (fs__default.existsSync(tmpPath)) {
|
|
79
|
+
try {
|
|
80
|
+
fs__default.unlinkSync(tmpPath);
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Queue a disk write to prevent race conditions during rapid concurrent mutations.
|
|
89
|
+
*/
|
|
90
|
+
queueSave() {
|
|
91
|
+
if (!this.autoSave) {
|
|
92
|
+
return Promise.resolve();
|
|
93
|
+
}
|
|
94
|
+
this.writeQueue = this.writeQueue.then(() => {
|
|
95
|
+
this.flushAtomic();
|
|
96
|
+
}).catch((err) => {
|
|
97
|
+
return Promise.reject(err);
|
|
98
|
+
});
|
|
99
|
+
return this.writeQueue;
|
|
100
|
+
}
|
|
101
|
+
async get(key) {
|
|
102
|
+
return this.getSync(key);
|
|
103
|
+
}
|
|
104
|
+
getSync(key) {
|
|
105
|
+
this.ensureLoaded();
|
|
106
|
+
const val = this.cache.get(key);
|
|
107
|
+
return val !== void 0 ? val : null;
|
|
108
|
+
}
|
|
109
|
+
async set(key, value) {
|
|
110
|
+
this.ensureLoaded();
|
|
111
|
+
this.cache.set(key, value);
|
|
112
|
+
await this.queueSave();
|
|
113
|
+
}
|
|
114
|
+
setSync(key, value) {
|
|
115
|
+
this.ensureLoaded();
|
|
116
|
+
this.cache.set(key, value);
|
|
117
|
+
if (this.autoSave) {
|
|
118
|
+
this.flushAtomic();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async has(key) {
|
|
122
|
+
return this.hasSync(key);
|
|
123
|
+
}
|
|
124
|
+
hasSync(key) {
|
|
125
|
+
this.ensureLoaded();
|
|
126
|
+
return this.cache.has(key);
|
|
127
|
+
}
|
|
128
|
+
async delete(key) {
|
|
129
|
+
this.ensureLoaded();
|
|
130
|
+
const existed = this.cache.delete(key);
|
|
131
|
+
if (existed && this.autoSave) {
|
|
132
|
+
await this.queueSave();
|
|
133
|
+
}
|
|
134
|
+
return existed;
|
|
135
|
+
}
|
|
136
|
+
deleteSync(key) {
|
|
137
|
+
this.ensureLoaded();
|
|
138
|
+
const existed = this.cache.delete(key);
|
|
139
|
+
if (existed && this.autoSave) {
|
|
140
|
+
this.flushAtomic();
|
|
141
|
+
}
|
|
142
|
+
return existed;
|
|
143
|
+
}
|
|
144
|
+
async keys() {
|
|
145
|
+
return this.keysSync();
|
|
146
|
+
}
|
|
147
|
+
keysSync() {
|
|
148
|
+
this.ensureLoaded();
|
|
149
|
+
return Array.from(this.cache.keys());
|
|
150
|
+
}
|
|
151
|
+
async clear() {
|
|
152
|
+
this.ensureLoaded();
|
|
153
|
+
this.cache.clear();
|
|
154
|
+
if (this.autoSave) {
|
|
155
|
+
await this.queueSave();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
clearSync() {
|
|
159
|
+
this.ensureLoaded();
|
|
160
|
+
this.cache.clear();
|
|
161
|
+
if (this.autoSave) {
|
|
162
|
+
this.flushAtomic();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Explicitly force a flush to disk (useful if autoSave is set to false).
|
|
167
|
+
*/
|
|
168
|
+
async flush() {
|
|
169
|
+
await this.queueSave();
|
|
170
|
+
}
|
|
171
|
+
flushSync() {
|
|
172
|
+
this.flushAtomic();
|
|
173
|
+
}
|
|
174
|
+
get size() {
|
|
175
|
+
this.ensureLoaded();
|
|
176
|
+
return this.cache.size;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
class MemoryStorage {
|
|
181
|
+
store = /* @__PURE__ */ new Map();
|
|
182
|
+
clone;
|
|
183
|
+
constructor(options) {
|
|
184
|
+
this.clone = options?.clone ?? true;
|
|
185
|
+
}
|
|
186
|
+
deepClone(val) {
|
|
187
|
+
if (!this.clone || val === null || typeof val !== "object") {
|
|
188
|
+
return val;
|
|
189
|
+
}
|
|
190
|
+
if (typeof structuredClone === "function") {
|
|
191
|
+
try {
|
|
192
|
+
return structuredClone(val);
|
|
193
|
+
} catch {
|
|
194
|
+
return JSON.parse(JSON.stringify(val));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return JSON.parse(JSON.stringify(val));
|
|
198
|
+
}
|
|
199
|
+
async get(key) {
|
|
200
|
+
return this.getSync(key);
|
|
201
|
+
}
|
|
202
|
+
getSync(key) {
|
|
203
|
+
const val = this.store.get(key);
|
|
204
|
+
if (val === void 0) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
return this.deepClone(val);
|
|
208
|
+
}
|
|
209
|
+
async set(key, value) {
|
|
210
|
+
this.setSync(key, value);
|
|
211
|
+
}
|
|
212
|
+
setSync(key, value) {
|
|
213
|
+
this.store.set(key, this.deepClone(value));
|
|
214
|
+
}
|
|
215
|
+
async has(key) {
|
|
216
|
+
return this.hasSync(key);
|
|
217
|
+
}
|
|
218
|
+
hasSync(key) {
|
|
219
|
+
return this.store.has(key);
|
|
220
|
+
}
|
|
221
|
+
async delete(key) {
|
|
222
|
+
return this.deleteSync(key);
|
|
223
|
+
}
|
|
224
|
+
deleteSync(key) {
|
|
225
|
+
return this.store.delete(key);
|
|
226
|
+
}
|
|
227
|
+
async keys() {
|
|
228
|
+
return this.keysSync();
|
|
229
|
+
}
|
|
230
|
+
keysSync() {
|
|
231
|
+
return Array.from(this.store.keys());
|
|
232
|
+
}
|
|
233
|
+
async clear() {
|
|
234
|
+
this.clearSync();
|
|
235
|
+
}
|
|
236
|
+
clearSync() {
|
|
237
|
+
this.store.clear();
|
|
238
|
+
}
|
|
239
|
+
get size() {
|
|
240
|
+
return this.store.size;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
exports.JSONFileStorage = JSONFileStorage;
|
|
245
|
+
exports.MemoryStorage = MemoryStorage;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { DevicePassport } from '../types/index.cjs';
|
|
2
|
+
|
|
3
|
+
interface PlaywrightContextOptions {
|
|
4
|
+
userAgent: string;
|
|
5
|
+
viewport: {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
};
|
|
9
|
+
deviceScaleFactor: number;
|
|
10
|
+
locale: string;
|
|
11
|
+
timezoneId: string;
|
|
12
|
+
geolocation?: {
|
|
13
|
+
latitude: number;
|
|
14
|
+
longitude: number;
|
|
15
|
+
accuracy?: number;
|
|
16
|
+
};
|
|
17
|
+
permissions?: string[];
|
|
18
|
+
extraHTTPHeaders: Record<string, string>;
|
|
19
|
+
hasTouch: boolean;
|
|
20
|
+
isMobile: boolean;
|
|
21
|
+
colorScheme?: 'light' | 'dark' | 'no-preference';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Builds Playwright BrowserContext options directly from a DevicePassport.
|
|
25
|
+
* Ready for `browser.newContext(getPlaywrightContext(passport))`.
|
|
26
|
+
*/
|
|
27
|
+
declare function getPlaywrightContext(passport: DevicePassport): PlaywrightContextOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Generates an in-page stealth script to patch runtime JavaScript APIs
|
|
30
|
+
* (navigator.webdriver, hardwareConcurrency, deviceMemory, WebGL unmasked vendor/renderer,
|
|
31
|
+
* navigator.userAgentData Client Hints, and Screen dimensions).
|
|
32
|
+
*
|
|
33
|
+
* 🔴 API-level spoofing is not enough on its own: `canvas.toDataURL()` and `gl.readPixels()`
|
|
34
|
+
* still return the **real** framebuffer, so a headless browser exposes SwiftShader while
|
|
35
|
+
* its UA claims a discrete GPU — and every persona on one machine hashes identically.
|
|
36
|
+
* The pixel-protection script is therefore appended here, so existing users get
|
|
37
|
+
* read-back-safe personas without changing their call sites.
|
|
38
|
+
*
|
|
39
|
+
* The noise is seeded from the passport seed: stable per account, different per account.
|
|
40
|
+
*/
|
|
41
|
+
declare function getStealthScript(passport: DevicePassport): string;
|
|
42
|
+
/**
|
|
43
|
+
* Applies stealth injections and extra headers to a Playwright BrowserContext.
|
|
44
|
+
*
|
|
45
|
+
* @param context Playwright BrowserContext instance
|
|
46
|
+
* @param passport DevicePassport instance
|
|
47
|
+
*/
|
|
48
|
+
declare function applyToPlaywrightContext(context: any, passport: DevicePassport): Promise<void>;
|
|
49
|
+
|
|
50
|
+
interface PuppeteerEmulationOptions {
|
|
51
|
+
launchArgs: string[];
|
|
52
|
+
defaultViewport: {
|
|
53
|
+
width: number;
|
|
54
|
+
height: number;
|
|
55
|
+
deviceScaleFactor: number;
|
|
56
|
+
isMobile: boolean;
|
|
57
|
+
hasTouch: boolean;
|
|
58
|
+
};
|
|
59
|
+
userAgent: string;
|
|
60
|
+
locale: string;
|
|
61
|
+
timezoneId: string;
|
|
62
|
+
extraHTTPHeaders: Record<string, string>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Returns Puppeteer launch args and page emulation parameters matching the DevicePassport.
|
|
66
|
+
*/
|
|
67
|
+
declare function getPuppeteerArgs(passport: DevicePassport): PuppeteerEmulationOptions;
|
|
68
|
+
/**
|
|
69
|
+
* Applies stealth injections, headers, viewport, and timezone emulation to a Puppeteer Page.
|
|
70
|
+
*
|
|
71
|
+
* @param page Puppeteer Page instance
|
|
72
|
+
* @param passport DevicePassport instance
|
|
73
|
+
*/
|
|
74
|
+
declare function applyToPuppeteerPage(page: any, passport: DevicePassport): Promise<void>;
|
|
75
|
+
|
|
76
|
+
export { applyToPlaywrightContext as b, applyToPuppeteerPage as c, getPuppeteerArgs as d, getStealthScript as e, getPlaywrightContext as g };
|
|
77
|
+
export type { PlaywrightContextOptions as P, PuppeteerEmulationOptions as a };
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
class JSONFileStorage {
|
|
5
|
+
filePath;
|
|
6
|
+
pretty;
|
|
7
|
+
autoSave;
|
|
8
|
+
cache = /* @__PURE__ */ new Map();
|
|
9
|
+
isLoaded = false;
|
|
10
|
+
writeQueue = Promise.resolve();
|
|
11
|
+
constructor(filePathOrOptions) {
|
|
12
|
+
if (typeof filePathOrOptions === "string") {
|
|
13
|
+
this.filePath = path.resolve(filePathOrOptions);
|
|
14
|
+
this.pretty = true;
|
|
15
|
+
this.autoSave = true;
|
|
16
|
+
} else {
|
|
17
|
+
this.filePath = path.resolve(filePathOrOptions.filePath);
|
|
18
|
+
this.pretty = filePathOrOptions.pretty ?? true;
|
|
19
|
+
this.autoSave = filePathOrOptions.autoSave ?? true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Ensure data is loaded from disk into the in-memory cache.
|
|
24
|
+
*/
|
|
25
|
+
ensureLoaded() {
|
|
26
|
+
if (this.isLoaded) return;
|
|
27
|
+
this.isLoaded = true;
|
|
28
|
+
if (!fs.existsSync(this.filePath)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const raw = fs.readFileSync(this.filePath, "utf-8").trim();
|
|
33
|
+
if (!raw) return;
|
|
34
|
+
const data = JSON.parse(raw);
|
|
35
|
+
if (typeof data === "object" && data !== null) {
|
|
36
|
+
for (const [key, value] of Object.entries(data)) {
|
|
37
|
+
this.cache.set(key, value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
const corruptBackup = `${this.filePath}.corrupt-${Date.now()}`;
|
|
42
|
+
try {
|
|
43
|
+
fs.copyFileSync(this.filePath, corruptBackup);
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
throw new Error(
|
|
47
|
+
`[JSONFileStorage] Failed to parse JSON file at ${this.filePath}: ${err.message}. A backup was created at ${corruptBackup}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Atomic flush to disk. Writes to a unique temp file in the same directory,
|
|
53
|
+
* then atomically renames it to target file.
|
|
54
|
+
*/
|
|
55
|
+
flushAtomic() {
|
|
56
|
+
const dir = path.dirname(this.filePath);
|
|
57
|
+
if (!fs.existsSync(dir)) {
|
|
58
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
const obj = {};
|
|
61
|
+
for (const [k, v] of this.cache.entries()) {
|
|
62
|
+
obj[k] = v;
|
|
63
|
+
}
|
|
64
|
+
const jsonString = this.pretty ? JSON.stringify(obj, null, 2) : JSON.stringify(obj);
|
|
65
|
+
const nonce = Math.random().toString(36).slice(2, 8);
|
|
66
|
+
const tmpPath = path.join(dir, `.${path.basename(this.filePath)}.${process.pid}.${Date.now()}.${nonce}.tmp`);
|
|
67
|
+
try {
|
|
68
|
+
fs.writeFileSync(tmpPath, jsonString, "utf-8");
|
|
69
|
+
fs.renameSync(tmpPath, this.filePath);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (fs.existsSync(tmpPath)) {
|
|
72
|
+
try {
|
|
73
|
+
fs.unlinkSync(tmpPath);
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Queue a disk write to prevent race conditions during rapid concurrent mutations.
|
|
82
|
+
*/
|
|
83
|
+
queueSave() {
|
|
84
|
+
if (!this.autoSave) {
|
|
85
|
+
return Promise.resolve();
|
|
86
|
+
}
|
|
87
|
+
this.writeQueue = this.writeQueue.then(() => {
|
|
88
|
+
this.flushAtomic();
|
|
89
|
+
}).catch((err) => {
|
|
90
|
+
return Promise.reject(err);
|
|
91
|
+
});
|
|
92
|
+
return this.writeQueue;
|
|
93
|
+
}
|
|
94
|
+
async get(key) {
|
|
95
|
+
return this.getSync(key);
|
|
96
|
+
}
|
|
97
|
+
getSync(key) {
|
|
98
|
+
this.ensureLoaded();
|
|
99
|
+
const val = this.cache.get(key);
|
|
100
|
+
return val !== void 0 ? val : null;
|
|
101
|
+
}
|
|
102
|
+
async set(key, value) {
|
|
103
|
+
this.ensureLoaded();
|
|
104
|
+
this.cache.set(key, value);
|
|
105
|
+
await this.queueSave();
|
|
106
|
+
}
|
|
107
|
+
setSync(key, value) {
|
|
108
|
+
this.ensureLoaded();
|
|
109
|
+
this.cache.set(key, value);
|
|
110
|
+
if (this.autoSave) {
|
|
111
|
+
this.flushAtomic();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async has(key) {
|
|
115
|
+
return this.hasSync(key);
|
|
116
|
+
}
|
|
117
|
+
hasSync(key) {
|
|
118
|
+
this.ensureLoaded();
|
|
119
|
+
return this.cache.has(key);
|
|
120
|
+
}
|
|
121
|
+
async delete(key) {
|
|
122
|
+
this.ensureLoaded();
|
|
123
|
+
const existed = this.cache.delete(key);
|
|
124
|
+
if (existed && this.autoSave) {
|
|
125
|
+
await this.queueSave();
|
|
126
|
+
}
|
|
127
|
+
return existed;
|
|
128
|
+
}
|
|
129
|
+
deleteSync(key) {
|
|
130
|
+
this.ensureLoaded();
|
|
131
|
+
const existed = this.cache.delete(key);
|
|
132
|
+
if (existed && this.autoSave) {
|
|
133
|
+
this.flushAtomic();
|
|
134
|
+
}
|
|
135
|
+
return existed;
|
|
136
|
+
}
|
|
137
|
+
async keys() {
|
|
138
|
+
return this.keysSync();
|
|
139
|
+
}
|
|
140
|
+
keysSync() {
|
|
141
|
+
this.ensureLoaded();
|
|
142
|
+
return Array.from(this.cache.keys());
|
|
143
|
+
}
|
|
144
|
+
async clear() {
|
|
145
|
+
this.ensureLoaded();
|
|
146
|
+
this.cache.clear();
|
|
147
|
+
if (this.autoSave) {
|
|
148
|
+
await this.queueSave();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
clearSync() {
|
|
152
|
+
this.ensureLoaded();
|
|
153
|
+
this.cache.clear();
|
|
154
|
+
if (this.autoSave) {
|
|
155
|
+
this.flushAtomic();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Explicitly force a flush to disk (useful if autoSave is set to false).
|
|
160
|
+
*/
|
|
161
|
+
async flush() {
|
|
162
|
+
await this.queueSave();
|
|
163
|
+
}
|
|
164
|
+
flushSync() {
|
|
165
|
+
this.flushAtomic();
|
|
166
|
+
}
|
|
167
|
+
get size() {
|
|
168
|
+
this.ensureLoaded();
|
|
169
|
+
return this.cache.size;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
class MemoryStorage {
|
|
174
|
+
store = /* @__PURE__ */ new Map();
|
|
175
|
+
clone;
|
|
176
|
+
constructor(options) {
|
|
177
|
+
this.clone = options?.clone ?? true;
|
|
178
|
+
}
|
|
179
|
+
deepClone(val) {
|
|
180
|
+
if (!this.clone || val === null || typeof val !== "object") {
|
|
181
|
+
return val;
|
|
182
|
+
}
|
|
183
|
+
if (typeof structuredClone === "function") {
|
|
184
|
+
try {
|
|
185
|
+
return structuredClone(val);
|
|
186
|
+
} catch {
|
|
187
|
+
return JSON.parse(JSON.stringify(val));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return JSON.parse(JSON.stringify(val));
|
|
191
|
+
}
|
|
192
|
+
async get(key) {
|
|
193
|
+
return this.getSync(key);
|
|
194
|
+
}
|
|
195
|
+
getSync(key) {
|
|
196
|
+
const val = this.store.get(key);
|
|
197
|
+
if (val === void 0) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return this.deepClone(val);
|
|
201
|
+
}
|
|
202
|
+
async set(key, value) {
|
|
203
|
+
this.setSync(key, value);
|
|
204
|
+
}
|
|
205
|
+
setSync(key, value) {
|
|
206
|
+
this.store.set(key, this.deepClone(value));
|
|
207
|
+
}
|
|
208
|
+
async has(key) {
|
|
209
|
+
return this.hasSync(key);
|
|
210
|
+
}
|
|
211
|
+
hasSync(key) {
|
|
212
|
+
return this.store.has(key);
|
|
213
|
+
}
|
|
214
|
+
async delete(key) {
|
|
215
|
+
return this.deleteSync(key);
|
|
216
|
+
}
|
|
217
|
+
deleteSync(key) {
|
|
218
|
+
return this.store.delete(key);
|
|
219
|
+
}
|
|
220
|
+
async keys() {
|
|
221
|
+
return this.keysSync();
|
|
222
|
+
}
|
|
223
|
+
keysSync() {
|
|
224
|
+
return Array.from(this.store.keys());
|
|
225
|
+
}
|
|
226
|
+
async clear() {
|
|
227
|
+
this.clearSync();
|
|
228
|
+
}
|
|
229
|
+
clearSync() {
|
|
230
|
+
this.store.clear();
|
|
231
|
+
}
|
|
232
|
+
get size() {
|
|
233
|
+
return this.store.size;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export { JSONFileStorage as J, MemoryStorage as M };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { DevicePassport } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
interface PlaywrightContextOptions {
|
|
4
|
+
userAgent: string;
|
|
5
|
+
viewport: {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
};
|
|
9
|
+
deviceScaleFactor: number;
|
|
10
|
+
locale: string;
|
|
11
|
+
timezoneId: string;
|
|
12
|
+
geolocation?: {
|
|
13
|
+
latitude: number;
|
|
14
|
+
longitude: number;
|
|
15
|
+
accuracy?: number;
|
|
16
|
+
};
|
|
17
|
+
permissions?: string[];
|
|
18
|
+
extraHTTPHeaders: Record<string, string>;
|
|
19
|
+
hasTouch: boolean;
|
|
20
|
+
isMobile: boolean;
|
|
21
|
+
colorScheme?: 'light' | 'dark' | 'no-preference';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Builds Playwright BrowserContext options directly from a DevicePassport.
|
|
25
|
+
* Ready for `browser.newContext(getPlaywrightContext(passport))`.
|
|
26
|
+
*/
|
|
27
|
+
declare function getPlaywrightContext(passport: DevicePassport): PlaywrightContextOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Generates an in-page stealth script to patch runtime JavaScript APIs
|
|
30
|
+
* (navigator.webdriver, hardwareConcurrency, deviceMemory, WebGL unmasked vendor/renderer,
|
|
31
|
+
* navigator.userAgentData Client Hints, and Screen dimensions).
|
|
32
|
+
*
|
|
33
|
+
* 🔴 API-level spoofing is not enough on its own: `canvas.toDataURL()` and `gl.readPixels()`
|
|
34
|
+
* still return the **real** framebuffer, so a headless browser exposes SwiftShader while
|
|
35
|
+
* its UA claims a discrete GPU — and every persona on one machine hashes identically.
|
|
36
|
+
* The pixel-protection script is therefore appended here, so existing users get
|
|
37
|
+
* read-back-safe personas without changing their call sites.
|
|
38
|
+
*
|
|
39
|
+
* The noise is seeded from the passport seed: stable per account, different per account.
|
|
40
|
+
*/
|
|
41
|
+
declare function getStealthScript(passport: DevicePassport): string;
|
|
42
|
+
/**
|
|
43
|
+
* Applies stealth injections and extra headers to a Playwright BrowserContext.
|
|
44
|
+
*
|
|
45
|
+
* @param context Playwright BrowserContext instance
|
|
46
|
+
* @param passport DevicePassport instance
|
|
47
|
+
*/
|
|
48
|
+
declare function applyToPlaywrightContext(context: any, passport: DevicePassport): Promise<void>;
|
|
49
|
+
|
|
50
|
+
interface PuppeteerEmulationOptions {
|
|
51
|
+
launchArgs: string[];
|
|
52
|
+
defaultViewport: {
|
|
53
|
+
width: number;
|
|
54
|
+
height: number;
|
|
55
|
+
deviceScaleFactor: number;
|
|
56
|
+
isMobile: boolean;
|
|
57
|
+
hasTouch: boolean;
|
|
58
|
+
};
|
|
59
|
+
userAgent: string;
|
|
60
|
+
locale: string;
|
|
61
|
+
timezoneId: string;
|
|
62
|
+
extraHTTPHeaders: Record<string, string>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Returns Puppeteer launch args and page emulation parameters matching the DevicePassport.
|
|
66
|
+
*/
|
|
67
|
+
declare function getPuppeteerArgs(passport: DevicePassport): PuppeteerEmulationOptions;
|
|
68
|
+
/**
|
|
69
|
+
* Applies stealth injections, headers, viewport, and timezone emulation to a Puppeteer Page.
|
|
70
|
+
*
|
|
71
|
+
* @param page Puppeteer Page instance
|
|
72
|
+
* @param passport DevicePassport instance
|
|
73
|
+
*/
|
|
74
|
+
declare function applyToPuppeteerPage(page: any, passport: DevicePassport): Promise<void>;
|
|
75
|
+
|
|
76
|
+
export { applyToPlaywrightContext as b, applyToPuppeteerPage as c, getPuppeteerArgs as d, getStealthScript as e, getPlaywrightContext as g };
|
|
77
|
+
export type { PlaywrightContextOptions as P, PuppeteerEmulationOptions as a };
|