|
1 | 1 | <!DOCTYPE html> |
2 | 2 | <html lang="en"><head><title></title><script> |
3 | | -class BotGuardClient { |
4 | | - constructor(options) { |
5 | | - this.userInteractionElement = options.userInteractionElement; |
6 | | - this.vm = options.globalObj[options.globalName]; |
7 | | - this.program = options.program; |
8 | | - this.vmFunctions = {}; |
9 | | - this.syncSnapshotFunction = null; |
10 | | - } |
11 | | - |
12 | | - /** |
13 | | - * Factory method to create and load a BotGuardClient instance. |
14 | | - * @param options - Configuration options for the BotGuardClient. |
15 | | - * @returns A promise that resolves to a loaded BotGuardClient instance. |
16 | | - */ |
17 | | - static async create(options) { |
18 | | - return await new BotGuardClient(options).load(); |
19 | | - } |
20 | | - |
21 | | - async load() { |
22 | | - if (!this.vm) |
23 | | - throw new Error('[BotGuardClient]: VM not found in the global object'); |
24 | | - |
25 | | - if (!this.vm.a) |
26 | | - throw new Error('[BotGuardClient]: Could not load program'); |
27 | | - |
28 | | - const vmFunctionsCallback = ( |
29 | | - asyncSnapshotFunction, |
30 | | - shutdownFunction, |
31 | | - passEventFunction, |
32 | | - checkCameraFunction |
33 | | - ) => { |
34 | | - this.vmFunctions = { |
35 | | - asyncSnapshotFunction: asyncSnapshotFunction, |
36 | | - shutdownFunction: shutdownFunction, |
37 | | - passEventFunction: passEventFunction, |
38 | | - checkCameraFunction: checkCameraFunction |
39 | | - }; |
40 | | - }; |
41 | | - |
42 | | - try { |
43 | | - this.syncSnapshotFunction = await this.vm.a(this.program, vmFunctionsCallback, true, this.userInteractionElement, () => {/** no-op */ }, [ [], [] ])[0]; |
44 | | - } catch (error) { |
45 | | - throw new Error(`[BotGuardClient]: Failed to load program (${error.message})`); |
46 | | - } |
47 | | - |
48 | | - // an asynchronous function runs in the background and it will eventually call |
49 | | - // `vmFunctionsCallback`, however we need to manually tell JavaScript to pass |
50 | | - // control to the things running in the background by interrupting this async |
51 | | - // function in any way, e.g. with a delay of 1ms. The loop is most probably not |
52 | | - // needed but is there just because. |
53 | | - for (let i = 0; i < 10000 && !this.vmFunctions.asyncSnapshotFunction; ++i) { |
54 | | - await new Promise(f => setTimeout(f, 1)) |
55 | | - } |
56 | | - |
57 | | - return this; |
58 | | - } |
59 | | - |
60 | | - /** |
61 | | - * Takes a snapshot asynchronously. |
62 | | - * @returns The snapshot result. |
63 | | - * @example |
64 | | - * ```ts |
65 | | - * const result = await botguard.snapshot({ |
66 | | - * contentBinding: { |
67 | | - * c: "a=6&a2=10&b=SZWDwKVIuixOp7Y4euGTgwckbJA&c=1729143849&d=1&t=7200&c1a=1&c6a=1&c6b=1&hh=HrMb5mRWTyxGJphDr0nW2Oxonh0_wl2BDqWuLHyeKLo", |
68 | | - * e: "ENGAGEMENT_TYPE_VIDEO_LIKE", |
69 | | - * encryptedVideoId: "P-vC09ZJcnM" |
70 | | - * } |
71 | | - * }); |
72 | | - * |
73 | | - * console.log(result); |
74 | | - * ``` |
75 | | - */ |
76 | | - async snapshot(args) { |
77 | | - return new Promise((resolve, reject) => { |
78 | | - if (!this.vmFunctions.asyncSnapshotFunction) |
79 | | - return reject(new Error('[BotGuardClient]: Async snapshot function not found')); |
80 | | - |
81 | | - this.vmFunctions.asyncSnapshotFunction((response) => resolve(response), [ |
82 | | - args.contentBinding, |
83 | | - args.signedTimestamp, |
84 | | - args.webPoSignalOutput, |
85 | | - args.skipPrivacyBuffer |
86 | | - ]); |
87 | | - }); |
88 | | - } |
89 | | -} |
90 | 3 | /** |
91 | | - * Parses the challenge data from the provided response data. |
| 4 | + * Factory method to create and load a BotGuardClient instance. |
| 5 | + * @param options - Configuration options for the BotGuardClient. |
| 6 | + * @returns A promise that resolves to a loaded BotGuardClient instance. |
92 | 7 | */ |
93 | | -function parseChallengeData(rawData) { |
94 | | - let challengeData = []; |
95 | | - |
96 | | - if (rawData.length > 1 && typeof rawData[1] === 'string') { |
97 | | - const descrambled = descramble(rawData[1]); |
98 | | - challengeData = JSON.parse(descrambled || '[]'); |
99 | | - } else if (rawData.length && typeof rawData[0] === 'object') { |
100 | | - challengeData = rawData[0]; |
101 | | - } |
102 | | - |
103 | | - const [ messageId, wrappedScript, wrappedUrl, interpreterHash, program, globalName, , clientExperimentsStateBlob ] = challengeData; |
104 | | - |
105 | | - const privateDoNotAccessOrElseSafeScriptWrappedValue = Array.isArray(wrappedScript) ? wrappedScript.find((value) => value && typeof value === 'string') : null; |
106 | | - const privateDoNotAccessOrElseTrustedResourceUrlWrappedValue = Array.isArray(wrappedUrl) ? wrappedUrl.find((value) => value && typeof value === 'string') : null; |
107 | | - |
108 | | - return { |
109 | | - messageId, |
110 | | - interpreterJavascript: { |
111 | | - privateDoNotAccessOrElseSafeScriptWrappedValue, |
112 | | - privateDoNotAccessOrElseTrustedResourceUrlWrappedValue |
113 | | - }, |
114 | | - interpreterHash, |
115 | | - program, |
116 | | - globalName, |
117 | | - clientExperimentsStateBlob |
| 8 | +function loadBotGuard(challengeData) { |
| 9 | + this.vm = this[challengeData.globalName]; |
| 10 | + this.program = challengeData.program; |
| 11 | + this.vmFunctions = {}; |
| 12 | + this.syncSnapshotFunction = null; |
| 13 | + |
| 14 | + if (!this.vm) |
| 15 | + throw new Error('[BotGuardClient]: VM not found in the global object'); |
| 16 | + |
| 17 | + if (!this.vm.a) |
| 18 | + throw new Error('[BotGuardClient]: Could not load program'); |
| 19 | + |
| 20 | + const vmFunctionsCallback = function ( |
| 21 | + asyncSnapshotFunction, |
| 22 | + shutdownFunction, |
| 23 | + passEventFunction, |
| 24 | + checkCameraFunction |
| 25 | + ) { |
| 26 | + this.vmFunctions = { |
| 27 | + asyncSnapshotFunction: asyncSnapshotFunction, |
| 28 | + shutdownFunction: shutdownFunction, |
| 29 | + passEventFunction: passEventFunction, |
| 30 | + checkCameraFunction: checkCameraFunction |
| 31 | + }; |
118 | 32 | }; |
| 33 | + |
| 34 | + this.syncSnapshotFunction = this.vm.a(this.program, vmFunctionsCallback, true, this.userInteractionElement, function () {/** no-op */ }, [ [], [] ])[0] |
| 35 | + |
| 36 | + // an asynchronous function runs in the background and it will eventually call |
| 37 | + // `vmFunctionsCallback`, however we need to manually tell JavaScript to pass |
| 38 | + // control to the things running in the background by interrupting this async |
| 39 | + // function in any way, e.g. with a delay of 1ms. The loop is most probably not |
| 40 | + // needed but is there just because. |
| 41 | + return new Promise(function (resolve, reject) { |
| 42 | + i = 0 |
| 43 | + refreshIntervalId = setInterval(function () { |
| 44 | + if (!!this.vmFunctions.asyncSnapshotFunction) { |
| 45 | + resolve(this) |
| 46 | + clearInterval(refreshIntervalId); |
| 47 | + } |
| 48 | + if (i >= 10000) { |
| 49 | + reject("asyncSnapshotFunction is null even after 10 seconds") |
| 50 | + clearInterval(refreshIntervalId); |
| 51 | + } |
| 52 | + i += 1; |
| 53 | + }, 1); |
| 54 | + }) |
119 | 55 | } |
120 | 56 |
|
121 | 57 | /** |
122 | | - * Descrambles the given challenge data. |
| 58 | + * Takes a snapshot asynchronously. |
| 59 | + * @returns The snapshot result. |
| 60 | + * @example |
| 61 | + * ```ts |
| 62 | + * const result = await botguard.snapshot({ |
| 63 | + * contentBinding: { |
| 64 | + * c: "a=6&a2=10&b=SZWDwKVIuixOp7Y4euGTgwckbJA&c=1729143849&d=1&t=7200&c1a=1&c6a=1&c6b=1&hh=HrMb5mRWTyxGJphDr0nW2Oxonh0_wl2BDqWuLHyeKLo", |
| 65 | + * e: "ENGAGEMENT_TYPE_VIDEO_LIKE", |
| 66 | + * encryptedVideoId: "P-vC09ZJcnM" |
| 67 | + * } |
| 68 | + * }); |
| 69 | + * |
| 70 | + * console.log(result); |
| 71 | + * ``` |
123 | 72 | */ |
124 | | -function descramble(scrambledChallenge) { |
125 | | - const buffer = base64ToU8(scrambledChallenge); |
126 | | - if (buffer.length) |
127 | | - return new TextDecoder().decode(buffer.map((b) => b + 97)); |
128 | | -} |
129 | | - |
130 | | -const base64urlCharRegex = /[-_.]/g; |
131 | | - |
132 | | -const base64urlToBase64Map = { |
133 | | - '-': '+', |
134 | | - _: '/', |
135 | | - '.': '=' |
136 | | -}; |
137 | | - |
138 | | -function base64ToU8(base64) { |
139 | | - let base64Mod; |
140 | | - |
141 | | - if (base64urlCharRegex.test(base64)) { |
142 | | - base64Mod = base64.replace(base64urlCharRegex, function (match) { |
143 | | - return base64urlToBase64Map[match]; |
144 | | - }); |
145 | | - } else { |
146 | | - base64Mod = base64; |
147 | | - } |
148 | | - |
149 | | - base64Mod = atob(base64Mod); |
150 | | - |
151 | | - return new Uint8Array( |
152 | | - [ ...base64Mod ].map( |
153 | | - (char) => char.charCodeAt(0) |
154 | | - ) |
155 | | - ); |
156 | | -} |
157 | | - |
158 | | -function u8ToBase64(u8, base64url = false) { |
159 | | - const result = btoa(String.fromCharCode(...u8)); |
160 | | - |
161 | | - if (base64url) { |
162 | | - return result |
163 | | - .replace(/\+/g, '-') |
164 | | - .replace(/\//g, '_'); |
165 | | - } |
166 | | - |
167 | | - return result; |
| 73 | +function snapshot(args) { |
| 74 | + return new Promise(function (resolve, reject) { |
| 75 | + if (!this.vmFunctions.asyncSnapshotFunction) |
| 76 | + return reject(new Error('[BotGuardClient]: Async snapshot function not found')); |
| 77 | + |
| 78 | + this.vmFunctions.asyncSnapshotFunction(function (response) { resolve(response) }, [ |
| 79 | + args.contentBinding, |
| 80 | + args.signedTimestamp, |
| 81 | + args.webPoSignalOutput, |
| 82 | + args.skipPrivacyBuffer |
| 83 | + ]); |
| 84 | + }); |
168 | 85 | } |
169 | 86 |
|
170 | | -async function runBotGuard(rawChallengeData) { |
171 | | - const challengeData = parseChallengeData(rawChallengeData) |
| 87 | +function runBotGuard(challengeData) { |
172 | 88 | const interpreterJavascript = challengeData.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue; |
173 | 89 |
|
174 | 90 | if (interpreterJavascript) { |
175 | 91 | new Function(interpreterJavascript)(); |
176 | 92 | } else throw new Error('Could not load VM'); |
177 | 93 |
|
178 | | - const botguard = await BotGuardClient.create({ |
| 94 | + const webPoSignalOutput = []; |
| 95 | + return loadBotGuard({ |
179 | 96 | globalName: challengeData.globalName, |
180 | 97 | globalObj: this, |
181 | 98 | program: challengeData.program |
182 | | - }); |
183 | | - |
184 | | - const webPoSignalOutput = []; |
185 | | - const botguardResponse = await botguard.snapshot({ webPoSignalOutput }); |
186 | | - return { webPoSignalOutput, botguardResponse } |
| 99 | + }).then(function (botguard) { |
| 100 | + return botguard.snapshot({ webPoSignalOutput: webPoSignalOutput }) |
| 101 | + }).then(function (botguardResponse) { |
| 102 | + return { webPoSignalOutput: webPoSignalOutput, botguardResponse: botguardResponse } |
| 103 | + }) |
187 | 104 | } |
188 | 105 |
|
189 | | -async function obtainPoToken(webPoSignalOutput, integrityTokenResponse, identifier) { |
190 | | - const integrityToken = integrityTokenResponse[0]; |
| 106 | +function obtainPoToken(webPoSignalOutput, integrityToken, identifier) { |
191 | 107 | const getMinter = webPoSignalOutput[0]; |
192 | 108 |
|
193 | 109 | if (!getMinter) |
194 | 110 | throw new Error('PMD:Undefined'); |
195 | 111 |
|
196 | | - const mintCallback = await getMinter(base64ToU8(integrityToken)); |
| 112 | + const mintCallback = getMinter(integrityToken); |
197 | 113 |
|
198 | 114 | if (!(mintCallback instanceof Function)) |
199 | 115 | throw new Error('APF:Failed'); |
200 | 116 |
|
201 | | - const result = await mintCallback(new TextEncoder().encode(identifier)); |
| 117 | + const result = mintCallback(identifier); |
202 | 118 |
|
203 | 119 | if (!result) |
204 | 120 | throw new Error('YNJ:Undefined'); |
205 | 121 |
|
206 | 122 | if (!(result instanceof Uint8Array)) |
207 | 123 | throw new Error('ODM:Invalid'); |
208 | 124 |
|
209 | | - return u8ToBase64(result, true); |
| 125 | + return result; |
210 | 126 | } |
211 | 127 | </script></head><body></body></html> |
0 commit comments