Skip to content

Commit 53b599b

Browse files
committed
Make JavaScript code compatible with older WebViews
1 parent 21df24a commit 53b599b

File tree

4 files changed

+273
-226
lines changed

4 files changed

+273
-226
lines changed

app/src/main/assets/po_token.html

Lines changed: 87 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -1,211 +1,127 @@
11
<!DOCTYPE html>
22
<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-
}
903
/**
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.
927
*/
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+
};
11832
};
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+
})
11955
}
12056

12157
/**
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+
* ```
12372
*/
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+
});
16885
}
16986

170-
async function runBotGuard(rawChallengeData) {
171-
const challengeData = parseChallengeData(rawChallengeData)
87+
function runBotGuard(challengeData) {
17288
const interpreterJavascript = challengeData.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue;
17389

17490
if (interpreterJavascript) {
17591
new Function(interpreterJavascript)();
17692
} else throw new Error('Could not load VM');
17793

178-
const botguard = await BotGuardClient.create({
94+
const webPoSignalOutput = [];
95+
return loadBotGuard({
17996
globalName: challengeData.globalName,
18097
globalObj: this,
18198
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+
})
187104
}
188105

189-
async function obtainPoToken(webPoSignalOutput, integrityTokenResponse, identifier) {
190-
const integrityToken = integrityTokenResponse[0];
106+
function obtainPoToken(webPoSignalOutput, integrityToken, identifier) {
191107
const getMinter = webPoSignalOutput[0];
192108

193109
if (!getMinter)
194110
throw new Error('PMD:Undefined');
195111

196-
const mintCallback = await getMinter(base64ToU8(integrityToken));
112+
const mintCallback = getMinter(integrityToken);
197113

198114
if (!(mintCallback instanceof Function))
199115
throw new Error('APF:Failed');
200116

201-
const result = await mintCallback(new TextEncoder().encode(identifier));
117+
const result = mintCallback(identifier);
202118

203119
if (!result)
204120
throw new Error('YNJ:Undefined');
205121

206122
if (!(result instanceof Uint8Array))
207123
throw new Error('ODM:Invalid');
208124

209-
return u8ToBase64(result, true);
125+
return result;
210126
}
211127
</script></head><body></body></html>

0 commit comments

Comments
 (0)