UNPKG

bgutils-js

Version:

A JavaScript library for interfacing with Botguard.

445 lines (433 loc) 16 kB
/* eslint-disable */ "use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // dist/index.js var index_exports = {}; __export(index_exports, { BG: () => core_exports, BGError: () => BGError, DeferredPromise: () => DeferredPromise, GOOG_API_KEY: () => GOOG_API_KEY, GOOG_BASE_URL: () => GOOG_BASE_URL, USER_AGENT: () => USER_AGENT, YT_BASE_URL: () => YT_BASE_URL, base64ToU8: () => base64ToU8, buildURL: () => buildURL, default: () => index_default, getHeaders: () => getHeaders, isBrowser: () => isBrowser, u8ToBase64: () => u8ToBase64 }); module.exports = __toCommonJS(index_exports); // dist/core/index.js var core_exports = {}; __export(core_exports, { BotGuardClient: () => BotGuardClient, Challenge: () => challengeFetcher_exports, PoToken: () => webPoClient_exports, WebPoMinter: () => WebPoMinter }); // dist/core/challengeFetcher.js var challengeFetcher_exports = {}; __export(challengeFetcher_exports, { create: () => create, descramble: () => descramble, parseChallengeData: () => parseChallengeData }); // dist/utils/constants.js var GOOG_BASE_URL = "https://jnn-pa.googleapis.com"; var YT_BASE_URL = "https://www.youtube.com"; var GOOG_API_KEY = "AIzaSyDyT5W0Jh49F30Pqqtyfdf7pDLFKLJoAnw"; var USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36(KHTML, like Gecko)"; // dist/utils/helpers.js var base64urlCharRegex = /[-_.]/g; var base64urlToBase64Map = { "-": "+", _: "/", ".": "=" }; var DeferredPromise = class { constructor() { this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); } }; var BGError = class extends TypeError { constructor(code, message, info) { super(message); this.name = "BGError"; this.code = code; if (info) this.info = info; } }; function base64ToU8(base64) { let base64Mod; if (base64urlCharRegex.test(base64)) { base64Mod = base64.replace(base64urlCharRegex, function(match) { return base64urlToBase64Map[match]; }); } else { base64Mod = base64; } base64Mod = atob(base64Mod); return new Uint8Array([...base64Mod].map((char) => char.charCodeAt(0))); } function u8ToBase64(u8, base64url = false) { const result = btoa(String.fromCharCode(...u8)); if (base64url) { return result.replace(/\+/g, "-").replace(/\//g, "_"); } return result; } function isBrowser() { var _a, _b, _c; const isBrowser2 = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined" && typeof window.HTMLElement !== "undefined" && typeof window.navigator !== "undefined" && typeof window.getComputedStyle === "function" && typeof window.requestAnimationFrame === "function" && typeof window.matchMedia === "function"; const hasValidWindow = (_c = (_b = (_a = Object.getOwnPropertyDescriptor(globalThis, "window")) == null ? void 0 : _a.get) == null ? void 0 : _b.toString().includes("[native code]")) != null ? _c : false; return isBrowser2 && hasValidWindow; } function getHeaders() { const headers = { "content-type": "application/json+protobuf", "x-goog-api-key": GOOG_API_KEY, "x-user-agent": "grpc-web-javascript/0.1" }; if (!isBrowser()) { headers["user-agent"] = USER_AGENT; } return headers; } function buildURL(endpointName, useYouTubeAPI) { return `${useYouTubeAPI ? YT_BASE_URL : GOOG_BASE_URL}/${useYouTubeAPI ? "api/jnn/v1" : "$rpc/google.internal.waa.v1.Waa"}/${endpointName}`; } // dist/core/challengeFetcher.js async function create(bgConfig, interpreterHash) { const requestKey = bgConfig.requestKey; if (!bgConfig.fetch) throw new BGError("BAD_CONFIG", "No fetch function provided"); const payload = [requestKey]; if (interpreterHash) payload.push(interpreterHash); const response = await bgConfig.fetch(buildURL("Create", bgConfig.useYouTubeAPI), { method: "POST", headers: getHeaders(), body: JSON.stringify(payload) }); if (!response.ok) throw new BGError("REQUEST_FAILED", "Failed to fetch challenge", { status: response.status }); const rawData = await response.json(); return parseChallengeData(rawData); } function parseChallengeData(rawData) { let challengeData = []; if (rawData.length > 1 && typeof rawData[1] === "string") { const descrambled = descramble(rawData[1]); challengeData = JSON.parse(descrambled || "[]"); } else if (rawData.length && typeof rawData[0] === "object") { challengeData = rawData[0]; } const [messageId, wrappedScript, wrappedUrl, interpreterHash, program, globalName, , clientExperimentsStateBlob] = challengeData; const privateDoNotAccessOrElseSafeScriptWrappedValue = Array.isArray(wrappedScript) ? wrappedScript.find((value) => value && typeof value === "string") : null; const privateDoNotAccessOrElseTrustedResourceUrlWrappedValue = Array.isArray(wrappedUrl) ? wrappedUrl.find((value) => value && typeof value === "string") : null; return { messageId, interpreterJavascript: { privateDoNotAccessOrElseSafeScriptWrappedValue, privateDoNotAccessOrElseTrustedResourceUrlWrappedValue }, interpreterHash, program, globalName, clientExperimentsStateBlob }; } function descramble(scrambledChallenge) { const buffer = base64ToU8(scrambledChallenge); if (buffer.length) return new TextDecoder().decode(buffer.map((b) => b + 97)); } // dist/core/webPoClient.js var webPoClient_exports = {}; __export(webPoClient_exports, { decodeColdStartToken: () => decodeColdStartToken, generate: () => generate, generateColdStartToken: () => generateColdStartToken, generatePlaceholder: () => generatePlaceholder }); // dist/core/botGuardClient.js var BotGuardClient = class _BotGuardClient { constructor(options) { this.deferredVmFunctions = new DeferredPromise(); this.defaultTimeout = 3e3; this.userInteractionElement = options.userInteractionElement; this.vm = options.globalObj[options.globalName]; this.program = options.program; } /** * Factory method to create and load a BotGuardClient instance. * @param options - Configuration options for the BotGuardClient. * @returns A promise that resolves to a loaded BotGuardClient instance. */ static async create(options) { return await new _BotGuardClient(options).load(); } async load() { if (!this.vm) throw new BGError("VM_INIT", "VM not found"); if (!this.vm.a) throw new BGError("VM_INIT", "VM init function not found"); const vmFunctionsCallback = (asyncSnapshotFunction, shutdownFunction, passEventFunction, checkCameraFunction) => { this.deferredVmFunctions.resolve({ asyncSnapshotFunction, shutdownFunction, passEventFunction, checkCameraFunction }); }; try { this.syncSnapshotFunction = await this.vm.a(this.program, vmFunctionsCallback, true, this.userInteractionElement, () => { }, [[], []])[0]; } catch (error) { throw new BGError("VM_ERROR", "Could not load program", { error }); } return this; } /** * Takes a snapshot asynchronously. * @returns The snapshot result. * @example * ```ts * const result = await botguard.snapshot({ * contentBinding: { * c: "a=6&a2=10&b=SZWDwKVIuixOp7Y4euGTgwckbJA&c=1729143849&d=1&t=7200&c1a=1&c6a=1&c6b=1&hh=HrMb5mRWTyxGJphDr0nW2Oxonh0_wl2BDqWuLHyeKLo", * e: "ENGAGEMENT_TYPE_VIDEO_LIKE", * encryptedVideoId: "P-vC09ZJcnM" * } * }); * * console.log(result); * ``` */ async snapshot(args, timeout = 3e3) { return await Promise.race([ new Promise(async (resolve, reject) => { const vmFunctions = await this.deferredVmFunctions.promise; if (!vmFunctions.asyncSnapshotFunction) return reject(new BGError("ASYNC_SNAPSHOT", "Asynchronous snapshot function not found")); await vmFunctions.asyncSnapshotFunction((response) => resolve(response), [ args.contentBinding, args.signedTimestamp, args.webPoSignalOutput, args.skipPrivacyBuffer ]); }), new Promise((_, reject) => setTimeout(() => reject(new BGError("TIMEOUT", "VM operation timed out")), timeout)) ]); } /** * Passes an event to the VM. * @throws Error Throws an error if the pass event function is not found. */ async passEvent(args, timeout = this.defaultTimeout) { return await Promise.race([ (async () => { const vmFunctions = await this.deferredVmFunctions.promise; if (!vmFunctions.passEventFunction) throw new BGError("PASS_EVENT", "Pass event function not found"); vmFunctions.passEventFunction(args); })(), new Promise((_, reject) => setTimeout(() => reject(new BGError("TIMEOUT", "VM operation timed out")), timeout)) ]); } /** * Checks the "camera". * @throws Error Throws an error if the check camera function is not found. */ async checkCamera(args, timeout = this.defaultTimeout) { return await Promise.race([ (async () => { const vmFunctions = await this.deferredVmFunctions.promise; if (!vmFunctions.checkCameraFunction) throw new BGError("CHECK_CAMERA", "Check camera function not found"); vmFunctions.checkCameraFunction(args); })(), new Promise((_, reject) => setTimeout(() => reject(new BGError("TIMEOUT", "VM operation timed out")), timeout)) ]); } /** * Shuts down the VM. Taking a snapshot after this will throw an error. * @throws Error Throws an error if the shutdown function is not found. */ async shutdown(timeout = this.defaultTimeout) { return await Promise.race([ (async () => { const vmFunctions = await this.deferredVmFunctions.promise; if (!vmFunctions.shutdownFunction) throw new BGError("SHUTDOWN", "Shutdown function not found"); vmFunctions.shutdownFunction(); })(), new Promise((_, reject) => setTimeout(() => reject(new BGError("TIMEOUT", "VM operation timed out")), timeout)) ]); } /** * Takes a snapshot synchronously. * @returns The snapshot result. * @throws Error Throws an error if the synchronous snapshot function is not found. */ async snapshotSynchronous(args) { if (!this.syncSnapshotFunction) throw new BGError("SYNC_SNAPSHOT", "Synchronous snapshot function not found"); return this.syncSnapshotFunction([ args.contentBinding, args.signedTimestamp, args.webPoSignalOutput, args.skipPrivacyBuffer ]); } }; // dist/core/webPoMinter.js var WebPoMinter = class _WebPoMinter { constructor(mintCallback) { this.mintCallback = mintCallback; } static async create(integrityTokenResponse, webPoSignalOutput) { const getMinter = webPoSignalOutput[0]; if (!getMinter) throw new BGError("VM_ERROR", "PMD:Undefined"); if (!integrityTokenResponse.integrityToken) throw new BGError("INTEGRITY_ERROR", "No integrity token provided", { integrityTokenResponse }); const mintCallback = await getMinter(base64ToU8(integrityTokenResponse.integrityToken)); if (!(mintCallback instanceof Function)) throw new BGError("VM_ERROR", "APF:Failed"); return new _WebPoMinter(mintCallback); } async mintAsWebsafeString(identifier) { const result = await this.mint(identifier); return u8ToBase64(result, true); } async mint(identifier) { const result = await this.mintCallback(new TextEncoder().encode(identifier)); if (!result) throw new BGError("VM_ERROR", "YNJ:Undefined"); if (!(result instanceof Uint8Array)) throw new BGError("VM_ERROR", "ODM:Invalid"); return result; } }; // dist/core/webPoClient.js async function generate(args) { const { program, bgConfig, globalName } = args; const { identifier } = bgConfig; const botguard = await BotGuardClient.create({ program, globalName, globalObj: bgConfig.globalObj }); const webPoSignalOutput = []; const botguardResponse = await botguard.snapshot({ webPoSignalOutput }); const payload = [bgConfig.requestKey, botguardResponse]; const integrityTokenResponse = await bgConfig.fetch(buildURL("GenerateIT", bgConfig.useYouTubeAPI), { method: "POST", headers: getHeaders(), body: JSON.stringify(payload) }); const integrityTokenJson = await integrityTokenResponse.json(); const [integrityToken, estimatedTtlSecs, mintRefreshThreshold, websafeFallbackToken] = integrityTokenJson; const integrityTokenData = { integrityToken, estimatedTtlSecs, mintRefreshThreshold, websafeFallbackToken }; const webPoMinter = await WebPoMinter.create(integrityTokenData, webPoSignalOutput); const poToken = await webPoMinter.mintAsWebsafeString(identifier); return { poToken, integrityTokenData }; } function generateColdStartToken(identifier, clientState) { const encodedIdentifier = new TextEncoder().encode(identifier); if (encodedIdentifier.length > 118) throw new BGError("BAD_INPUT", "Content binding is too long.", { identifierLength: encodedIdentifier.length }); const timestamp = Math.floor(Date.now() / 1e3); const randomKeys = [Math.floor(Math.random() * 256), Math.floor(Math.random() * 256)]; const header = randomKeys.concat([ 0, clientState != null ? clientState : 1 ], [ timestamp >> 24 & 255, timestamp >> 16 & 255, timestamp >> 8 & 255, timestamp & 255 ]); const packet = new Uint8Array(2 + header.length + encodedIdentifier.length); packet[0] = 34; packet[1] = header.length + encodedIdentifier.length; packet.set(header, 2); packet.set(encodedIdentifier, 2 + header.length); const payload = packet.subarray(2); const keyLength = randomKeys.length; for (let i = keyLength; i < payload.length; i++) { payload[i] ^= payload[i % keyLength]; } return u8ToBase64(packet, true); } function generatePlaceholder(identifier, clientState) { return generateColdStartToken(identifier, clientState); } function decodeColdStartToken(token) { const packet = base64ToU8(token); const payloadLength = packet[1]; const totalPacketLength = 2 + payloadLength; if (packet.length !== totalPacketLength) throw new BGError("BAD_INPUT", "Invalid packet length.", { packetLength: packet.length, expectedLength: totalPacketLength }); const payload = packet.subarray(2); const keyLength = 2; for (let i = keyLength; i < payload.length; ++i) { payload[i] ^= payload[i % keyLength]; } const keys = [payload[0], payload[1]]; const unknownVal = payload[2]; const clientState = payload[3]; const timestamp = payload[4] << 24 | payload[5] << 16 | payload[6] << 8 | payload[7]; const date = new Date(timestamp * 1e3); const identifier = new TextDecoder().decode(payload.subarray(8)); return { identifier, timestamp, unknownVal, clientState, keys, date }; } // dist/index.js var index_default = core_exports; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { BG, BGError, DeferredPromise, GOOG_API_KEY, GOOG_BASE_URL, USER_AGENT, YT_BASE_URL, base64ToU8, buildURL, getHeaders, isBrowser, u8ToBase64 }); //# sourceMappingURL=index.cjs.map