Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/plugins/default-browser-emulator/injected-scripts/_descriptorBuilder.ts
1029 views
1
const nativeErrorRegex = new RegExp(/^(\w+):\s/);
2
3
const globalSymbols = {};
4
for (const symbol of ReflectCached.ownKeys(Symbol)) {
5
if (typeof Symbol[symbol] === 'symbol') {
6
globalSymbols[`${String(Symbol[symbol])}`] = Symbol[symbol];
7
}
8
}
9
10
function createError(message: string, type?: { new (message: string): any }) {
11
if (!type) {
12
const match = nativeErrorRegex.exec(message);
13
if (match.length) {
14
message = message.replace(`${match[1]}: `, '');
15
try {
16
type = self[match[1]];
17
} catch (err) {
18
// ignore
19
}
20
}
21
}
22
if (!type) type = Error;
23
// eslint-disable-next-line new-cap
24
return new type(message);
25
}
26
27
function newObjectConstructor(newProps: IDescriptor) {
28
return function() {
29
if (newProps._$constructorException) {
30
throw createError(newProps._$constructorException);
31
}
32
Object.setPrototypeOf(this, getObjectAtPath(newProps._$protos[0]));
33
const props = Object.entries(newProps);
34
const obj = {};
35
for (const [prop, value] of props) {
36
if (prop.startsWith('_$')) continue;
37
let propName: string | symbol = prop;
38
if (propName.startsWith('Symbol(')) {
39
propName = Symbol.for(propName.match(/Symbol\((.+)\)/)[1]);
40
}
41
Object.defineProperty(obj, propName, buildDescriptor(value));
42
}
43
return obj;
44
};
45
}
46
47
function buildDescriptor(entry: IDescriptor) {
48
const attrs: PropertyDescriptor = {};
49
const flags = entry._$flags || '';
50
if (flags.includes('c')) attrs.configurable = true;
51
if (flags.includes('w')) attrs.writable = true;
52
if (flags.includes('e')) attrs.enumerable = true;
53
54
if (entry._$get) {
55
attrs.get = new Proxy(Function.prototype.call.bind({}), {
56
apply() {
57
if (entry._$accessException) throw createError(entry._$accessException);
58
if (entry._$value) return entry._$value;
59
if (entry['_$$value()']) return entry['_$$value()']();
60
},
61
});
62
overriddenFns.set(attrs.get, entry._$get);
63
} else if (entry['_$$value()']) {
64
attrs.value = entry['_$$value()']();
65
} else if (entry._$value !== undefined) {
66
attrs.value = entry._$value;
67
}
68
69
if (entry._$set) {
70
attrs.set = new Proxy(Function.prototype.call.bind({}), {
71
apply() {},
72
});
73
overriddenFns.set(attrs.set, entry._$set);
74
}
75
76
if (entry._$function) {
77
const newProps = entry['new()'];
78
if (!newProps) {
79
// use function call just to get a function that doesn't create prototypes on new
80
// bind to an empty object so we don't modify the original
81
attrs.value = new Proxy(Function.prototype.call.bind({}), {
82
apply() {
83
return entry._$invocation;
84
},
85
});
86
} else {
87
attrs.value = newObjectConstructor(newProps);
88
}
89
if (entry._$invocation !== undefined) {
90
Object.setPrototypeOf(attrs.value, Function.prototype);
91
delete attrs.value.prototype;
92
delete attrs.value.constructor;
93
}
94
overriddenFns.set(attrs.value, entry._$function);
95
}
96
97
if (typeof entry === 'object') {
98
const props = Object.entries(entry).filter(([prop]) => !prop.startsWith('_$'));
99
if (!attrs.value && (props.length || entry._$protos)) {
100
attrs.value = {};
101
}
102
if (entry._$protos) {
103
attrs.value = Object.setPrototypeOf(attrs.value, getObjectAtPath(entry._$protos[0]));
104
}
105
106
for (const [prop, value] of props) {
107
if (prop.startsWith('_$')) continue;
108
if (prop === 'arguments' || prop === 'caller') continue;
109
let propName: string | number | symbol = prop;
110
if (propName.startsWith('Symbol(')) {
111
propName = globalSymbols[propName];
112
if (!propName) {
113
const symbolName = (propName as string).match(/Symbol\((.+)\)/)[1];
114
propName = Symbol.for(symbolName);
115
}
116
}
117
const descriptor = buildDescriptor(value);
118
if (propName === 'prototype') {
119
Object.defineProperty(descriptor.value, 'constructor', {
120
// Blake: changed this from props on TS. is it right?
121
value: newObjectConstructor(value),
122
writable: true,
123
enumerable: false,
124
configurable: true,
125
});
126
if (!entry.prototype._$flags || !entry.prototype._$flags.includes('w')) {
127
descriptor.writable = false;
128
}
129
if (entry._$function) {
130
overriddenFns.set(descriptor.value.constructor, entry._$function);
131
}
132
}
133
Object.defineProperty(attrs.value, propName, descriptor);
134
}
135
}
136
137
return attrs;
138
}
139
140
// eslint-disable-next-line @typescript-eslint/no-unused-vars
141
function getParentAndProperty(path: string) {
142
const parts = breakdownPath(path, 1);
143
if (!parts) return undefined;
144
return { parent: parts.parent, property: parts.remainder[0] };
145
}
146
147
function breakdownPath(path: string, propsToLeave) {
148
if (!path || path.startsWith('detached')) {
149
// can't do these yet... need to know how to get to them (ie, super prototype of X)
150
return undefined;
151
}
152
153
const parts = path.split(/\.Symbol\(([\w.]+)\)|\.(\w+)/).filter(Boolean);
154
let obj: any = self;
155
while (parts.length > propsToLeave) {
156
let next: string | symbol = parts.shift();
157
if (next === 'window') continue;
158
if (next.startsWith('Symbol.')) next = Symbol.for(next);
159
obj = obj[next];
160
if (!obj) {
161
throw new Error(`Property not found -> ${path} at ${String(next)}`);
162
}
163
}
164
return { parent: obj, remainder: parts };
165
}
166
167
function getObjectAtPath(path) {
168
const parts = breakdownPath(path, 0);
169
if (!parts) return undefined;
170
return parts.parent;
171
}
172
173
declare interface IDescriptor {
174
_$flags: string;
175
_$type: string;
176
_$get?: any;
177
_$set?: any;
178
_$accessException?: string;
179
_$constructorException?: string;
180
_$value?: string;
181
'_$$value()'?: () => string;
182
_$function?: string;
183
_$invocation?: string;
184
_$protos?: string[];
185
'new()'?: IDescriptor;
186
prototype: IDescriptor;
187
}
188
189