Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/plugins/default-browser-emulator/injected-scripts/_proxyUtils.ts
1029 views
1
/////// MASK TO STRING ////////////////////////////////////////////////////////////////////////////////////////////////
2
3
// eslint-disable-next-line prefer-const -- must be let: could change for different browser (ie, Safari)
4
let nativeToStringFunctionString = `${Function.toString}`;
5
// when functions are re-bound to work around the loss of scope issue in chromium, they blow up their native toString
6
const overriddenFns = new Map<Function, string>();
7
8
// eslint-disable-next-line no-extend-native
9
Object.defineProperty(Function.prototype, 'toString', {
10
...Object.getOwnPropertyDescriptor(Function.prototype, 'toString'),
11
value: new Proxy(Function.prototype.toString, {
12
apply(target: () => string, thisArg: any, args?: any): any {
13
if (overriddenFns.has(thisArg)) {
14
return overriddenFns.get(thisArg);
15
}
16
// from puppeteer-stealth: Check if the toString prototype of the context is the same as the global prototype,
17
// if not indicates that we are doing a check across different windows
18
const hasSameProto = Object.getPrototypeOf(Function.prototype.toString).isPrototypeOf(
19
thisArg.toString,
20
);
21
if (hasSameProto === false) {
22
// Pass the call on to the local Function.prototype.toString instead
23
return thisArg.toString(...(args ?? []));
24
}
25
try {
26
return target.apply(thisArg, args);
27
} catch (error) {
28
cleanErrorStack(error, (line, i) => {
29
if (line.includes('Object.toString') && i === 1) {
30
return line.replace('Object.toString', 'Function.toString');
31
}
32
return line;
33
});
34
throw error;
35
}
36
},
37
setPrototypeOf(target: any, v: any): boolean {
38
let protoTarget = v;
39
if (v === Function.prototype.toString) {
40
protoTarget = target;
41
}
42
try {
43
return ObjectCached.setPrototypeOf(target, protoTarget);
44
} catch (error) {
45
throw cleanErrorStack(error, null, true);
46
}
47
},
48
}),
49
});
50
overriddenFns.set(Function.prototype.toString, nativeToStringFunctionString);
51
52
/////// END TOSTRING //////////////////////////////////////////////////////////////////////////////////////////////////
53
54
// From puppeteer-stealth: this is to prevent someone snooping at Reflect calls
55
const ReflectCached = {
56
construct: Reflect.construct.bind(Reflect),
57
get: Reflect.get.bind(Reflect),
58
set: Reflect.set.bind(Reflect),
59
apply: Reflect.apply.bind(Reflect),
60
ownKeys: Reflect.ownKeys.bind(Reflect),
61
getOwnPropertyDescriptor: Reflect.getOwnPropertyDescriptor.bind(Reflect),
62
};
63
64
const ObjectCached = {
65
setPrototypeOf: Object.setPrototypeOf.bind(Object),
66
};
67
68
enum ProxyOverride {
69
callOriginal = '_____invoke_original_____',
70
}
71
72
declare let sourceUrl: string;
73
74
function cleanErrorStack(
75
error: Error,
76
replaceLineFn?: (line: string, index: number) => string,
77
startAfterSourceUrl = false,
78
) {
79
if (!error.stack) return error;
80
81
const split = error.stack.includes('\r\n') ? '\r\n' : '\n';
82
const stack = error.stack.split(/\r?\n/);
83
const newStack = [];
84
for (let i = 0; i < stack.length; i += 1) {
85
let line = stack[i];
86
if (line.includes(sourceUrl)) {
87
if (startAfterSourceUrl === true) {
88
newStack.length = 1;
89
}
90
continue;
91
}
92
if (replaceLineFn) line = replaceLineFn(line, i);
93
newStack.push(line);
94
}
95
error.stack = newStack.join(split);
96
return error;
97
}
98
99
function proxyConstructor<T, K extends keyof T>(
100
owner: T,
101
key: K,
102
overrideFn: (
103
target?: T[K],
104
argArray?: T[K] extends new (...args: infer P) => any ? P : never[],
105
newTarget?: T[K],
106
) => (T[K] extends new () => infer Z ? Z : never) | ProxyOverride,
107
) {
108
const descriptor = Object.getOwnPropertyDescriptor(owner, key);
109
const toString = descriptor.value.toString();
110
descriptor.value = new Proxy(descriptor.value, {
111
construct() {
112
try {
113
const result = overrideFn(...arguments);
114
if (result !== ProxyOverride.callOriginal) {
115
return result as any;
116
}
117
} catch (err) {
118
throw cleanErrorStack(err);
119
}
120
return ReflectCached.construct(...arguments);
121
},
122
});
123
overriddenFns.set(descriptor.value, toString);
124
Object.defineProperty(owner, key, descriptor);
125
}
126
127
function proxyFunction<T, K extends keyof T>(
128
thisObject: T,
129
functionName: K,
130
overrideFn: (
131
target?: T[K],
132
thisArg?: T,
133
argArray?: T[K] extends (...args: infer P) => any ? P : never[],
134
) => (T[K] extends (...args: any[]) => infer Z ? Z : never) | ProxyOverride,
135
overrideOnlyForInstance = false,
136
) {
137
const descriptorInHierarchy = getDescriptorInHierarchy(thisObject, functionName);
138
if (!descriptorInHierarchy) {
139
throw new Error(`Could not find descriptor for function: ${functionName}`);
140
}
141
const { descriptorOwner, descriptor } = descriptorInHierarchy;
142
143
const toString = descriptor.value.toString();
144
descriptorOwner[functionName] = new Proxy<any>(descriptorOwner[functionName], {
145
apply(target, thisArg, argArray) {
146
if (overrideOnlyForInstance === false || thisArg === thisObject) {
147
try {
148
const result = overrideFn(...arguments);
149
if (result !== ProxyOverride.callOriginal) {
150
if (result instanceof Promise && result.catch) {
151
return result.catch(err => {
152
throw cleanErrorStack(err);
153
});
154
}
155
return result;
156
}
157
} catch (err) {
158
throw cleanErrorStack(err);
159
}
160
}
161
return ReflectCached.apply(...arguments);
162
},
163
setPrototypeOf(target: any, v: any): boolean {
164
let protoTarget = v;
165
if (v === descriptorOwner[functionName]) {
166
protoTarget = target;
167
}
168
try {
169
return ObjectCached.setPrototypeOf(target, protoTarget);
170
} catch (error) {
171
throw cleanErrorStack(error, null, true);
172
}
173
},
174
});
175
overriddenFns.set(descriptorOwner[functionName] as any, toString);
176
return thisObject[functionName];
177
}
178
179
function proxyGetter<T, K extends keyof T>(
180
thisObject: T,
181
propertyName: K,
182
overrideFn: (target?: T[K], thisArg?: T) => T[K] | ProxyOverride,
183
overrideOnlyForInstance = false,
184
) {
185
const descriptorInHierarchy = getDescriptorInHierarchy(thisObject, propertyName);
186
if (!descriptorInHierarchy) {
187
throw new Error(`Could not find descriptor for getter: ${propertyName}`);
188
}
189
190
const { descriptorOwner, descriptor } = descriptorInHierarchy;
191
const toString = descriptor.get.toString();
192
descriptor.get = new Proxy(descriptor.get, {
193
apply(_, thisArg) {
194
if (overrideOnlyForInstance === false || thisArg === thisObject) {
195
const result = overrideFn(...arguments);
196
if (result !== ProxyOverride.callOriginal) return result;
197
}
198
return ReflectCached.apply(...arguments);
199
},
200
setPrototypeOf(target: any, v: any): boolean {
201
let protoTarget = v;
202
if (v === descriptor.get) {
203
protoTarget = target;
204
}
205
try {
206
return ObjectCached.setPrototypeOf(target, protoTarget);
207
} catch (error) {
208
throw cleanErrorStack(error, null, true);
209
}
210
},
211
});
212
overriddenFns.set(descriptor.get, toString);
213
Object.defineProperty(descriptorOwner, propertyName, descriptor);
214
return descriptor.get;
215
}
216
217
function proxySetter<T, K extends keyof T>(
218
thisObject: T,
219
propertyName: K,
220
overrideFn: (
221
target?: T[K],
222
thisArg?: T,
223
value?: T[K] extends (value: infer P) => any ? P : never,
224
) => void | ProxyOverride,
225
overrideOnlyForInstance = false,
226
) {
227
const descriptorInHierarchy = getDescriptorInHierarchy(thisObject, propertyName);
228
if (!descriptorInHierarchy) {
229
throw new Error(`Could not find descriptor for setter: ${propertyName}`);
230
}
231
const { descriptorOwner, descriptor } = descriptorInHierarchy;
232
const toString = descriptor.set.toString();
233
descriptor.set = new Proxy(descriptor.set, {
234
apply(target, thisArg, args) {
235
if (!overrideOnlyForInstance || thisArg === thisObject) {
236
const result = overrideFn(target as any, thisArg, ...args);
237
if (result !== ProxyOverride.callOriginal) return result;
238
}
239
return ReflectCached.apply(...arguments);
240
},
241
});
242
overriddenFns.set(descriptor.set, toString);
243
Object.defineProperty(descriptorOwner, propertyName, descriptor);
244
return descriptor.set;
245
}
246
247
function getDescriptorInHierarchy<T, K extends keyof T>(obj: T, prop: K) {
248
let proto = obj;
249
do {
250
if (proto.hasOwnProperty(prop)) {
251
return { descriptorOwner: proto, descriptor: Object.getOwnPropertyDescriptor(proto, prop) };
252
}
253
proto = Object.getPrototypeOf(proto);
254
} while (proto);
255
256
return null;
257
}
258
259
// eslint-disable-next-line @typescript-eslint/no-unused-vars
260
function addDescriptorAfterProperty(
261
path: string,
262
prevProperty: string,
263
propertyName: string,
264
descriptor: PropertyDescriptor,
265
) {
266
const owner = getObjectAtPath(path);
267
if (!owner) {
268
console.log(`ERROR: Parent for property descriptor not found: ${path} -> ${propertyName}`);
269
return;
270
}
271
const descriptors = Object.getOwnPropertyDescriptors(owner);
272
// if already exists, don't add again
273
if (descriptors[propertyName]) {
274
return;
275
}
276
277
const inHierarchy = getDescriptorInHierarchy(owner, propertyName);
278
if (inHierarchy && descriptor.value) {
279
if (inHierarchy.descriptor.get) {
280
proxyGetter(owner, propertyName, () => descriptor.value, true);
281
} else {
282
throw new Error("Can't override descriptor that doesnt have a getter");
283
}
284
return;
285
}
286
287
let hasPassedProperty = false;
288
for (const [key, existingDescriptor] of Object.entries(descriptors)) {
289
if (hasPassedProperty) {
290
// only way to reorder properties is to re-add them
291
delete owner[key];
292
Object.defineProperty(owner, key, existingDescriptor);
293
}
294
if (key === prevProperty) {
295
Object.defineProperty(owner, propertyName, descriptor);
296
hasPassedProperty = true;
297
}
298
}
299
}
300
301
if (typeof module === 'object' && typeof module.exports === 'object') {
302
module.exports = {
303
proxyFunction,
304
};
305
}
306
307