@trustcaptcha/trustcaptcha-frontend
Version:
Frondend library for trustcaptcha
1,232 lines (1,218 loc) • 141 kB
JavaScript
import { h, r as registerInstance, c as createEvent, H as Host, g as getElement } from './index-32c1cf48.js';
class ApiService {
static async verifyUser(config, body, signal) {
const url = `${config.trustcaptchaApi}/verifications?siteKey=${config.sitekey}`;
return await this.post(url, body, signal);
}
static async sendSolutions(config, verificationId, startSolvingTimestamp, solvedTimestamp, tasks, honeypotField, userEvents, signal) {
const url = `${config.trustcaptchaApi}/verifications/${verificationId}/challenges`;
const body = { startSolvingTimestamp, solvedTimestamp, tasks, honeypotField, userEvents };
return await this.post(url, body, signal);
}
static async post(url = '', body = {}, signal) {
return await fetch(url, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow',
referrerPolicy: 'no-referrer',
body: JSON.stringify(body),
signal,
});
}
}
const powWorkerScript = `
var __awaiter=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(a,o){function i(e){try{c(r.next(e))}catch(e){o(e)}}function u(e){try{c(r.throw(e))}catch(e){o(e)}}function c(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,u)}c((r=r.apply(e,t||[])).next())}))},__generator=this&&this.__generator||function(e,t){var n,r,a,o,i={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]};return o={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function u(u){return function(c){return function(u){if(n)throw new TypeError("Generator is already executing.");for(;o&&(o=0,u[0]&&(i=0)),i;)try{if(n=1,r&&(a=2&u[0]?r.return:u[0]?r.throw||((a=r.return)&&a.call(r),0):r.next)&&!(a=a.call(r,u[1])).done)return a;switch(r=0,a&&(u=[2&u[0],a.value]),u[0]){case 0:case 1:a=u;break;case 4:return i.label++,{value:u[1],done:!1};case 5:i.label++,r=u[1],u=[0];continue;case 7:u=i.ops.pop(),i.trys.pop();continue;default:if(!(a=i.trys,(a=a.length>0&&a[a.length-1])||6!==u[0]&&2!==u[0])){i=0;continue}if(3===u[0]&&(!a||u[1]>a[0]&&u[1]<a[3])){i.label=u[1];break}if(6===u[0]&&i.label<a[1]){i.label=a[1],a=u;break}if(a&&i.label<a[2]){i.label=a[2],i.ops.push(u);break}a[2]&&i.ops.pop(),i.trys.pop();continue}u=t.call(e,i)}catch(e){u=[6,e],r=0}finally{n=a=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,c])}}},_this=this;function base64ToArrayBuffer(e){for(var t=self.atob(e),n=t.length,r=new Uint8Array(n),a=0;a<n;a++)r[a]=t.charCodeAt(a);return r.buffer}function appendBuffer(e,t){var n=new Uint8Array(e.byteLength+t.byteLength);return n.set(new Uint8Array(e),0),n.set(new Uint8Array(t),e.byteLength),n.buffer}function countLeadingZeroBits(e){var t=new Uint8Array(e),n=t.findIndex((function(e){return 0!==e}));return-1===n?8*t.length:8*n+(Math.clz32(t[n])-24)}function executePowTask(e,t){return __awaiter(this,void 0,void 0,(function(){var n,r,a,o;return __generator(this,(function(i){switch(i.label){case 0:n=base64ToArrayBuffer(e.input),r=0,i.label=1;case 1:return a="tcn".concat(r),o=appendBuffer(n,(new TextEncoder).encode(a).buffer),[4,crypto.subtle.digest("SHA-256",o)];case 2:return countLeadingZeroBits(i.sent())===t?[2,{number:e.number,nonce:a}]:(r++,[3,1]);case 3:return[2]}}))}))}self.addEventListener("message",(function(e){return __awaiter(_this,void 0,void 0,(function(){var t,n,r,a;return __generator(this,(function(o){switch(o.label){case 0:return t=e.data,n=t.task,r=t.difficulty,a=postMessage,[4,executePowTask(n,r)];case 1:return a.apply(void 0,[o.sent()]),[2]}}))}))}));
`;
class WorkerPool {
constructor() {
this.queue = [];
this.activeTasks = 0;
this.workers = [];
this.isReset = false;
const blob = new Blob([powWorkerScript], { type: "application/javascript" });
this.objectUrl = URL.createObjectURL(blob);
this.maxWorkers = Math.min(Math.max(4, navigator.hardwareConcurrency || 4), 10);
}
enqueue(task, difficulty) {
return new Promise((resolve, reject) => {
if (this.isReset) {
reject(new Error("WorkerPool has been reset"));
return;
}
this.queue.push({ task, difficulty, resolve, reject });
this.processQueue();
});
}
processQueue() {
if (this.queue.length === 0 || this.activeTasks >= this.maxWorkers || this.isReset) {
return;
}
const { task, difficulty, resolve, reject } = this.queue.shift();
this.activeTasks++;
const worker = new Worker(this.objectUrl);
this.workers.push(worker);
worker.postMessage({ task, difficulty });
worker.onmessage = (event) => {
if (this.isReset) {
reject(new Error("WorkerPool has been reset"));
}
else {
resolve(event.data);
}
this.activeTasks--;
this.workers.splice(this.workers.indexOf(worker), 1);
worker.terminate();
this.processQueue();
};
worker.onerror = () => {
if (!this.isReset) {
reject(new Error("Worker error"));
}
else {
reject(new Error("WorkerPool has been reset"));
}
this.activeTasks--;
this.workers.splice(this.workers.indexOf(worker), 1);
worker.terminate();
this.processQueue();
};
}
reset() {
this.isReset = true;
// Terminate all active workers
for (const worker of this.workers) {
worker.terminate();
}
this.workers = [];
// Clear the queue
this.queue = [];
}
}
class PowSolver {
constructor() {
this.workerPool = new WorkerPool();
}
async solveTasks(userResponseDto) {
const promises = userResponseDto.challenge.tasks.map(task => this.workerPool.enqueue(task, userResponseDto.challenge.difficulty));
return Promise.all(promises);
}
reset() {
this.workerPool.reset();
}
}
const VERSION = '1.8.4';
class InformationCollector {
constructor(box) {
this.box = box;
}
async collectInformation() {
const browserInformation = await this.collectProperties(this.box.config.mode === 'minimal'
? this.minimalPropertiesToCollect()
: this.standardPropertiesToCollect());
let userEvents = [];
if (this.box.config.mode === 'standard') {
userEvents = this.box.eventTracker.getEventsAndResetArray();
}
return {
custom: {
boxCreationTimestamp: this.box.boxCreationTimestamp,
startSolvingTimestamp: new Date(),
timezone: this.getTimezone(),
honeypotField: this.box.honeypotField.value,
framework: this.safeGet(() => this.detectFrontendFramework()),
version: VERSION,
bypassToken: this.box.config.bypassToken,
mode: this.getMode(),
boxSettings: {
language: this.box.config.language,
currentLanguage: this.box.config.translation.language,
theme: this.box.config.theme,
currentTheme: this.box.config.currentTheme,
autostart: this.box.config.autostart,
mode: this.box.config.mode,
slider: this.box.config.slider,
hideBranding: this.box.config.hideBranding,
hidePrivacy: this.box.config.hidePrivacy,
customPrivacy: !this.box.config.privacyUrl.includes("https://www.trustcaptcha.com/"),
invisible: this.box.config.invisible,
}
},
browserInformation: browserInformation,
userEvents: userEvents,
};
}
getTimezone() {
var _a;
const DateTimeFormat = (_a = window.Intl) === null || _a === void 0 ? void 0 : _a.DateTimeFormat;
if (DateTimeFormat) {
const timezone = new DateTimeFormat().resolvedOptions().timeZone;
if (timezone) {
return timezone;
}
}
const offset = -this.getTimezoneOffset();
return `UTC${offset >= 0 ? '+' : ''}${Math.abs(offset)}`;
}
getTimezoneOffset() {
const currentYear = new Date().getFullYear();
return Math.max(this.toFloat(new Date(currentYear, 0, 1).getTimezoneOffset()), this.toFloat(new Date(currentYear, 6, 1).getTimezoneOffset()));
}
toFloat(value) {
return parseFloat(value);
}
getMode() {
return this.box.config.mode === 'minimal' ? 'MINIMAL_DATA' : 'STANDARD';
}
minimalPropertiesToCollect() {
return [
'devicePixelRatio',
'navigator.connection.downlink',
'navigator.connection.effectiveType',
'navigator.connection.rtt',
'navigator.connection.saveData',
'location.href',
'navigator.cookieEnabled',
'navigator.deviceMemory',
'navigator.doNotTrack',
'navigator.hardwareConcurrency',
'navigator.language',
'navigator.languages',
'navigator.userAgent',
'origin',
];
}
standardPropertiesToCollect() {
return [
'navigator.connection.downlink',
'navigator.connection.effectiveType',
'navigator.connection.rtt',
'navigator.connection.saveData',
'navigator.connection.type',
'devicePixelRatio',
'history.length',
'innerHeight',
'innerWidth',
'length',
'location.href',
'locationbar.visible',
'menubar.visible',
'navigator.cookieEnabled',
'navigator.deviceMemory',
'navigator.doNotTrack',
'navigator.hardwareConcurrency',
'navigator.language',
'navigator.languages',
'navigator.maxTouchPoints',
'navigator.pdfViewerEnabled',
'navigator.platform',
'navigator.userAgent',
'navigator.webdriver',
'navigator.vendor',
'origin',
'performance.navigation.type',
'performance.navigation.redirectCount',
'performance.now()',
'outerHeight',
'outerWidth',
'personalbar.visible',
'screen.availHeight',
'screen.availWidth',
'screen.colorDepth',
'screen.height',
'screen.orientation.angle',
'screen.orientation.type',
'screen.pixelDepth',
'screen.width',
'screenLeft',
'screenTop',
'screenX',
'screenY',
'scrollbars.visible'
];
}
async collectProperties(paths) {
const result = {};
for (const path of paths) {
const fullPath = 'window.' + path;
const key = fullPath.replace(/\./g, '-');
result[key] = this.safeGet(() => this.getValueAtPath(fullPath));
}
Object.assign(result, {
'dom-automationNote': this.safeGet(() => this.getAutomationNote()),
'dom-webGlSupport': this.safeGet(() => this.isWebGLSupported()),
'dom-canvasSupport': this.safeGet(() => this.isCanvasSupported()),
'cookies': document.cookie.split('; ').map((cookie) => cookie.split('=')[0]),
'plugins': Array.from(navigator.plugins).map(plugin => plugin.name).join(','),
'fp-audio': await this.hashData(await this.getAudioFingerprint()),
'fp-canvas': await this.hashData(this.getCanvasFingerprint()),
'fp-webgl': await this.hashData(this.getWebGLFingerprint()),
'fp-navigator': await this.hashData(this.getNavigatorFingerprint()),
'fp-fonts': await this.hashData(this.getFontsFingerprint()),
'fp-screen': await this.hashData(this.getScreenFingerprint()),
});
const sortedKeys = Object.keys(result).sort();
const serialized = sortedKeys
.map(key => {
const value = Array.isArray(result[key])
? `[${result[key].join(', ')}]`
: result[key];
return `${key}=${value}`;
})
.join(', ');
const mc = await this.hashData(serialized);
Object.assign(result, { 'mc': mc });
return result;
}
getValueAtPath(path) {
const parts = path.split('.');
let current = window;
for (const part of parts.slice(1)) {
if (!current) {
return null;
}
const methodMatch = part.match(/^(\w+)\(\)$/);
if (methodMatch) {
const methodName = methodMatch[1];
try {
current = current[methodName]();
}
catch (_a) {
return null;
}
}
else {
current = current[part];
}
}
return current;
}
safeGet(fn) {
try {
const result = fn();
if (result === undefined) {
return null;
}
return result;
}
catch (_a) {
return null;
}
}
getAutomationNote() {
const documentDetectionKeys = [
'__webdriver_evaluate',
'__selenium_evaluate',
'__webdriver_script_function',
'__webdriver_script_func',
'__webdriver_script_fn',
'__fxdriver_evaluate',
'__driver_unwrapped',
'__webdriver_unwrapped',
'__driver_evaluate',
'__selenium_unwrapped',
'__fxdriver_unwrapped',
];
const windowDetectionKeys = [
'_phantom',
'__nightmare',
'_selenium',
'callPhantom',
'callSelenium',
'_Selenium_IDE_Recorder',
];
for (const key of windowDetectionKeys) {
if (window[key]) {
return true;
}
}
for (const key of documentDetectionKeys) {
if (window.document[key]) {
return true;
}
}
for (const key in window.document) {
if (key.match(/\$[a-z]dc_/) &&
window.document[key]['cache_']) {
return true;
}
}
if (window['external'] &&
window['external'].toString() &&
window['external'].toString().indexOf('Sequentum') !== -1) {
return true;
}
if (window.document.documentElement.getAttribute('selenium'))
return true;
if (window.document.documentElement.getAttribute('webdriver'))
return true;
if (window.document.documentElement.getAttribute('driver'))
return true;
return false;
}
isCanvasSupported() {
const elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}
isWebGLSupported() {
try {
const canvas = document.createElement('canvas');
return !!(window.WebGLRenderingContext &&
(canvas.getContext('webgl') || canvas.getContext('experimental-webgl')));
}
catch (e) {
return false;
}
}
detectFrontendFramework() {
if (this.box.config.framework) {
return this.box.config.framework;
}
try {
if (!!document.querySelector('[data-reactroot], [data-reactid]')) {
return 'react';
}
if (!!document.querySelector('[ng-version]')) {
return 'angular';
}
if (!!document.querySelector('[data-vue]')) {
return 'vue';
}
return 'other';
}
catch (error) {
return 'other';
}
}
getCanvasFingerprint() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('Browser Fingerprint', 2, 2);
return canvas.toDataURL();
}
getWebGLFingerprint() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl)
return null;
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
return vendor + '|' + renderer;
}
getAudioFingerprint() {
return new Promise((resolve, reject) => {
try {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioCtx.createOscillator();
const analyser = audioCtx.createAnalyser();
const gain = audioCtx.createGain();
const scriptProcessor = audioCtx.createScriptProcessor(4096, 1, 1);
let fingerprint = '';
scriptProcessor.onaudioprocess = function () {
const array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array);
fingerprint = array.join('');
audioCtx.close();
resolve(fingerprint);
};
oscillator.type = 'triangle';
oscillator.connect(analyser);
analyser.connect(scriptProcessor);
scriptProcessor.connect(gain);
gain.connect(audioCtx.destination);
oscillator.start(0);
setTimeout(() => {
if (!fingerprint) {
audioCtx.close();
resolve('');
}
}, 500);
}
catch (error) {
reject(error);
}
});
}
getNavigatorFingerprint() {
return `${this.safeGet(() => navigator.cookieEnabled)}|${this.safeGet(() => navigator.doNotTrack)}|${this.safeGet(() => navigator.pdfViewerEnabled)}|${this.safeGet(() => navigator.platform)}|${this.safeGet(() => navigator.vendor)}`;
}
getScreenFingerprint() {
return `${this.safeGet(() => window.screen.width)}x${this.safeGet(() => window.screen.height)}x${this.safeGet(() => window.screen.colorDepth)}x${this.safeGet(() => window.screen.pixelDepth)}x${this.safeGet(() => window.screen.orientation.type)}`;
}
getFontsFingerprint() {
const baseFonts = ["monospace", "sans-serif", "serif"];
const testString = "mmmmmmmmmmlli";
const testSize = "72px";
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
const widths = {};
baseFonts.forEach((font) => {
context.font = `${testSize} ${font}`;
widths[font] = context.measureText(testString).width;
});
const detectedFonts = [];
const fontList = [
"Arial", "Verdana", "Times New Roman", "Courier New", "Comic Sans MS",
"Georgia", "Impact", "Trebuchet MS", "Palatino", "Tahoma", "Helvetica",
"Geneva", "Menlo", "Monaco", "Didot", "Gill Sans",
"Segoe UI", "Candara", "Consolas", "Franklin Gothic Medium", "Book Antiqua",
"Roboto", "Open Sans", "Lato", "Oswald", "Montserrat",
"Futura", "Garamond", "Baskerville", "Optima", "Lucida Bright", "Century Gothic",
"Arial Unicode MS", "SimSun", "MS Mincho", "Batang", "Gulim"
];
fontList.forEach((font) => {
const testFont = `${font}, monospace`;
context.font = `${testSize} ${testFont}`;
const width = context.measureText(testString).width;
if (width !== widths["monospace"]) {
detectedFonts.push(font);
}
});
return detectedFonts.join(",");
}
async hashData(data) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
return Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
}
var ErrorCode;
(function (ErrorCode) {
ErrorCode[ErrorCode["UNKNOWN_ERROR"] = 0] = "UNKNOWN_ERROR";
ErrorCode[ErrorCode["NO_FORM_FOUND"] = 1] = "NO_FORM_FOUND";
ErrorCode[ErrorCode["COMMUNICATION_FAILURE"] = 2] = "COMMUNICATION_FAILURE";
ErrorCode[ErrorCode["SITE_KEY_NOT_VALID"] = 3] = "SITE_KEY_NOT_VALID";
ErrorCode[ErrorCode["MODES_NOT_MATCHING"] = 4] = "MODES_NOT_MATCHING";
ErrorCode[ErrorCode["CAPTCHA_NOT_ACCESSIBLE"] = 5] = "CAPTCHA_NOT_ACCESSIBLE";
ErrorCode[ErrorCode["POW_FAILURE"] = 6] = "POW_FAILURE";
ErrorCode[ErrorCode["PAYMENT_REQUIRED"] = 7] = "PAYMENT_REQUIRED";
ErrorCode[ErrorCode["LOCKED"] = 8] = "LOCKED";
ErrorCode[ErrorCode["LICENSE_INVALID"] = 9] = "LICENSE_INVALID";
ErrorCode[ErrorCode["OPTION_NOT_AVAILABLE"] = 10] = "OPTION_NOT_AVAILABLE";
ErrorCode[ErrorCode["NO_SITE_KEY_SPECIFIED"] = 11] = "NO_SITE_KEY_SPECIFIED";
})(ErrorCode || (ErrorCode = {}));
class ErrorModel {
constructor(errorCode, message) {
this.errorCode = ErrorCode[errorCode];
this.message = message;
}
}
class ApiManager {
constructor(captchaBox, successCallback, failureCallback) {
this.isReset = false;
this.captchaBox = captchaBox;
this.successCallback = successCallback;
this.failureCallback = failureCallback;
this.abortController = new AbortController();
this.powSolver = new PowSolver();
}
verify() {
new InformationCollector(this.captchaBox).collectInformation().then(data => {
ApiService.verifyUser(this.captchaBox.config, data, this.abortController.signal)
.then((verificationResponse) => {
if (this.isReset)
return;
switch (verificationResponse.status) {
case 200:
if (this.captchaBox.config.slider !== 'disabled') {
this.captchaBox.captchaSlider.startSlider();
}
verificationResponse.json().then((data) => { this.solveChallenges(data); });
break;
case 201:
verificationResponse.json().then((response) => this.successCallback(response.verificationToken));
break;
case 402:
this.failureCallback(new ErrorModel(ErrorCode.PAYMENT_REQUIRED, "Limit of requests has been reached. Please contact support."));
break;
case 403:
this.failureCallback(new ErrorModel(ErrorCode.CAPTCHA_NOT_ACCESSIBLE, `The captcha is not accessible from '${location.hostname}'.`));
break;
case 404:
this.failureCallback(new ErrorModel(ErrorCode.SITE_KEY_NOT_VALID, `No captcha found with siteKey ${this.captchaBox.config.sitekey}.`));
break;
case 422:
this.failureCallback(new ErrorModel(ErrorCode.MODES_NOT_MATCHING, `Captcha mode '${this.captchaBox.config.mode}' not compatible with the captcha mode set in the settings.`));
break;
case 423:
this.failureCallback(new ErrorModel(ErrorCode.LOCKED, "Captcha blocked. Please contact the support."));
break;
default:
this.failureCallback(new ErrorModel(ErrorCode.UNKNOWN_ERROR, "Unknown error."));
break;
}
})
.catch((error) => {
if (this.isReset)
return;
if (error.name === "AbortError") ;
else {
this.failureCallback(new ErrorModel(ErrorCode.COMMUNICATION_FAILURE, "Server not reachable. Check your internet connection and try again."));
}
});
});
}
async solveChallenges(userResponseDto) {
if (this.isReset)
return;
const startSolvingTimestamp = new Date().toISOString();
const nonces = await this.powSolver.solveTasks(userResponseDto);
const solvedTimestamp = new Date().toISOString();
if (this.isReset)
return;
ApiService.sendSolutions(this.captchaBox.config, userResponseDto.challenge.verificationId, startSolvingTimestamp, solvedTimestamp, nonces, this.captchaBox.honeypotField.value, this.captchaBox.eventTracker.getEventsAndResetArray(), this.abortController.signal)
.then((solutionResponse) => {
if (this.isReset)
return;
switch (solutionResponse.status) {
case 200:
solutionResponse.json().then((newChallengeDto) => { this.solveChallenges(newChallengeDto); });
break;
case 201:
solutionResponse.json().then((response) => this.successCallback(response.verificationToken));
break;
case 404:
this.failureCallback(new ErrorModel(ErrorCode.SITE_KEY_NOT_VALID, `No captcha found with siteKey ${this.captchaBox.config.sitekey}.`));
break;
case 409:
this.failureCallback(new ErrorModel(ErrorCode.POW_FAILURE, "The solution of the bot verification tasks failed."));
break;
default:
this.failureCallback(new ErrorModel(ErrorCode.UNKNOWN_ERROR, "Unknown error."));
break;
}
})
.catch((error) => {
if (this.isReset)
return;
if (error.name === "AbortError") ;
else {
this.failureCallback(new ErrorModel(ErrorCode.COMMUNICATION_FAILURE, "Failed to communicate with the captcha backend services."));
}
});
}
reset() {
this.isReset = true;
this.abortController.abort();
this.powSolver.reset();
}
}
var Status;
(function (Status) {
Status[Status["START"] = 0] = "START";
Status[Status["RUNNING"] = 1] = "RUNNING";
Status[Status["DONE"] = 2] = "DONE";
Status[Status["FAILED"] = 3] = "FAILED";
})(Status || (Status = {}));
class EventTracker {
constructor() {
this.userEvents = [];
this.active = true;
this.eventProperties = {};
// Gruppiere Event-Typen und ihre gemeinsamen Eigenschaften
const mouseEvents = ['click', 'mousedown', 'mouseup', 'mousemove'];
const mouseEventProperties = ['isTrusted', 'timeStamp', 'type', 'altKey', 'bubbles', 'button', 'buttons', 'cancelBubble', 'cancelable', 'clientX', 'clientY', 'composed', 'ctrlKey', 'defaultPrevented', 'detail', 'eventPhase', 'layerX', 'layerY', 'metaKey', 'movementX', 'movementY', 'offsetX', 'offsetY', 'pageX', 'pageY', 'returnValue', 'screenX', 'screenY', 'shiftKey', 'which', 'x', 'y'];
mouseEvents.forEach(eventType => {
this.eventProperties[eventType] = mouseEventProperties;
});
const touchEvents = ['touchstart', 'touchend', 'touchmove', 'touchcancel'];
const touchEventProperties = ['isTrusted', 'timeStamp', 'type', 'altKey', 'bubbles', 'cancelBubble', 'cancelable', 'changedTouches', 'composed', 'ctrlKey', 'defaultPrevented', 'detail', 'eventPhase', 'metaKey', 'returnValue', 'shiftKey', 'targetTouches', 'touches'
];
touchEvents.forEach(eventType => {
this.eventProperties[eventType] = touchEventProperties;
});
const keyboardEvents = ['keydown', 'keyup'];
const keyboardEventProperties = ['isTrusted', 'timeStamp', 'type', 'altKey', 'bubbles', 'cancelBubble', 'cancelable', 'composed', 'ctrlKey', 'defaultPrevented', 'detail', 'eventPhase', 'isComposing', 'location', 'metaKey', 'repeat', 'returnValue', 'shiftKey'];
keyboardEvents.forEach(eventType => {
this.eventProperties[eventType] = keyboardEventProperties;
});
const uiEvents = ['resize'];
const uiEventProperties = ['isTrusted', 'timeStamp', 'type', 'bubbles', 'cancelBubble', 'cancelable', 'composed', 'eventPhase', 'returnValue'];
uiEvents.forEach(eventType => {
this.eventProperties[eventType] = uiEventProperties;
});
const focusEvents = ['focus', 'blur'];
const focusEventProperties = ['isTrusted', 'timeStamp', 'type', 'bubbles', 'cancelBubble', 'cancelable', 'composed', 'defaultPrevented', 'eventPhase', 'returnValue'];
focusEvents.forEach(eventType => {
this.eventProperties[eventType] = focusEventProperties;
});
const clipboardEvents = ['copy', 'paste'];
const clipboardEventProperties = ['isTrusted', 'timeStamp', 'type', 'bubbles', 'cancelBubble', 'cancelable', 'composed', 'defaultPrevented', 'eventPhase', 'returnValue', 'clipboardData'];
clipboardEvents.forEach(eventType => {
this.eventProperties[eventType] = clipboardEventProperties;
});
const scrollEvents = ['scroll'];
const scrollEventProperties = ['isTrusted', 'timeStamp', 'type', 'bubbles', 'cancelBubble', 'cancelable', 'composed', 'defaultPrevented', 'eventPhase', 'returnValue'];
scrollEvents.forEach(eventType => {
this.eventProperties[eventType] = scrollEventProperties;
});
// Event Listener hinzufügen
mouseEvents.forEach(eventType => {
if (eventType === 'mousemove') {
document.addEventListener(eventType, this.throttle(event => this.saveEvent(event), 1));
}
else {
document.addEventListener(eventType, event => this.saveEvent(event));
}
});
touchEvents.forEach(eventType => {
document.addEventListener(eventType, event => this.saveEvent(event));
});
keyboardEvents.forEach(eventType => {
document.addEventListener(eventType, event => this.saveEvent(event));
});
uiEvents.forEach(eventType => {
window.addEventListener(eventType, event => this.saveEvent(event));
});
scrollEvents.forEach(eventType => {
window.addEventListener(eventType, event => this.saveEvent(event));
});
clipboardEvents.forEach(eventType => {
document.addEventListener(eventType, event => this.saveEvent(event));
});
// Focus und Blur Events für bestimmte Elemente
const selectors = [
'a[href]',
'area[href]',
'button',
'details',
'input:not([type="hidden"]):not([type="radio"]):not([type="checkbox"]):not([disabled])',
'iframe',
'object',
'select:not([disabled])',
'textarea:not([disabled])',
'summary',
'[contenteditable]'
];
selectors.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
focusEvents.forEach(eventType => {
element.addEventListener(eventType, event => this.saveEvent(event));
});
});
});
}
throttle(func, limit) {
let inThrottle;
return function () {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
getEventsAndResetArray() {
const currentEvents = this.userEvents;
this.userEvents = [];
return currentEvents;
}
saveEvent(event) {
if (!this.active) {
return;
}
if (this.userEvents.length >= 25000) {
this.userEvents.shift();
}
const eventType = event.type;
if (this.eventProperties.hasOwnProperty(eventType)) {
const propsToInclude = this.eventProperties[eventType];
const sanitizedEvent = this.pickProperties(event, propsToInclude);
this.userEvents.push(sanitizedEvent);
}
else {
console.log('Unhandled event type:', eventType);
}
}
pickProperties(event, props) {
const safeEvent = {};
props.forEach(key => {
try {
let value = event[key];
if (key === 'clipboardData') {
value = {
files: value.files.length,
items: value.items.length,
types: value.types.length
};
}
else if (key === 'changedTouches' || key === 'targetTouches' || key === 'touches') {
value = this.mapTouchList(value);
}
if (typeof value !== 'function') {
safeEvent[key] = value;
}
}
catch (err) {
// Ignoriere nicht zugängliche Eigenschaften
}
});
return safeEvent;
}
mapTouchList(touches) {
const touchesArray = [];
for (let i = 0; i < touches.length; i++) {
const touch = touches[i];
touchesArray.push({
clientX: touch.clientX,
clientY: touch.clientY,
force: touch.force,
identifier: touch.identifier,
pageX: touch.pageX,
pageY: touch.pageY,
radiusX: touch.radiusX,
radiusY: touch.radiusY,
rotationAngle: touch.rotationAngle,
screenX: touch.screenX,
screenY: touch.screenY,
});
}
return touchesArray;
}
stop() {
this.active = false;
}
reset() {
this.userEvents = [];
this.active = true;
}
}
class SimpleEventEmitter {
constructor() {
this.listeners = {};
}
on(event, listener) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(listener);
}
// public off(event: string, listener: Listener): void {
// if (!this.listeners[event]) return;
// this.listeners[event] = this.listeners[event].filter((l) => l !== listener);
// }
emit(event, ...args) {
if (!this.listeners[event])
return;
const listeners = [...this.listeners[event]];
for (const listener of listeners) {
listener(...args);
}
}
}
class CaptchaBox {
constructor(config, htmlElement, captchaSlider) {
this.statusChangedEvent = new SimpleEventEmitter();
this.captchaFinishEvent = new SimpleEventEmitter();
this.status = Status.START;
this.boxCreationTimestamp = new Date();
this.eventTracker = new EventTracker();
this.verificationToken = null;
this.apiManager = null;
this.config = config;
this.htmlElement = htmlElement;
this.captchaSlider = captchaSlider;
}
setup() {
this.formElement = this.htmlElement.closest('form');
if (this.formElement != null) {
this.honeypotField = this.createHoneypotField();
this.verificationTokenField = this.createVerificationTokenField();
this.registerElementFocusListeners();
}
else {
this.setErrorStatus(new ErrorModel(ErrorCode.NO_FORM_FOUND, "No parent form element found."));
}
}
createHoneypotField() {
const honeypotField = document.createElement('input');
honeypotField.setAttribute('type', 'text');
honeypotField.setAttribute('name', this.generateHoneypotName());
honeypotField.setAttribute('style', 'display:none');
honeypotField.setAttribute('tabindex', '-1');
this.formElement.appendChild(honeypotField);
return honeypotField;
}
generateHoneypotName() {
const baseNames = [
"userDetail", "confirmAction", "userData", "verifyContact", "sessionInfo",
"profileUpdate", "accountVerify", "securityCheck", "membershipInfo", "customerPreference",
"siteFeedback", "userExperience", "navigationData", "serviceUsage", "interactionTrack",
"clientDetail", "transactionVerify", "operationStatus", "activityLog", "registrationData",
"processCheck", "confirmationStatus", "userContribution", "accessValidation", "engagementLevel",
"validationCode", "preferenceSetting", "userEngage", "activityConfirm", "serviceInteraction"
];
const randomIndex = Math.floor(Math.random() * baseNames.length);
const randomNumber = Math.floor(Math.random() * 1000);
return baseNames[randomIndex] + randomNumber;
}
createVerificationTokenField() {
const verificationTokenField = document.createElement('input');
verificationTokenField.setAttribute('type', 'text');
verificationTokenField.setAttribute('name', this.config.tokenFieldName);
verificationTokenField.setAttribute('style', 'display:none');
verificationTokenField.setAttribute('tabindex', '-1');
return verificationTokenField;
}
registerElementFocusListeners() {
this.formElement.addEventListener('keydown', (event) => {
if (event instanceof KeyboardEvent) {
this.autostartVerification();
}
});
this.formElement.querySelectorAll('input:not([data-autostart="false"]), select:not([data-autostart="false"]), textarea:not([data-autostart="false"])').forEach(element => {
element.addEventListener('focusin', () => {
this.autostartVerification();
});
});
}
autostartVerification() {
if (this.config.autostart) {
this.startVerification();
}
}
startVerification() {
if (this.status !== Status.START) {
return;
}
this.switchStatus(Status.RUNNING);
this.apiManager = new ApiManager(this, (verificationToken) => {
this.eventTracker.stop();
this.finishVerification(verificationToken);
this.apiManager = null;
}, (reason) => {
this.eventTracker.stop();
this.setErrorStatus(reason);
this.apiManager = null;
});
this.apiManager.verify();
}
finishVerification(verificationToken) {
const jsonString = JSON.stringify(verificationToken);
const base64String = btoa(jsonString);
this.verificationToken = base64String;
this.verificationTokenField.setAttribute("value", base64String);
this.formElement.appendChild(this.verificationTokenField);
this.handleCaptchaSolved();
}
handleCaptchaSolved() {
if (this.verificationToken && (this.config.slider === 'disabled' || this.captchaSlider.status === 'init' || this.captchaSlider.status === 'success')) {
this.captchaFinishEvent.emit("captchaSolved", this.verificationToken);
this.switchStatus(Status.DONE);
}
}
switchStatus(nextStatus) {
this.status = nextStatus;
this.statusChangedEvent.emit('statusChanged', nextStatus);
}
setErrorStatus(errorModel) {
this.switchStatus(Status.FAILED);
this.captchaFinishEvent.emit("captchaFailed", errorModel);
}
reset() {
if (this.apiManager) {
this.apiManager.reset();
this.apiManager = null;
}
this.honeypotField.removeAttribute("value");
this.verificationToken = null;
this.verificationTokenField.removeAttribute("value");
if (this.verificationTokenField && this.formElement.contains(this.verificationTokenField)) {
this.formElement.removeChild(this.verificationTokenField);
}
this.eventTracker.reset();
this.switchStatus(Status.START);
this.captchaFinishEvent.emit("captchaReset");
}
setNewConfig(config) {
this.config = config;
}
}
function mapTranslation(currentLanguage, array) {
return {
language: currentLanguage,
boxStart: array[0],
boxInProgress: array[1],
boxCompleted: array[2],
endPrivacyPolicy: array[3],
sliderRunning: array[4],
sliderRelease: array[5],
sliderSuccess: array[6],
sliderError: array[7],
sliderHold: array[8],
ariaLabelStart: (array[9] || translations['en'][9]),
ariaLabelRunning: (array[10] || translations['en'][10]),
ariaLabelDone: (array[11] || translations['en'][11]),
srRunning: (array[12] || translations['en'][12]),
srDone: (array[13] || translations['en'][13]),
srFailed: (array[14] || translations['en'][14]),
srTrustcaptcha: (array[15] || translations['en'][15]),
srPrivacy: (array[16] || translations['en'][16]),
};
}
function mergeTranslations(defaultTranslation, customTranslation) {
return Object.assign(Object.assign({}, defaultTranslation), customTranslation);
}
function getTranslation(language = 'auto', customTranslations = '') {
let filteredCustomTranslations = [];
if (customTranslations && customTranslations != '') {
try {
filteredCustomTranslations = JSON.parse(customTranslations).filter(it => it.language && it.language !== '');
}
catch (e) {
// nothing
}
}
let selectedLanguage = language;
if (language === 'auto') {
const foundLanguage = document.documentElement.lang.trim().toLowerCase().substring(0, 2);
if (foundLanguage && translations[foundLanguage]) {
selectedLanguage = foundLanguage;
}
else if (foundLanguage && filteredCustomTranslations.find(it => it.language === foundLanguage)) {
selectedLanguage = foundLanguage;
}
else {
selectedLanguage = 'en';
}
}
const translationArray = translations[selectedLanguage] || translations['en'];
const translation = mapTranslation(selectedLanguage, translationArray);
const customTranslation = filteredCustomTranslations.find(it => it.language === selectedLanguage);
if (customTranslation) {
return mergeTranslations(translation, customTranslation);
}
return translation;
}
const translations = {
// Arabisch
ar: [
"ابدأ التحقق",
"التحقق جارٍ",
"أنا إنسان!",
"سياسة الخصوصية",
"اضغط الزر حتى يصبح أخضر تماماً",
"الآن اترك الزر",
"ناجح",
"تم تحرير الزر مبكرًا جدًا أو متأخرًا جدًا",
"استمر في الضغط",
],
// Weißrussisch
be: [
"Пачаць праверку",
"Праверка працягваецца",
"Я чалавек!",
"Палітыка прыватнасці",
"Націсніце кнопку, пакуль яна не стане цалкам зялёнай",
"Цяпер адпусціце",
"Паспяхова",
"Адпушчаны занадта рана або занадта позна",
"Трымайце націснутым",
],
// Bulgarisch
bg: [
"Старт на проверката",
"Проверката е в ход",
"Аз съм човек!",
"Защита на данни",
"Натиснете бутона, докато не стане напълно зелен",
"освободете бутона сега",
"успешно завършен",
"Освободено твърде рано или твърде късно",
"Дръжте натиснато",
],
// Bosnisch
bs: [
"Započni verifikaciju",
"Verifikacija je u toku",
"Ja sam čovjek!",
"Privatnost",
"Pritisnite dugme dok ne postane potpuno zeleno",
"Sada otpustite",
"Uspješno",
"Otpušteno prerano ili prekasno",
"Držite pritisnuto",
],
// Katalanisch
ca: [
"Comença la verificació",
"Verificació en curs",
"Sóc humà!",
"Privacitat",
"Prem el botó fins que es torni completament verd",
"Ara solta el botó",
"Exitós",
"Alliberat massa aviat o massa tard",
"Mantingues pressionat",
],
// Tschechisch
cs: [
"Začít kontrolu",
"Kontrola probíhá",
"Jsem člověk!",
"Soukromí",
"Stiskněte tlačítko, dokud nebude úplně zelené",
"Teď uvolněte",
"Úspěšné",
"Uvolněno příliš brzy nebo příliš pozdě",
"Držte stisknuté",
],
// Dänisch
da: [
"Start kontrol",
"Kontrol i gang",
"Jeg er et menneske!",
"Privatliv",
"Tryk på knappen, indtil den bliver helt grøn",
"Slip nu",
"Succesfuldt",
"Sluppet for tidligt eller for sent",
"Hold knappen nede",
],
// Deutsch
de: [
"Überprüfung starten",
"Überprüfung läuft",
"Ich bin ein Mensch!",
"Datenschutz",
"Knopf drücken, bis er vollständig grün ist",
"Jetzt loslassen",
"Erfolgreich",
"Zu früh oder zu spät losgelassen",
"Gedrückt halten",
"CAPTCHA nicht gestartet, klicken Sie hier, um das CAPTCHA zu starten",
"CAPTCHA läuft",
"CAPTCHA abgeschlossen",
"CAPTCHA-Prüfung gestartet",
"CAPTCHA-Prüfung erfolgreich abgeschlossen",
"CAPTCHA ist aufgrund eines technischen Fehlers fehlgeschlagen. Bitte wenden Sie sich an den Support.",
"Zur Trustcaptcha-Website gehen",
"Zur Datenschutzerklärung gehen"
],
// Griechisch
el: [
"Έναρξη ελέγχου",
"Ο έλεγχος βρίσκεται σε εξέλιξη",
"Είμαι άνθρωπος!",
"Απόρρητο",
"Πατήστε το κουμπί μέχρι να γίνει εντελώς πράσινο",
"Αφήστε τώρα",
"Επιτυχία",
"Απελευθερώθηκε πολύ νωρίς ή πολύ αργά",
"Κρατήστε πατημένο",
],
// Englisch
en: [
"Start verification",
"Verification in progress",
"I am a human!",
"Privacy",
"Press the button until it turns completely green",
"Now release",
"Success",
"Released too early or too late",
"Keep holding",
"CAPTCHA not started, click here to start the CAPTCHA",
"CAPTCHA in process",
"CAPTCHA completed",
"CAPTCHA verification started",
"CAPTCHA verification successfully completed",
"CAPTCHA has failed due to a technical error. Please contact the support.",
"Go to the Trustcaptcha website",
"Go to the privacy policy"
],
// Spanisch
es: [
"Iniciar verificación",
"Verificación en progreso",
"¡Soy humano!",
"Privacidad",
"Presiona el botón hasta que se vuelva completamente verde",
"Ahora suelta",
"Éxito",
"Liberado demasiado pronto o demasiado tarde",
"Mantén presionado",
],
// Estnisch
et: [
"Alusta kontrollimist",
"Kontroll on käimas.",
"Ma olen inimene!",
"Privaatsus",
"Vajuta nuppu, kuni see muutub täielikult roheliseks",
"Nüüd lase",
"Edukas",
"Vabastatud liiga vara või liiga hilja",
"Hoia all vajutatud",
],
// Finnisch
fi: [
"Aloita tarkistus",
"Tarkistus käynnissä",
"Olen ihminen!",
"Yksityisyys",
"Paina nappia, kunnes se muuttuu täysin vihreäksi",
"Vapauta nyt",
"Onnistunut",
"Vapautit painikkeen liian aikaisin tai liian myöhään",
"Pidä pohjassa",
],
// Französisch
fr: [
"Démarrer la vérification",
"Vérification en cours",
"Je suis un humain!",
"Confidentialité",
"Appuyez sur le bouton jusqu'à ce qu'il devienne complètement vert",
"Relâchez maintenant",
"Succès",
"Relâché trop tôt ou trop tard",
"Maintenez enfoncé",
],
// Hindi
hi: [
"सत्यापन प्रारंभ करें",
"सत्यापन प्रगति पर है",
"मैं एक इंसान हूँ!",
"गोपनीयता",
"जब तक बटन पूरी तरह से हरा न हो जाए, बटन दबाएं",
"अब छोड़ें",
"सफल",
"बहुत जल्दी या बहुत देर से छोड़ा गया",
"दबाए रखें",
],
// Kroatisch
hr: [
"Pokreni provjeru",
"Provjera je u tijeku",
"Ja sam čovjek!",
"Privatnost",
"Pritisnite gumb dok ne postane potpuno zelen",
"Sada otpustite",
"Uspješno",
"Otpušteno prerano ili prekasno",
"Držite pritisnuto",
],
// Ungarisch
hu: [
"Indítsa el az ellenőrzést",
"Ellenőrzés folyamatban",
"Ember vagyok!",
"Adatvédelem",
"Nyomja meg a gombot, amíg teljesen zöld nem lesz",
"Most engedje el",
"Sikeres",
"Túl korán v