Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/full-client/test/html/worker.js
1029 views
1
function logError() {
2
// eslint-disable-next-line no-console,prefer-rest-params
3
console.error(...arguments);
4
}
5
(async () => {
6
const getWorkerData = async () => {
7
let canvas2d;
8
let webglVendor;
9
let webglRenderer;
10
let webgl2Vendor;
11
let webgl2Renderer;
12
try {
13
const canvasOffscreen = new OffscreenCanvas(500, 200);
14
canvasOffscreen.getContext('2d');
15
const getDataURI = async () => {
16
const blob = await canvasOffscreen.convertToBlob();
17
const reader = new FileReader();
18
reader.readAsDataURL(blob);
19
return new Promise(resolve => {
20
reader.onloadend = () => resolve(reader.result);
21
});
22
};
23
canvas2d = await getDataURI();
24
25
const canvasOffscreenWebgl = new OffscreenCanvas(256, 256);
26
const contextWebgl = canvasOffscreenWebgl.getContext('webgl');
27
const renererInfo = contextWebgl.getExtension('WEBGL_debug_renderer_info');
28
webglVendor = contextWebgl.getParameter(renererInfo.UNMASKED_VENDOR_WEBGL);
29
webglRenderer = contextWebgl.getParameter(renererInfo.UNMASKED_RENDERER_WEBGL);
30
try {
31
const canvasOffscreenWebgl2 = new OffscreenCanvas(256, 256);
32
const contextWebgl2 = canvasOffscreenWebgl2.getContext('webgl2');
33
const renerer2Info = contextWebgl2.getExtension('WEBGL_debug_renderer_info');
34
webgl2Vendor = contextWebgl2.getParameter(renerer2Info.UNMASKED_VENDOR_WEBGL);
35
webgl2Renderer = contextWebgl2.getParameter(renerer2Info.UNMASKED_RENDERER_WEBGL);
36
} catch (error) {
37
logError(error);
38
}
39
} catch (error) {
40
logError(error);
41
}
42
43
const timezoneLocation = Intl.DateTimeFormat().resolvedOptions().timeZone;
44
const { deviceMemory, hardwareConcurrency, language, platform, userAgent } = navigator;
45
return {
46
timezoneLocation,
47
language,
48
deviceMemory,
49
hardwareConcurrency,
50
userAgent,
51
platform,
52
canvas2d,
53
webglVendor,
54
webglRenderer,
55
webgl2Vendor,
56
webgl2Renderer,
57
};
58
};
59
60
// Tests
61
// eslint-disable-next-line no-undef
62
const isWorker = !globalThis.document && !!globalThis.WorkerGlobalScope;
63
// eslint-disable-next-line no-undef
64
const isSharedWorker = !!globalThis.SharedWorkerGlobalScope;
65
// eslint-disable-next-line no-undef
66
const isServiceWorker = !!globalThis.ServiceWorkerGlobalScope;
67
68
// WorkerGlobalScope
69
const getWorkerGlobalScope = async () => {
70
const data = await getWorkerData();
71
postMessage(data);
72
// eslint-disable-next-line no-restricted-globals
73
close();
74
};
75
76
const getDedicatedWorker = phantomDarkness => {
77
return new Promise(resolve => {
78
try {
79
if (phantomDarkness && !phantomDarkness.Worker) {
80
return resolve({});
81
}
82
if (phantomDarkness && phantomDarkness.Worker.prototype.constructor.name !== 'Worker') {
83
throw new Error('Worker tampered with by client');
84
}
85
const WorkerSConstructor = phantomDarkness ? phantomDarkness.Worker : Worker;
86
const dedicatedWorker = new WorkerSConstructor(document.currentScript.src);
87
dedicatedWorker.onmessage = message => {
88
dedicatedWorker.terminate();
89
return resolve(message.data);
90
};
91
} catch (error) {
92
logError(error);
93
return resolve({});
94
}
95
});
96
};
97
98
// SharedWorkerGlobalScope
99
const getSharedWorkerGlobalScope = () => {
100
// eslint-disable-next-line no-undef
101
onconnect = async message => {
102
const port = message.ports[0];
103
const data = await getWorkerData();
104
port.postMessage(data);
105
};
106
};
107
108
const getSharedWorker = phantomDarkness => {
109
return new Promise(resolve => {
110
try {
111
if (phantomDarkness && !phantomDarkness.SharedWorker) {
112
return resolve({});
113
}
114
if (
115
phantomDarkness &&
116
phantomDarkness.SharedWorker.prototype.constructor.name !== 'SharedWorker'
117
) {
118
throw new Error('SharedWorker tampered with by client');
119
}
120
const WorkerSConstructor = phantomDarkness ? phantomDarkness.SharedWorker : SharedWorker;
121
const sharedWorker = new WorkerSConstructor(document.currentScript.src);
122
sharedWorker.port.start();
123
sharedWorker.port.addEventListener('message', message => {
124
sharedWorker.port.close();
125
return resolve(message.data);
126
});
127
} catch (error) {
128
logError(error);
129
return resolve({});
130
}
131
});
132
};
133
134
// ServiceWorkerGlobalScope
135
const getServiceWorkerGlobalScope = () => {
136
const broadcast = new BroadcastChannel('creep_service');
137
broadcast.onmessage = async event => {
138
if (event.data && event.data.type === 'fingerprint') {
139
const data = await getWorkerData();
140
broadcast.postMessage(data);
141
}
142
};
143
};
144
145
const getServiceWorker = () => {
146
return new Promise(resolve => {
147
try {
148
if (!('serviceWorker' in navigator)) {
149
return resolve({});
150
}
151
// eslint-disable-next-line no-proto
152
if (navigator.serviceWorker.__proto__.constructor.name !== 'ServiceWorkerContainer') {
153
throw new Error('ServiceWorkerContainer tampered with by client');
154
}
155
navigator.serviceWorker
156
.register(document.currentScript.src, {
157
// scope: 'tests/',
158
})
159
.catch(error => {
160
logError(error);
161
return resolve({});
162
});
163
navigator.serviceWorker.ready
164
.then(registration => {
165
const broadcast = new BroadcastChannel('creep_service');
166
broadcast.onmessage = message => {
167
registration.unregister();
168
broadcast.close();
169
return resolve(message.data);
170
};
171
return broadcast.postMessage({ type: 'fingerprint' });
172
})
173
.catch(error => {
174
logError(error);
175
return resolve({});
176
});
177
} catch (error) {
178
logError(error);
179
return resolve({});
180
}
181
});
182
};
183
184
// WorkerGlobalScope
185
if (isWorker) {
186
// eslint-disable-next-line no-nested-ternary
187
return isServiceWorker
188
? getServiceWorkerGlobalScope()
189
: isSharedWorker
190
? getSharedWorkerGlobalScope()
191
: getWorkerGlobalScope();
192
}
193
194
// Window
195
// frame
196
const ghost = () => `
197
height: 100vh;
198
width: 100vw;
199
position: absolute;
200
left:-10000px;
201
visibility: hidden;
202
`;
203
const getRandomValues = () => {
204
const id = [...crypto.getRandomValues(new Uint32Array(10))].map(n => n.toString(36)).join('');
205
return id;
206
};
207
const getPhantomIframe = () => {
208
try {
209
const numberOfIframes = window.length;
210
const frag = new DocumentFragment();
211
const div = document.createElement('div');
212
const id = getRandomValues();
213
div.setAttribute('id', id);
214
frag.appendChild(div);
215
div.innerHTML = `<div style="${ghost()}"><iframe></iframe></div>`;
216
document.body.appendChild(frag);
217
const iframeWindow = window[numberOfIframes];
218
return { iframeWindow, div };
219
} catch (error) {
220
logError('getPhantomIframe', error);
221
return { iframeWindow: window, div: undefined };
222
}
223
};
224
const { iframeWindow: phantomDarkness, div: parentPhantom } = getPhantomIframe();
225
226
const [windowScope, dedicatedWorker, sharedWorker, serviceWorker] = await Promise.all([
227
getWorkerData(),
228
getDedicatedWorker(phantomDarkness),
229
getSharedWorker(phantomDarkness),
230
getServiceWorker(),
231
]).catch(error => {
232
logError(error.message);
233
});
234
235
if (parentPhantom) {
236
parentPhantom.parentNode.removeChild(parentPhantom);
237
}
238
239
await fetch('/worker-result', {
240
method: 'POST',
241
body: JSON.stringify({ windowScope, dedicatedWorker, sharedWorker, serviceWorker }),
242
});
243
})();
244
245