mono/reference/tiktok/files/index_deobfuscated.js
2026-01-29 18:35:51 +01:00

693 lines
20 KiB
JavaScript

/**
* TikTok Privacy and Network Security (PNS) Runtime - Deobfuscated
* Original file: index.js
*
* This is TikTok's Privacy and Network Security (PNS) system that:
* - Controls web API usage and ensures compliance with privacy policies
* - Monitors and intercepts network requests for security
* - Implements cookie consent management
* - Provides comprehensive analytics and tracking
* - Manages service worker communication
* - Enforces content security policies
*/
"use strict";
/**
* Core Constants and Event Types
*/
const EVENT_TYPES = {
MAIN_THREAD: "main_thread",
OUT_APP: "out_app",
COOKIE_SET_BY_DOCUMENT: "cookie_set_by_document",
COOKIE_BLOCKED_ON_START: "cookie_blocked_on_start",
GENERAL_FETCH: "general_fetch",
REQUEST_LOG: "request_log",
WEBAPI: "webapi",
STORAGE_USE: "storage_use",
SW_INCOMPAT: "sw_incompat",
READY_FOR_MSG: "ready_for_msg",
FORCE_UPDATE_SW: "force_update_sw",
FREQUENCY: "frequency",
COST_TIME: "cost_time",
MAIN_THREAD_CTX: "main_thread_ctx",
NETWORK_RULE: "network_rule"
};
const SW_EVENTS = {
RUNTIME_SW_EVENT: "__PNS_RUNTIME_SW_EVENT__",
RUNTIME_SE_ERROR: "__PNS_RUNTIME_SE_ERROR__",
RUNTIME: "__PNS_RUNTIME__"
};
/**
* Global Runtime Instance Manager
* Creates and manages the global PNS runtime instance
*/
function createGlobalRuntime(globalName = getGlobalName()) {
let runtime = globalThis[globalName];
if (!runtime) {
runtime = {
pendingEvents: [],
pendingConfig: {},
pendingListeners: {},
errors: [],
/**
* Push event to pending queue
*/
pushEvent: function(eventName, eventDetail = null, source = EVENT_TYPES.MAIN_THREAD, options) {
addToQueue(runtime.pendingEvents, {
eventName,
eventDetail,
source,
options
}, 100);
},
/**
* Push error to error queue
*/
pushError: function(error) {
addToQueue(runtime.errors, error, 20);
},
pageContextObservers: []
};
globalThis[globalName] = runtime;
}
return runtime;
}
/**
* Get global name from script parameters or use default
*/
function getGlobalName() {
const scriptSrc = document.currentScript?.src;
try {
const url = new URL(scriptSrc);
return url.searchParams.get("globalName") || SW_EVENTS.RUNTIME;
} catch (error) {
return SW_EVENTS.RUNTIME;
}
}
/**
* Add item to queue with size limit
*/
function addToQueue(queue, item, maxSize) {
queue.splice(0, queue.length - maxSize + 1);
queue.push(item);
}
/**
* Privacy and Network Security Core Classes
*/
/**
* Cookie Consent Manager
* Handles cookie blocking and consent management
*/
class CookieConsentManager {
constructor(config) {
this.config = config;
this.blockedCookies = this.getBlockedCookies(config.blockers);
}
/**
* Get list of cookies to block based on domain matching
*/
getBlockedCookies(blockers = []) {
for (const blocker of blockers) {
const { domains = [], cookies = [] } = blocker;
if (domains.some(domain => location.hostname.endsWith(domain))) {
return cookies;
}
}
return [];
}
/**
* Hook document.cookie setter to intercept cookie operations
*/
hookCookieSetter(reportCallback) {
const originalDescriptor = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
Object.defineProperty(document, 'cookie', {
set: (value) => {
const cookieData = this.processCookieSet(value, reportCallback);
if (!cookieData._blocked) {
originalDescriptor.set.call(document, value);
}
return cookieData._blocked;
},
get: originalDescriptor.get,
configurable: true
});
}
/**
* Process cookie setting with privacy rules
*/
processCookieSet(cookieValue, reportCallback) {
const cookieData = {
rawValue: cookieValue,
name: this.getCookieName(cookieValue),
_time: Date.now(),
_blocked: false,
_sample_rate: this.config.sampleRate,
_stack_rate: 0,
_rule_names: []
};
// Check if cookie should be blocked
cookieData._blocked = this.blockedCookies.includes(cookieData.name);
// Apply privacy rules and report if needed
if (Math.random() < cookieData._sample_rate) {
reportCallback(cookieData);
}
return cookieData;
}
getCookieName(cookieString) {
const name = cookieString.split('=')[0];
return name ? name.trim() : undefined;
}
}
/**
* Network Request Interceptor
* Intercepts and processes all network requests for security and privacy
*/
class NetworkInterceptor {
constructor(config, callbacks) {
this.config = config;
this.callbacks = callbacks;
this.originalFetch = window.fetch;
this.originalXHR = window.XMLHttpRequest;
this.hookFetch();
this.hookXMLHttpRequest();
}
/**
* Hook fetch API for request interception
*/
hookFetch() {
const self = this;
window.fetch = function(...args) {
const request = new Request(...args);
const requestData = self.extractRequestData(request);
// Apply security rules
const processedData = self.applySecurityRules(requestData);
if (processedData._blocked) {
return Promise.resolve(new Response("Request blocked", {
status: 410,
statusText: "Request blocked by privacy policy"
}));
}
// Report request for analytics
self.callbacks.report(processedData);
return self.originalFetch.apply(this, args);
};
}
/**
* Hook XMLHttpRequest for legacy request interception
*/
hookXMLHttpRequest() {
const self = this;
const OriginalXHR = this.originalXHR;
window.XMLHttpRequest = class extends OriginalXHR {
constructor() {
super();
this.__pumbaa_detail = {};
}
open(method, url, ...args) {
this.__pumbaa_detail = {
method: method.toUpperCase(),
request_url: new URL(url, location.href).href,
_request_time: Date.now(),
_blocked: false
};
return super.open(method, url, ...args);
}
send(body) {
const processedData = self.applySecurityRules(this.__pumbaa_detail);
if (processedData._blocked) {
this.status = 410;
this.statusText = "Request blocked";
return;
}
self.callbacks.report(processedData);
return super.send(body);
}
};
}
/**
* Extract request data for processing
*/
extractRequestData(request) {
const url = new URL(request.url);
return {
request_url: request.url,
request_host: url.host,
request_path: url.pathname,
search: url.search,
method: request.method,
headers: this.extractHeaders(request.headers),
_request_time: Date.now(),
_blocked: false,
_sample_rate: 0
};
}
/**
* Extract headers from request
*/
extractHeaders(headers) {
const headerMap = {};
headers.forEach((value, key) => {
headerMap[key] = value;
});
return headerMap;
}
/**
* Apply security and privacy rules to requests
*/
applySecurityRules(requestData) {
// Check against blocklist/allowlist
if (this.isRequestBlocked(requestData.request_url)) {
requestData._blocked = true;
requestData["x-pns-block"] = "1";
}
// Apply URL replacement rules
const modifiedUrl = this.applyUrlReplacement(requestData.request_url);
if (modifiedUrl !== requestData.request_url) {
requestData.request_url = modifiedUrl;
requestData["x-pns-replace"] = "1";
requestData._replaced_fields = ["url"];
}
return requestData;
}
/**
* Check if request should be blocked
*/
isRequestBlocked(url) {
const { blocklist = [], allowlist = [] } = this.config;
// Check blocklist
if (blocklist.some(blocked => url.startsWith(blocked))) {
return true;
}
// Check allowlist (if exists, URL must be in it)
if (allowlist.length > 0) {
return !allowlist.some(allowed => url.startsWith(allowed));
}
return false;
}
/**
* Apply URL replacement rules (e.g., HTTP to HTTPS)
*/
applyUrlReplacement(url) {
const { replace = {} } = this.config;
for (const [pattern, replacement] of Object.entries(replace)) {
if (url.startsWith(pattern)) {
return replacement + url.substring(pattern.length);
}
}
return url;
}
}
/**
* Web API Monitor
* Monitors usage of sensitive web APIs
*/
class WebAPIMonitor {
constructor(config, reportCallback) {
this.config = config;
this.reportCallback = reportCallback;
this.hookSensitiveAPIs();
}
/**
* Hook sensitive web APIs for monitoring
*/
hookSensitiveAPIs() {
const apis = this.config.apis || [];
apis.forEach(api => {
this.hookAPI(api);
});
}
/**
* Hook individual API
*/
hookAPI(apiConfig) {
const { apiObj, apiName, apiType, sampleRate, block } = apiConfig;
const target = this.getAPITarget(apiObj);
if (!target) return;
const originalMethod = target[apiName];
if (typeof originalMethod !== 'function') return;
const self = this;
target[apiName] = function(...args) {
// Report API usage
if (Math.random() < sampleRate) {
self.reportCallback({
apiRule: apiConfig,
args: args,
_blocked: block
});
}
// Block if configured
if (block) {
console.warn(`API ${apiName} blocked by privacy policy`);
return;
}
return originalMethod.apply(this, args);
};
}
/**
* Get API target object
*/
getAPITarget(apiObj) {
if (!apiObj) return window;
const parts = apiObj.split('.');
let target = window;
for (const part of parts) {
target = target[part];
if (!target) return null;
}
return target;
}
}
/**
* Page Context Manager
* Manages page context and navigation tracking
*/
class PageContextManager {
constructor() {
this.observers = [];
this.context = this.buildInitialContext();
this.setupNavigationTracking();
}
/**
* Build initial page context
*/
buildInitialContext() {
const url = new URL(location.href);
return {
url: url.href,
host: url.host,
path: url.pathname,
search: url.search,
hash: url.hash,
region: this.getRegion(),
business: this.getBusiness(),
env: this.getEnvironment()
};
}
/**
* Setup navigation change tracking
*/
setupNavigationTracking() {
// Track popstate events
window.addEventListener('popstate', () => {
this.updateContext(this.buildInitialContext());
});
// Hook history API
['pushState', 'replaceState'].forEach(method => {
const original = History.prototype[method];
History.prototype[method] = function(...args) {
original.apply(this, args);
// Update context after navigation
setTimeout(() => {
this.updateContext(this.buildInitialContext());
}, 0);
};
});
}
/**
* Update page context and notify observers
*/
updateContext(newContext) {
const changes = this.getContextChanges(this.context, newContext);
if (Object.keys(changes).length > 0) {
Object.assign(this.context, changes);
// Notify observers
this.observers.forEach(observer => {
if (!observer.fields || observer.fields.some(field => field in changes)) {
observer.func(this.context);
}
});
}
}
/**
* Get context changes
*/
getContextChanges(oldContext, newContext) {
const changes = {};
for (const key in newContext) {
if (oldContext[key] !== newContext[key]) {
changes[key] = newContext[key];
}
}
return changes;
}
/**
* Add context observer
*/
addObserver(callback, fields) {
this.observers.push({ func: callback, fields });
}
getRegion() {
// Extract region from meta tags or config
return document.querySelector('meta[name="pumbaa-ctx"]')?.content?.region || 'unknown';
}
getBusiness() {
// Extract business context
return document.querySelector('meta[name="pumbaa-web-config"]')?.content?.business || 'tiktok';
}
getEnvironment() {
// Determine environment (prod, staging, dev)
return location.hostname.includes('tiktok.com') ? 'prod' : 'dev';
}
}
/**
* Main PNS Runtime Class
* Orchestrates all privacy and security components
*/
class PNSRuntime {
constructor() {
this.config = this.loadConfiguration();
this.pageContext = new PageContextManager();
this.setupComponents();
}
/**
* Load PNS configuration from embedded script or meta tags
*/
loadConfiguration() {
// Try to load from embedded script tag
const configScript = document.getElementById('pumbaa-rule');
if (configScript) {
try {
return JSON.parse(configScript.textContent);
} catch (error) {
console.warn('Failed to parse PNS config:', error);
}
}
// Fallback to default configuration
return this.getDefaultConfig();
}
/**
* Setup all PNS components
*/
setupComponents() {
const runtime = createGlobalRuntime();
// Setup cookie consent management
if (this.config.cookie?.enabled) {
const cookieManager = new CookieConsentManager(this.config.cookie);
cookieManager.hookCookieSetter((data) => {
runtime.pushEvent(EVENT_TYPES.COOKIE_SET_BY_DOCUMENT, data);
});
}
// Setup network interception
if (this.config.network?.enabled) {
const networkInterceptor = new NetworkInterceptor(this.config.network, {
report: (data) => {
runtime.pushEvent(EVENT_TYPES.GENERAL_FETCH, data);
}
});
}
// Setup web API monitoring
if (this.config.webapi?.enabled) {
const apiMonitor = new WebAPIMonitor(this.config.webapi, (data) => {
runtime.pushEvent(EVENT_TYPES.WEBAPI, data);
});
}
// Setup service worker communication
this.setupServiceWorkerCommunication(runtime);
}
/**
* Setup service worker communication for enhanced security
*/
setupServiceWorkerCommunication(runtime) {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('message', (event) => {
if (event.data.event === SW_EVENTS.RUNTIME_SW_EVENT) {
// Handle service worker events
runtime.pushEvent(event.data.eventName, event.data.data);
}
});
navigator.serviceWorker.ready.then((registration) => {
const sw = registration.active || navigator.serviceWorker.controller;
if (sw) {
// Send configuration to service worker
sw.postMessage({
eventName: EVENT_TYPES.READY_FOR_MSG,
source: EVENT_TYPES.MAIN_THREAD,
data: this.pageContext.context
});
}
});
}
}
/**
* Get default configuration if none provided
*/
getDefaultConfig() {
return {
cookie: {
enabled: true,
sampleRate: 0.07,
blockers: []
},
network: {
enabled: true,
sampleRate: 0.03,
intercept: []
},
webapi: {
enabled: true,
apis: []
}
};
}
}
/**
* Initialize PNS Runtime
* Main entry point that starts the privacy and security system
*/
function initializePNS() {
// Check if already initialized
if (window.__PUMBAA_RUN_FLAG__) {
return;
}
window.__PUMBAA_RUN_FLAG__ = true;
try {
// Initialize the PNS runtime
const pnsRuntime = new PNSRuntime();
console.log('TikTok Privacy and Network Security (PNS) initialized');
// Load core PNS module
const script = document.createElement('script');
script.src = './core.js?globalName=' + getGlobalName();
script.crossOrigin = 'anonymous';
script.async = true;
// Copy dataset from current script
if (document.currentScript) {
Object.assign(script.dataset, document.currentScript.dataset);
}
document.head.appendChild(script);
document.head.removeChild(script);
} catch (error) {
console.error('Failed to initialize PNS:', error);
// Report error to global runtime
const runtime = createGlobalRuntime();
runtime.pushError(error);
}
}
// Auto-initialize if conditions are met
if (typeof window !== 'undefined' &&
window.Symbol &&
!(/ByteLocale/g.test(navigator.userAgent))) {
initializePNS();
}