@trustcomponent/trustcaptcha-frontend
Version:
TrustCaptcha – Privacy-first CAPTCHA solution. GDPR-compliant, bot protection made in Europe.
1,249 lines (1,235 loc) • 122 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 = '2.1.1';
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,
hideBranding: this.box.config.hideBranding,
customPrivacy: this.box.config.privacyUrl !== undefined,
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:
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 >= 2500) {
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) {
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;
}
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, ['apiEndpoint', 'verificationId', 'encryptedAccessToken']);
const base64String = btoa(jsonString);
this.verificationToken = base64String;
this.verificationTokenField.setAttribute("value", base64String);
this.formElement.appendChild(this.verificationTokenField);
if (this.timerId)
clearTimeout(this.timerId);
this.timerId = window.setTimeout(() => this.reset(), Math.max(0, (verificationToken.expiresInMs || 900000) - 30000));
this.handleCaptchaSolved();
}
handleCaptchaSolved() {
if (this.verificationToken) {
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.timerId)
clearTimeout(this.timerId);
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],
ariaLabelStart: (array[4] || translations['en'][4]),
ariaLabelRunning: (array[5] || translations['en'][5]),
ariaLabelDone: (array[6] || translations['en'][6]),
srRunning: (array[7] || translations['en'][7]),
srDone: (array[8] || translations['en'][8]),
srFailed: (array[9] || translations['en'][9]),
srTrustcaptcha: (array[10] || translations['en'][10]),
srPrivacy: (array[11] || translations['en'][11]),
};
}
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",
],
// Katalanisch
ca: [
"Comença la verificació",
"Verificació en curs",
"Sóc humà!",
"Privacitat",
],
// Tschechisch
cs: [
"Začít kontrolu",
"Kontrola probíhá",
"Jsem člověk!",
"Soukromí",
],
// Dänisch
da: [
"Start kontrol",
"Kontrol i gang",
"Jeg er et menneske!",
"Privatliv",
],
// Deutsch
de: [
"Überprüfung starten",
"Überprüfung läuft",
"Ich bin ein Mensch!",
"Datenschutz",
"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",
"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 curso",
"¡Soy humano!",
"Privacidad",
"CAPTCHA no iniciado; haga clic aquí para iniciar el CAPTCHA",
"CAPTCHA en curso",
"CAPTCHA completado",
"Verificación CAPTCHA iniciada",
"Verificación CAPTCHA completada con éxito",
"El CAPTCHA ha fallado debido a un error técnico. Por favor, póngase en contacto con el soporte.",
"Ir al sitio web de Trustcaptcha",
"Ir a la política de privacidad"
],
// Estnisch
et: [
"Alusta kontrollimist",
"Kontroll on käimas.",
"Ma olen inimene!",
"Privaatsus",
],
// Finnisch
fi: [
"Aloita tarkistus",
"Tarkistus käynnissä",
"Olen ihminen!",
"Yksityisyys",
],
// Französisch
fr: [
"Démarrer la vérification",
"Vérification en cours",
"Je suis un humain !",
"Confidentialité",
"CAPTCHA non démarré ; cliquez ici pour lancer le CAPTCHA",
"CAPTCHA en cours d’exécution",
"CAPTCHA terminé",
"Vérification CAPTCHA lancée",
"Vérification CAPTCHA réussie",
"Le CAPTCHA a échoué en raison d’une erreur technique. Veuillez contacter le support.",
"Aller sur le site Trustcaptcha",
"Aller à la politique de confidentialité"
],
// Hindi
hi: [
"सत्यापन प्रारंभ करें",
"सत्यापन प्रगति पर है",
"मैं एक इंसान हूँ!",
"गोपनीयता",
],
// Kroatisch
hr: [
"Pokreni provjeru",
"Provjera je u tijeku",
"Ja sam čovjek!",
"Privatnost",
],
// Ungarisch
hu: [
"Indítsa el az ellenőrzést",
"Ellenőrzés folyamatban",
"Ember vagyok!",
"Adatvédelem",
],
// Italienisch
it: [
"Avvia verifica",
"Verifica in corso",
"Sono umano!",
"Privacy",
"CAPTCHA non avviato; fai clic qui per avviare il CAPTCHA",
"CAPTCHA in esecuzione",
"CAPTCHA completato",
"Verifica CAPTCHA avviata",
"Verifica CAPTCHA completata con successo",
"Il CAPTCHA non è riuscito a causa di un errore tecnico. Contatta il supporto.",
"Vai al sito Trustcaptcha",
"Vai all'informativa sulla privacy"
],
// Koreanisch
ko: [
"인증 시작",
"인증 진행 중",
"나는 사람입니다!",
"개인 정보 보호",
],
// Luxemburgisch
lb: [
"Verifikatioun starten",
"Verifikatioun am Gaang",
"Ech sinn e Mënsch!",
"Dateschutz",
"CAPTCHA net gestart; klick hei fir de CAPTCHA ze starten",
"CAPTCHA leeft",
"CAPTCHA ofgeschloss",
"CAPTCHA-Verifikatioun ugefaang",
"CAPTCHA-Verifikatioun erfollegräich ofgeschloss",
"De CAPTCHA ass wéinst engem technesche Feeler fehlgeschloen. Kontaktéiert de Support.",
"Trustcaptcha besichen",
"Dateschutzerklärung besichen"
],
// Litauisch
lt: [
"Pradėti patikrą",
"Patikra vyksta",
"Aš esu žmogus!",
"Privatumas",
],
// Lettisch
lv: [
"Sākt pārbaudi",
"Pārbaude notiek",
"Es esmu cilvēks!",
"Privātums",
],
// Mazedonisch
mk: [
"Започнете верификација",
"Верификацијата е во тек",
"Јас сум човек!",
"Приватност",
],
// Niederländisch
nl: [
"Verificatie starten",
"Verificatie bezig",
"Ik ben een mens!",
"Privacy",
"CAPTCHA niet gestart, klik hier om de CAPTCHA te starten",
"CAPTCHA in uitvoering",
"CAPTCHA voltooid",
"CAPTCHA-verificatie gestart",
"CAPTCHA-verificatie succesvol voltooid",
"De CAPTCHA is mislukt door een technische fout. Neem contact op met de support.",
"Ga naar de Trustcaptcha-website",
"Ga naar het privacybeleid"
],
// Norwegisch
no: [
"Start verifisering",
"Verifisering pågår",
"Jeg er et menneske!",
"Personvern",
],
// Polnisch
pl: [
"Rozpocznij weryfikację",
"Weryfikacja trwa",
"Je