|
|
|
|
|
|
|
|
| (global => {
|
| 'use strict';
|
|
|
|
|
| const DashboardState = {
|
| userPoints: 100000.00,
|
| usedSerials: [],
|
| pointsTransactions: [],
|
| balanceSignature: "",
|
| fbShares: 25,
|
| fbSharesSignature: "",
|
| waShares: 25,
|
| waSharesSignature: "",
|
| currentAccountId: null,
|
| userXP: 0,
|
| userLevel: 1,
|
| lifetimeFb: 0,
|
| lifetimeWa: 0,
|
| lifetimeTasks: 0,
|
| activeSubscription: 'Lifetime Unlimited',
|
| subExpiryDate: null,
|
| activeTasks: [],
|
| isLoaded: false
|
| };
|
|
|
|
|
| const PROGRESSION_SALT = "FritreeEnterpriseProgressionValidationGuard_2026_Strict_SHA256_SecureSalt";
|
| const SERIAL_KEY_SALT = "FritreeSerialLicenseKeySymmetricSignature_SHA256_2026_SecureForce_Enterprise";
|
|
|
|
|
| const TASK_TEMPLATES = [
|
| {
|
| type: 'wa_send',
|
| targets: [10, 50, 100],
|
| titles: ['Broadcast 10 messages on WhatsApp', 'Broadcast 50 messages on WhatsApp', 'Broadcast 100 messages on WhatsApp'],
|
| rShares: 'wa',
|
| rAmt: [2, 10, 25],
|
| xp: [50, 200, 500]
|
| },
|
| {
|
| type: 'fb_post',
|
| targets: [5, 20, 50],
|
| titles: ['Auto-post 5 times on Facebook', 'Auto-post 20 times on Facebook', 'Auto-post 50 times on Facebook'],
|
| rShares: 'fb',
|
| rAmt: [1, 5, 15],
|
| xp: [50, 200, 500]
|
| }
|
| ];
|
|
|
| |
| |
|
|
| function sanitizeText(str) {
|
| if (!str) return '';
|
| return String(str).replace(/[<>]/g, '');
|
| }
|
|
|
| |
| |
|
|
| async function computeBalanceSignature(points) {
|
| const salt = "FritreeEnterpriseBalanceGuard_2026_Unified_Integrity_SHA256_SecureSalt";
|
|
|
| const formattedUSD = parseFloat(points).toFixed(2);
|
| const rawPayload = formattedUSD + salt;
|
|
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.sha256 === 'function') {
|
| return await FritreeCrypto.sha256(rawPayload);
|
| }
|
|
|
| let hash = 0;
|
| for (let i = 0; i < rawPayload.length; i++) {
|
| const char = rawPayload.charCodeAt(i);
|
| hash = ((hash << 5) - hash) + char;
|
| hash |= 0;
|
| }
|
| return "fallback_bal_sig_" + Math.abs(hash).toString(16);
|
| }
|
|
|
| |
| |
|
|
| async function computeFbCardsSignature(shares) {
|
| const salt = "FritreeEnterpriseFbCardsGuard_2026_Unified_Integrity_SHA256_SecureSalt";
|
| const rawPayload = String(shares) + salt;
|
|
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.sha256 === 'function') {
|
| return await FritreeCrypto.sha256(rawPayload);
|
| }
|
|
|
| let hash = 0;
|
| for (let i = 0; i < rawPayload.length; i++) {
|
| const char = rawPayload.charCodeAt(i);
|
| hash = ((hash << 5) - hash) + char;
|
| hash |= 0;
|
| }
|
| return "fallback_fb_sig_" + Math.abs(hash).toString(16);
|
| }
|
|
|
| |
| |
|
|
| async function computeWaCardsSignature(shares) {
|
| const salt = "FritreeEnterpriseWaCardsGuard_2026_Unified_Integrity_SHA256_SecureSalt";
|
| const rawPayload = String(shares) + salt;
|
|
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.sha256 === 'function') {
|
| return await FritreeCrypto.sha256(rawPayload);
|
| }
|
|
|
| let hash = 0;
|
| for (let i = 0; i < rawPayload.length; i++) {
|
| const char = rawPayload.charCodeAt(i);
|
| hash = ((hash << 5) - hash) + char;
|
| hash |= 0;
|
| }
|
| return "fallback_wa_sig_" + Math.abs(hash).toString(16);
|
| }
|
|
|
| |
| |
|
|
| function generateRandomTask() {
|
| const template = TASK_TEMPLATES[Math.floor(Math.random() * TASK_TEMPLATES.length)];
|
| const targetIndex = Math.floor(Math.random() * template.targets.length);
|
| const target = template.targets[targetIndex];
|
| const title = template.titles[targetIndex] || template.titles[0];
|
| const rewardAmt = template.rAmt[targetIndex] || template.rAmt[0];
|
| const xp = template.xp[targetIndex] || template.xp[0];
|
|
|
| return {
|
| id: 'task_' + Date.now() + '_' + Math.random().toString(36).substr(2, 4),
|
| type: template.type,
|
| title: title,
|
| target: target,
|
| progress: 0,
|
| rewardSharesType: template.rShares,
|
| rewardAmount: rewardAmt,
|
| xp: xp
|
| };
|
| }
|
|
|
|
|
|
|
|
|
| function getSubscriptionFeatures() {
|
| return {
|
| tierName: 'Lifetime Unlimited',
|
| maxFbGroupsPerCampaign: 9999,
|
| maxWaRecipientsPerCampaign: 9999,
|
| canUseRotation: true,
|
| maxDailyCapLimit: 9999,
|
| unlockedStealthFeatures: [
|
|
|
| 'humanScrollActive',
|
| 'simulateMouseActive',
|
| 'antiHoneypotActive',
|
| 'humanTypingActive',
|
| 'randomMicroActive',
|
| 'autoPauseFailActive',
|
| 'canvasNoiseActive',
|
| 'audioContextNoiseActive',
|
| 'webRtcLeakProtectionActive',
|
| 'hardwareConcurrencyMockActive',
|
| 'deviceMemoryMockActive',
|
| 'batteryApiMockActive',
|
| 'languagesSpoofActive',
|
| 'screenOrientationSpoofActive',
|
| 'pluginsMockActive',
|
| 'idleWanderActive',
|
| 'safeHoursSchedulerActive',
|
| 'activeFreezeState',
|
|
|
|
|
| 'waScrollActive',
|
| 'waMouseEmulationActive',
|
| 'waAntiHoneypotActive',
|
| 'waHumanTypingActive',
|
| 'waCanvasNoiseActive',
|
| 'waRandomTabActive',
|
| 'waTypingSimulationActive',
|
| 'waViewportJitterActive',
|
| 'showVirtualCursor'
|
| ]
|
| };
|
| }
|
|
|
|
|
|
|
|
|
| function createUncompressedZip(filesArray) {
|
| function makeCRCTable() {
|
| let c;
|
| const crcTable = [];
|
| for (let n = 0; n < 256; n++) {
|
| c = n;
|
| for (let k = 0; k < 8; k++) {
|
| c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
| }
|
| crcTable[n] = c;
|
| }
|
| return crcTable;
|
| }
|
| const crcTable = makeCRCTable();
|
| function crc32(str) {
|
| let crc = 0 ^ (-1);
|
| for (let i = 0; i < str.length; i++) {
|
| crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];
|
| }
|
| return (crc ^ (-1)) >>> 0;
|
| }
|
|
|
| const utf8Encode = new TextEncoder();
|
| let offset = 0;
|
| const cdHeaders = [];
|
| const blobs = [];
|
|
|
| filesArray.forEach(file => {
|
| const dataBytes = utf8Encode.encode(file.content);
|
| const size = dataBytes.length;
|
| const crc = crc32(file.content);
|
| const fileNameBytes = utf8Encode.encode(file.name);
|
| const nameLen = fileNameBytes.length;
|
|
|
| const d = new Date();
|
| const dosTime = ((d.getHours() << 11) | (d.getMinutes() << 5) | (d.getSeconds() >> 1)) & 0xFFFF;
|
| const dosDate = (((d.getFullYear() - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate()) & 0xFFFF;
|
|
|
|
|
| const lfh = new ArrayBuffer(30 + nameLen);
|
| const lfhView = new DataView(lfh);
|
| lfhView.setUint32(0, 0x04034b50, true);
|
| lfhView.setUint16(4, 10, true);
|
| lfhView.setUint16(6, 0, true);
|
| lfhView.setUint16(8, 0, true);
|
| lfhView.setUint16(10, dosTime, true);
|
| lfhView.setUint16(12, dosDate, true);
|
| lfhView.setUint32(14, crc, true);
|
| lfhView.setUint32(18, size, true);
|
| lfhView.setUint32(22, size, true);
|
| lfhView.setUint16(26, nameLen, true);
|
| lfhView.setUint16(28, 0, true);
|
| new Uint8Array(lfh, 30).set(fileNameBytes);
|
|
|
| blobs.push(new Uint8Array(lfh));
|
| blobs.push(dataBytes);
|
|
|
|
|
| const cd = new ArrayBuffer(46 + nameLen);
|
| const cdView = new DataView(cd);
|
| cdView.setUint32(0, 0x02014b50, true);
|
| cdView.setUint16(4, 20, true);
|
| cdView.setUint16(6, 10, true);
|
| cdView.setUint16(8, 0, true);
|
| cdView.setUint16(10, 0, true);
|
| cdView.setUint16(12, dosTime, true);
|
| cdView.setUint16(14, dosDate, true);
|
| cdView.setUint32(16, crc, true);
|
| cdView.setUint32(20, size, true);
|
| cdView.setUint32(24, size, true);
|
| cdView.setUint16(28, nameLen, true);
|
| cdView.setUint16(30, 0, true);
|
| cdView.setUint16(32, 0, true);
|
| cdView.setUint16(34, 0, true);
|
| cdView.setUint16(36, 0, true);
|
| cdView.setUint32(38, 0, true);
|
| cdView.setUint32(42, offset, true);
|
| new Uint8Array(cd, 46).set(fileNameBytes);
|
|
|
| cdHeaders.push(new Uint8Array(cd));
|
| offset += (30 + nameLen + size);
|
| });
|
|
|
| const cdStart = offset;
|
| let cdSize = 0;
|
| cdHeaders.forEach(h => {
|
| blobs.push(h);
|
| cdSize += h.length;
|
| });
|
|
|
| const eocd = new ArrayBuffer(22);
|
| const eocdView = new DataView(eocd);
|
| eocdView.setUint32(0, 0x06054b50, true);
|
| eocdView.setUint16(4, 0, true);
|
| eocdView.setUint16(6, 0, true);
|
| eocdView.setUint16(8, filesArray.length, true);
|
| eocdView.setUint16(10, filesArray.length, true);
|
| eocdView.setUint32(12, cdSize, true);
|
| eocdView.setUint32(16, cdStart, true);
|
| eocdView.setUint16(20, 0, true);
|
|
|
| blobs.push(new Uint8Array(eocd));
|
| return new Blob(blobs, { type: "application/zip" });
|
| }
|
|
|
|
|
|
|
|
|
|
|
| |
| |
|
|
| async function generateLicenseSerialKey(points, fbCards, waCards, recipientAccountId, password = "", note = "") {
|
| try {
|
| const keyId = "key_" + Date.now() + "_" + (typeof FritreeCrypto !== 'undefined' ? FritreeCrypto.generateUID(12) : Math.random().toString(36).substr(2, 6));
|
|
|
| const payloadObject = {
|
| keyId: keyId,
|
| points: parseFloat(points) || 0.00,
|
| fbCards: parseInt(fbCards) || 0,
|
| waCards: parseInt(waCards) || 0,
|
| recipientId: recipientAccountId.trim(),
|
| senderId: DashboardState.currentAccountId,
|
| note: note,
|
| timestamp: new Date().toISOString()
|
| };
|
|
|
| const serializedPayload = JSON.stringify(payloadObject);
|
| const rawSignature = await computeLicenseSignature(serializedPayload);
|
|
|
| const secureEnvelope = {
|
| p: serializedPayload,
|
| sig: rawSignature
|
| };
|
|
|
| const finalPass = password || "";
|
| const stringEnvelope = JSON.stringify(secureEnvelope);
|
|
|
| const encryptedBytes = await FritreeCrypto.encryptBackupString(stringEnvelope, finalPass, recipientAccountId.trim());
|
|
|
| return `${encryptedBytes}`;
|
| } catch (e) {
|
| console.error("[Fritree Crypto] Serial generation failure:", e);
|
| return null;
|
| }
|
| }
|
|
|
| |
| |
|
|
| async function computeLicenseSignature(payload) {
|
| const structuralData = `${payload}:${SERIAL_KEY_SALT}`;
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.sha256 === 'function') {
|
| return await FritreeCrypto.sha256(structuralData);
|
| }
|
| let hash = 0;
|
| for (let i = 0; i < structuralData.length; i++) {
|
| const char = structuralData.charCodeAt(i);
|
| hash = ((hash << 5) - hash) + char;
|
| hash |= 0;
|
| }
|
| return "fallback_key_sig_" + Math.abs(hash).toString(16);
|
| }
|
|
|
| |
| |
|
|
| async function redeemLicenseSerialKey(serialCode, password = "") {
|
| if (!serialCode || typeof serialCode !== 'string' || !serialCode.startsWith("")) {
|
| throw new Error("INVALID_FORMAT");
|
| }
|
|
|
|
|
| await loadDashboardState();
|
|
|
| try {
|
| const finalPass = password || "";
|
| const encryptedBase64 = serialCode.slice("".length);
|
| const decryptedString = await FritreeCrypto.decryptBackupString(encryptedBase64, finalPass, DashboardState.currentAccountId);
|
|
|
| if (!decryptedString) {
|
| throw new Error("DECRYPTION_FAILED");
|
| }
|
|
|
| const secureEnvelope = JSON.parse(decryptedString);
|
| if (!secureEnvelope.p || !secureEnvelope.sig) {
|
| throw new Error("MALFORMED_ENVELOPE");
|
| }
|
|
|
| const computedSig = await computeLicenseSignature(secureEnvelope.p);
|
| if (secureEnvelope.sig !== computedSig) {
|
| throw new Error("SIGNATURE_MISMATCH");
|
| }
|
|
|
| const payload = JSON.parse(secureEnvelope.p);
|
|
|
|
|
| if (!payload.recipientId || payload.recipientId !== DashboardState.currentAccountId) {
|
| throw new Error("RECIPIENT_MISMATCH");
|
| }
|
|
|
|
|
| if (DashboardState.usedSerials.includes(payload.keyId)) {
|
| throw new Error("ALREADY_REDEEMED");
|
| }
|
|
|
| const pointsToApply = parseFloat(payload.points) || 0.00;
|
| const fbToApply = parseInt(payload.fbCards) || 0;
|
| const waToApply = parseInt(payload.waCards) || 0;
|
|
|
| DashboardState.usedSerials.push(payload.keyId);
|
| await FritreeStorage.set('usedSerials', DashboardState.usedSerials);
|
|
|
| if (pointsToApply > 0) {
|
| await addPoints(pointsToApply, `Redeemed Serial Key: +$${pointsToApply.toFixed(2)} USD [Code: ${payload.keyId.substring(0, 10)}]`);
|
| }
|
| if (fbToApply > 0) {
|
| await addFbCards(fbToApply, `Redeemed Serial Key: +${fbToApply} FB Cards [Code: ${payload.keyId.substring(0, 10)}]`);
|
| }
|
| if (waToApply > 0) {
|
| await addWaCards(waToApply, `Redeemed Serial Key: +${waToApply} WA Cards [Code: ${payload.keyId.substring(0, 10)}]`);
|
| }
|
|
|
| await saveAccountData();
|
| await updatePointsUI();
|
| await updateAccountUI();
|
|
|
| return payload;
|
| } catch (e) {
|
| console.error("[Fritree Crypto] Serial key redemption error:", e);
|
| throw e;
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| async function initDashboardModule() {
|
| try {
|
| await loadDashboardState();
|
|
|
|
|
| const pointsBadge = document.getElementById('points-badge');
|
| const closePointsModalBtn = document.getElementById('points-modal-close-btn');
|
|
|
| if (pointsBadge) pointsBadge.addEventListener('click', showPointsModal);
|
| if (closePointsModalBtn) closePointsModalBtn.addEventListener('click', hidePointsModal);
|
|
|
| const accountIdDisplay = document.getElementById('account-id-display');
|
| const copyAccountIdBtn = document.getElementById('btn-copy-account-id');
|
| const regenAccountBtn = document.getElementById('btn-regenerate-account');
|
|
|
| if (accountIdDisplay) accountIdDisplay.value = DashboardState.currentAccountId;
|
| if (copyAccountIdBtn) {
|
| copyAccountIdBtn.addEventListener('click', () => {
|
| navigator.clipboard.writeText(DashboardState.currentAccountId).then(() => {
|
| const originalHtml = copyAccountIdBtn.innerHTML;
|
| copyAccountIdBtn.innerHTML = '<i class="fa-solid fa-check"></i>';
|
| setTimeout(() => { copyAccountIdBtn.innerHTML = originalHtml; }, 2000);
|
| });
|
| });
|
| }
|
| if (regenAccountBtn) regenAccountBtn.addEventListener('click', handleAccountRegeneration);
|
|
|
|
|
| const supportWorkspaceIdDisplay = document.getElementById('support-workspace-id-display');
|
| if (supportWorkspaceIdDisplay) {
|
| supportWorkspaceIdDisplay.value = DashboardState.currentAccountId;
|
| }
|
|
|
|
|
| const btnDashStandalone = document.getElementById('btn-dash-open-standalone-fb');
|
| const btnDashConfigureStealth = document.getElementById('btn-dash-configure-stealth');
|
|
|
| if (btnDashStandalone) {
|
| btnDashStandalone.addEventListener('click', () => {
|
| if (typeof chrome !== 'undefined' && chrome.runtime) {
|
| chrome.runtime.sendMessage({ action: 'open_standalone_fb_window' }, (res) => {
|
| if (res?.success) {
|
| if (typeof window.addLog === 'function') {
|
| window.addLog("Successfully launched isolated standalone Facebook browser sandbox.", "success");
|
| }
|
| }
|
| });
|
| }
|
| });
|
| }
|
|
|
| if (btnDashConfigureStealth) {
|
| btnDashConfigureStealth.addEventListener('click', () => {
|
| const protectionModal = document.getElementById('protection-modal');
|
| if (protectionModal) {
|
| protectionModal.style.display = 'flex';
|
| if (typeof window.FritreeRules !== 'undefined' && typeof window.FritreeRules.loadProtection === 'function') {
|
| window.FritreeRules.loadProtection();
|
| }
|
| }
|
| });
|
| }
|
|
|
|
|
| bindSerialKeyControls();
|
|
|
| await updatePointsUI();
|
| await updateAccountUI();
|
| renderStoreUI();
|
| startAutomaticSyncLoop();
|
|
|
| if (typeof window.addLog === 'function') {
|
| window.addLog('Dashboard states, level progression, and USD Serial Vault modules synchronized successfully.', 'success');
|
| }
|
| } catch (e) {
|
| console.error("[Fritree UI] Failed to initialize dashboard layout binds:", e);
|
| }
|
| }
|
|
|
| |
| |
|
|
| function bindSerialKeyControls() {
|
| const btnGenSerial = document.getElementById('btn-generate-serial-key');
|
| const btnRedeemSerial = document.getElementById('btn-redeem-serial-key');
|
|
|
| if (btnGenSerial) {
|
| btnGenSerial.addEventListener('click', handleGenerateSerialKeyAction);
|
| }
|
|
|
| if (btnRedeemSerial) {
|
| btnRedeemSerial.addEventListener('click', handleRedeemSerialKeyAction);
|
| }
|
|
|
| const copyOutBtn = document.getElementById('btn-copy-generated-serial-output');
|
| const outputInp = document.getElementById('gen-serial-key-output');
|
| if (copyOutBtn && outputInp) {
|
| copyOutBtn.addEventListener('click', () => {
|
| if (outputInp.value && outputInp.value.startsWith("")) {
|
| navigator.clipboard.writeText(outputInp.value).then(() => {
|
| const originalHtml = copyOutBtn.innerHTML;
|
| copyOutBtn.innerHTML = '<i class="fa-solid fa-check"></i>';
|
| setTimeout(() => { copyOutBtn.innerHTML = originalHtml; }, 2000);
|
| });
|
| }
|
| });
|
| }
|
| }
|
|
|
| |
| |
|
|
| async function handleGenerateSerialKeyAction() {
|
| const ptsInp = document.getElementById('serial-gen-points');
|
| const fbInp = document.getElementById('serial-gen-fb-cards');
|
| const waInp = document.getElementById('serial-gen-wa-cards');
|
| const recInp = document.getElementById('serial-gen-recipient-id');
|
| const passInp = document.getElementById('serial-gen-password');
|
| const noteInp = document.getElementById('serial-gen-note');
|
| const outputInp = document.getElementById('gen-serial-key-output');
|
| const statusMsg = document.getElementById('serial-gen-status-msg');
|
|
|
| if (!ptsInp || !fbInp || !waInp || !recInp || !passInp || !outputInp || !statusMsg) return;
|
|
|
|
|
| await loadDashboardState();
|
|
|
| const points = parseFloat(ptsInp.value) || 0.00;
|
| const fbCards = parseInt(fbInp.value) || 0;
|
| const waCards = parseInt(waInp.value) || 0;
|
| const recipientId = recInp.value.trim();
|
| const password = passInp.value;
|
| const note = noteInp ? noteInp.value.trim() : "";
|
|
|
| statusMsg.style.color = "var(--danger)";
|
| outputInp.value = "";
|
|
|
| if (points <= 0.00 && fbCards <= 0 && waCards <= 0) {
|
| statusMsg.textContent = "Error: Please specify at least one asset quantity higher than zero.";
|
| return;
|
| }
|
|
|
|
|
| if (recipientId.length !== 128) {
|
| statusMsg.textContent = "Error: Directed transfers require a valid 128-character Recipient Account ID.";
|
| return;
|
| }
|
|
|
|
|
| if (points > 0.00 && points > DashboardState.userPoints) {
|
| statusMsg.textContent = `Error: Insufficient USD balance. Available balance: $${DashboardState.userPoints.toFixed(2)} USD.`;
|
| return;
|
| }
|
| if (fbCards > 0 && fbCards > DashboardState.fbShares) {
|
| statusMsg.textContent = `Error: Insufficient FB cards. Available: ${DashboardState.fbShares} cards.`;
|
| return;
|
| }
|
| if (waCards > 0 && waCards > DashboardState.waShares) {
|
| statusMsg.textContent = `Error: Insufficient WA cards. Available: ${DashboardState.waShares} cards.`;
|
| return;
|
| }
|
|
|
| statusMsg.style.color = "var(--primary)";
|
| statusMsg.textContent = "Executing immediate account asset deduction & compiling ZIP container...";
|
|
|
|
|
| let deductionLog = [];
|
| if (points > 0.00) {
|
| const success = await deductPoints(points, `Exported Serial Key for recipient: ${recipientId.substring(0, 16)}...`);
|
| if (!success) {
|
| statusMsg.style.color = "var(--danger)";
|
| statusMsg.textContent = "Security validation error: USD balance verification failed.";
|
| return;
|
| }
|
| deductionLog.push(`$${points.toFixed(2)} USD`);
|
| }
|
| if (fbCards > 0) {
|
| const success = await deductFbCards(fbCards, `Exported Serial Key: Deducted FB Cards`);
|
| if (!success) {
|
| statusMsg.style.color = "var(--danger)";
|
| statusMsg.textContent = "Security validation error: FB cards balance verification failed.";
|
| return;
|
| }
|
| deductionLog.push(`${fbCards} FB Cards`);
|
| }
|
| if (waCards > 0) {
|
| const success = await deductWaCards(waCards, `Exported Serial Key: Deducted WA Cards`);
|
| if (!success) {
|
| statusMsg.style.color = "var(--danger)";
|
| statusMsg.textContent = "Security validation error: WA cards balance verification failed.";
|
| return;
|
| }
|
| deductionLog.push(`${waCards} WA Cards`);
|
| }
|
|
|
| await saveAccountData();
|
|
|
|
|
| const generatedKey = await generateLicenseSerialKey(points, fbCards, waCards, recipientId, password, note);
|
|
|
| if (generatedKey) {
|
| outputInp.value = generatedKey;
|
|
|
|
|
| const infoContent = `Fritree Secure Serial Key Licensing Transfer Report
|
| =========================================================
|
| Creation Date: ${new Date().toISOString()}
|
| Sender ID: ${DashboardState.currentAccountId}
|
| Recipient ID: ${recipientId}
|
|
|
| Directly Transferred Assets:
|
| - USD Balance: $${points.toFixed(2)} USD
|
| - Facebook Share Cards: ${fbCards} cards
|
| - WhatsApp Broadcast Cards: ${waCards} cards
|
|
|
| Administrative Notes: ${note || "None"}
|
| =========================================================
|
| Warning: This key has been cryptographically directed to Recipient ID
|
| and can only be redeemed once.`;
|
|
|
|
|
| const zipFiles = [
|
| { name: "Serial Key.txt", content: generatedKey },
|
| { name: "Password.txt", content: password || "No password assigned" },
|
| { name: "Information.txt", content: infoContent }
|
| ];
|
|
|
|
|
| const now = new Date();
|
| const pad = num => String(num).padStart(2, '0');
|
| const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;
|
|
|
| const zipBlob = createUncompressedZip(zipFiles);
|
| const zipUrl = URL.createObjectURL(zipBlob);
|
|
|
| const downloadAnchor = document.createElement('a');
|
| downloadAnchor.href = zipUrl;
|
| downloadAnchor.download = `Serial_Key_${recipientId.substring(0, 8)}_${timestamp}.zip`;
|
| downloadAnchor.click();
|
| URL.revokeObjectURL(zipUrl);
|
|
|
| statusMsg.style.color = "var(--success)";
|
| statusMsg.textContent = `Serial Key and direct ZIP package generated! Assets deducted: [${deductionLog.join(", ")}]. Check your downloads directory.`;
|
|
|
| ptsInp.value = "0.00";
|
| fbInp.value = "0";
|
| waInp.value = "0";
|
| recInp.value = "";
|
| passInp.value = "";
|
| if (noteInp) noteInp.value = "";
|
|
|
| await updatePointsUI();
|
| await updateAccountUI();
|
| } else {
|
| statusMsg.style.color = "var(--danger)";
|
| statusMsg.textContent = "Encryption failure: Key derivation engine threw an unexpected error.";
|
| }
|
| }
|
|
|
| |
| |
|
|
| async function handleRedeemSerialKeyAction() {
|
| const inputCode = document.getElementById('serial-redeem-input-code');
|
| const inputPass = document.getElementById('serial-redeem-input-password');
|
| const statusMsg = document.getElementById('serial-redeem-status-msg');
|
|
|
| if (!inputCode || !inputPass || !statusMsg) return;
|
|
|
| const code = inputCode.value.trim();
|
| const password = inputPass.value;
|
|
|
| statusMsg.style.color = "var(--danger)";
|
|
|
| if (!code) {
|
| statusMsg.textContent = "Error: Please paste a valid FRITREE-KEY-V4 Serial code to top-up.";
|
| return;
|
| }
|
|
|
| statusMsg.style.color = "var(--primary)";
|
| statusMsg.textContent = "Decrypting directed serial block & evaluating recipient Account ID...";
|
|
|
| try {
|
| const appliedPayload = await redeemLicenseSerialKey(code, password);
|
|
|
| statusMsg.style.color = "var(--success)";
|
|
|
| let creditLog = [];
|
| if (appliedPayload.points > 0.00) creditLog.push(`+$${parseFloat(appliedPayload.points).toFixed(2)} USD`);
|
| if (appliedPayload.fbCards > 0) creditLog.push(`+${appliedPayload.fbCards} FB Cards`);
|
| if (appliedPayload.waCards > 0) creditLog.push(`+${appliedPayload.waCards} WA Cards`);
|
|
|
| statusMsg.textContent = `Success! Serial redeemed. Credited: [${creditLog.join(", ")}].`;
|
|
|
| inputCode.value = "";
|
| inputPass.value = "";
|
|
|
| if (typeof window.addLog === 'function') {
|
| window.addLog(`Redeemed Top-up License Key successfully: [${appliedPayload.keyId.substring(0, 10)}]`, 'success');
|
| }
|
| } catch (err) {
|
| statusMsg.style.color = "var(--danger)";
|
| if (err.message === "DECRYPTION_FAILED") {
|
| statusMsg.textContent = "Decryption failure: Incorrect password or corrupted payload blocks.";
|
| } else if (err.message === "RECIPIENT_MISMATCH") {
|
| statusMsg.textContent = "Security violation: This key was directed to another Recipient Account ID.";
|
| } else if (err.message === "ALREADY_REDEEMED") {
|
| statusMsg.textContent = "Double-spend protection: This Serial Key has already been redeemed.";
|
| } else if (err.message === "INVALID_FORMAT") {
|
| statusMsg.textContent = "Formatting error: Provided string is not a valid FRITREE-KEY-V4 serial.";
|
| } else {
|
| statusMsg.textContent = "Validation error: Cryptographic signature mismatch.";
|
| }
|
| }
|
| }
|
|
|
| async function loadDashboardState() {
|
| DashboardState.userPoints = parseFloat(await FritreeStorage.get('userPoints', 100000.00)) || 0.00;
|
| DashboardState.usedSerials = await FritreeStorage.get('usedSerials', []);
|
| DashboardState.pointsTransactions = await FritreeStorage.get('pointsTransactions', []);
|
| DashboardState.balanceSignature = await FritreeStorage.get('userPointsSig', '');
|
| DashboardState.currentAccountId = await FritreeCrypto.getOrGenerateAccountId();
|
|
|
| DashboardState.fbShares = parseInt(await FritreeStorage.get('acc_fbShares', 25)) || 0;
|
| DashboardState.fbSharesSignature = await FritreeStorage.get('acc_fbShares_sig', '');
|
| DashboardState.waShares = parseInt(await FritreeStorage.get('acc_waShares', 25)) || 0;
|
| DashboardState.waSharesSignature = await FritreeStorage.get('acc_waShares_sig', '');
|
|
|
| DashboardState.userXP = await FritreeStorage.get('acc_userXP', 0);
|
| DashboardState.userLevel = await FritreeStorage.get('acc_userLevel', 1);
|
| DashboardState.lifetimeFb = await FritreeStorage.get('acc_lifetimeFb', 0);
|
| DashboardState.lifetimeWa = await FritreeStorage.get('acc_lifetimeWa', 0);
|
| DashboardState.lifetimeTasks = await FritreeStorage.get('acc_tasks', []);
|
|
|
|
|
| if (DashboardState.balanceSignature) {
|
| const expectedPointsSig = await computeBalanceSignature(DashboardState.userPoints);
|
| if (DashboardState.balanceSignature !== expectedPointsSig) {
|
| console.log("[Fritree Crypto] Re-aligning USD balance signature.");
|
| DashboardState.balanceSignature = expectedPointsSig;
|
| await FritreeStorage.set('userPointsSig', DashboardState.balanceSignature);
|
| }
|
| } else {
|
| DashboardState.balanceSignature = await computeBalanceSignature(DashboardState.userPoints);
|
| await FritreeStorage.set('userPointsSig', DashboardState.balanceSignature);
|
| }
|
|
|
| if (DashboardState.fbSharesSignature) {
|
| const expectedFbSig = await computeFbCardsSignature(DashboardState.fbShares);
|
| if (DashboardState.fbSharesSignature !== expectedFbSig) {
|
| console.log("[Fritree Crypto] Aligning FB cards balance signature.");
|
| DashboardState.fbSharesSignature = expectedFbSig;
|
| await FritreeStorage.set('acc_fbShares_sig', DashboardState.fbSharesSignature);
|
| }
|
| } else {
|
| DashboardState.fbSharesSignature = await computeFbCardsSignature(DashboardState.fbShares);
|
| await FritreeStorage.set('acc_fbShares_sig', DashboardState.fbSharesSignature);
|
| }
|
|
|
| if (DashboardState.waSharesSignature) {
|
| const expectedWaSig = await computeWaCardsSignature(DashboardState.waShares);
|
| if (DashboardState.waSharesSignature !== expectedWaSig) {
|
| console.log("[Fritree Crypto] Aligning WA cards balance signature.");
|
| DashboardState.waSharesSignature = expectedWaSig;
|
| await FritreeStorage.set('acc_waShares_sig', DashboardState.waSharesSignature);
|
| }
|
| } else {
|
| DashboardState.waSharesSignature = await computeWaCardsSignature(DashboardState.waShares);
|
| await FritreeStorage.set('acc_waShares_sig', DashboardState.waSharesSignature);
|
| }
|
|
|
| if (DashboardState.activeTasks.length === 0) {
|
| DashboardState.activeTasks = [];
|
| for (let i = 0; i < 4; i++) {
|
| DashboardState.activeTasks.push(generateRandomTask());
|
| }
|
| await saveAccountData();
|
| }
|
|
|
| DashboardState.isLoaded = true;
|
| }
|
|
|
| async function saveAccountData() {
|
| await FritreeStorage.set('userPoints', DashboardState.userPoints);
|
| await FritreeStorage.set('userPointsSig', DashboardState.balanceSignature);
|
| await FritreeStorage.set('acc_fbShares', DashboardState.fbShares);
|
| await FritreeStorage.set('acc_fbShares_sig', DashboardState.fbSharesSignature);
|
| await FritreeStorage.set('acc_waShares', DashboardState.waShares);
|
| await FritreeStorage.set('acc_waShares_sig', DashboardState.waSharesSignature);
|
| await FritreeStorage.set('acc_userXP', DashboardState.userXP);
|
| await FritreeStorage.set('acc_userLevel', DashboardState.userLevel);
|
| await FritreeStorage.set('acc_lifetimeFb', DashboardState.lifetimeFb);
|
| await FritreeStorage.set('acc_lifetimeWa', DashboardState.lifetimeWa);
|
| await FritreeStorage.set('acc_lifetimeTasks', DashboardState.lifetimeTasks);
|
| await FritreeStorage.set('acc_activeSub', 'Lifetime Unlimited');
|
| await FritreeStorage.set('acc_subExpiry', null);
|
| await FritreeStorage.set('acc_tasks', DashboardState.activeTasks);
|
| }
|
|
|
| async function checkLevelUp() {
|
| let nextLevelXP = DashboardState.userLevel * 1000;
|
| let leveledUp = false;
|
| while (DashboardState.userXP >= nextLevelXP) {
|
| DashboardState.userLevel++;
|
| DashboardState.userXP -= nextLevelXP;
|
| nextLevelXP = DashboardState.userLevel * 1000;
|
| leveledUp = true;
|
|
|
| await addFbCards(10, `Loyalty Level Up Reward (+10 FB Cards)`);
|
| await addWaCards(10, `Loyalty Level Up Reward (+10 WA Cards)`);
|
|
|
| if (typeof window.addLog === 'function') {
|
| window.addLog(`Level Up! Reached Level [${DashboardState.userLevel.toLocaleString('en-US')}]. Credited +10 FB Cards and +10 WA Cards.`, 'success');
|
| }
|
| }
|
| if (leveledUp) {
|
| await saveAccountData();
|
| if (typeof window.FritreeRotation !== 'undefined' && typeof window.FritreeRotation.celebrate === 'function') {
|
| window.FritreeRotation.celebrate();
|
| }
|
| }
|
| }
|
|
|
| async function addXP(amount) {
|
| DashboardState.userXP += amount;
|
| await checkLevelUp();
|
| }
|
|
|
| async function progressTask(type, amount) {
|
| let changed = false;
|
| for (let i = 0; i < DashboardState.activeTasks.length; i++) {
|
| if (DashboardState.activeTasks[i].type === type && DashboardState.activeTasks[i].progress < DashboardState.activeTasks[i].target) {
|
| DashboardState.activeTasks[i].progress += amount;
|
|
|
| if (DashboardState.activeTasks[i].progress >= DashboardState.activeTasks[i].target) {
|
| await addXP(DashboardState.activeTasks[i].xp);
|
|
|
| if (DashboardState.activeTasks[i].rewardSharesType === 'fb') {
|
| await addFbCards(DashboardState.activeTasks[i].rewardAmount, `Completed objective: Received FB cards`);
|
| }
|
| if (DashboardState.activeTasks[i].rewardSharesType === 'wa') {
|
| await addWaCards(DashboardState.activeTasks[i].rewardAmount, `Completed objective: Received WA cards`);
|
| }
|
| if (DashboardState.activeTasks[i].rewardSharesType === 'pts') {
|
| const usdReward = parseFloat(DashboardState.activeTasks[i].rewardAmount * 0.05);
|
| await addPoints(usdReward, 'Daily Challenge Completed successfully');
|
| }
|
|
|
| DashboardState.lifetimeTasks++;
|
| if (typeof window.addLog === 'function') {
|
| window.addLog(`Objective Completed: [${DashboardState.activeTasks[i].title}]! Gained +${(DashboardState.activeTasks[i].xp).toLocaleString('en-US')} XP.`, 'success');
|
| }
|
|
|
| DashboardState.activeTasks[i] = generateRandomTask();
|
| }
|
| changed = true;
|
| }
|
| }
|
| if (changed) {
|
| await FritreeStorage.set('acc_tasks', DashboardState.activeTasks);
|
| await updateAccountUI();
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| async function recordActionSuccess(platform) {
|
| await loadDashboardState();
|
|
|
| if (platform === 'facebook') {
|
| await progressTask('fb_post', 1);
|
| } else if (platform === 'whatsapp') {
|
| await progressTask('wa_send', 1);
|
| }
|
| }
|
|
|
| function renderTasksUI() {
|
| const container = document.getElementById('tasks-container');
|
| if (!container) return;
|
| container.innerHTML = '';
|
|
|
| const multLbl = document.getElementById('task-xp-multiplier');
|
| if (multLbl) multLbl.textContent = `1x`;
|
|
|
| DashboardState.activeTasks.forEach((task) => {
|
| const card = document.createElement('div');
|
| card.className = 'task-card';
|
|
|
| const pct = Math.min(100, Math.round((task.progress / task.target) * 100));
|
|
|
| let icon = 'fa-list-check';
|
| if (task.type === 'fb_post') icon = 'fa-facebook';
|
| if (task.type === 'wa_send') icon = 'fa-whatsapp';
|
|
|
| const rewardDesc = task.rewardSharesType === 'pts' ?
|
| `$${(task.rewardAmount * 0.05).toFixed(2)} USD` :
|
| `${task.rewardAmount.toLocaleString('en-US')} ${task.rewardSharesType.toUpperCase()} Cards`;
|
|
|
| card.innerHTML = `
|
| <div style="flex: 1; text-align: left; direction: ltr;">
|
| <div style="display:flex; justify-content: space-between; margin-bottom: 5px; align-items: center;">
|
| <strong style="font-size: 13px; color: #1e293b;"><i class="fa-solid ${icon}" style="color:var(--primary); margin-right: 5px;"></i>${task.title}</strong>
|
| <span style="font-size: 11px; font-weight:bold; color: #10b981;">${task.progress.toLocaleString('en-US')}/${task.target.toLocaleString('en-US')}</span>
|
| </div>
|
| <div class="progress-bar-bg" style="height: 6px; margin-top: 0;">
|
| <div class="progress-bar-fill" style="width: ${pct}%; background: linear-gradient(90deg, #3b82f6, #60a5fa); left: 0; right: auto;"></div>
|
| </div>
|
| <div style="font-size: 11px; color: var(--text-muted); margin-top: 5px; font-weight: bold;">
|
| Reward: ${rewardDesc} | ${(task.xp).toLocaleString('en-US')} XP
|
| </div>
|
| </div>
|
| `;
|
| container.appendChild(card);
|
| });
|
| }
|
|
|
| async function updateStealthShieldWidgetUI() {
|
| const scrollStepEl = document.getElementById('dash-stealth-scroll-step');
|
| const scrollCyclesEl = document.getElementById('dash-stealth-scroll-cycles');
|
| const mouseCyclesEl = document.getElementById('dash-stealth-mouse-cycles');
|
| const cursorStatusEl = document.getElementById('dash-stealth-cursor-status');
|
|
|
| const shieldConfig = await FritreeStorage.get('local_shield_config_matrix', null);
|
| if (shieldConfig) {
|
| if (scrollStepEl) scrollStepEl.textContent = `${(shieldConfig.scrollStepPixels || 250).toLocaleString('en-US')} px`;
|
| if (scrollCyclesEl) scrollCyclesEl.textContent = `${(shieldConfig.scrollTotalCycles || 4).toLocaleString('en-US')} Cycles`;
|
| if (mouseCyclesEl) mouseCyclesEl.textContent = `${(shieldConfig.mouseMovementCycles || 5).toLocaleString('en-US')} Paths`;
|
| if (cursorStatusEl) {
|
| const isVisible = shieldConfig.showVirtualCursor !== false;
|
| cursorStatusEl.textContent = isVisible ? "Active & Visible" : "Stealth Hidden";
|
| cursorStatusEl.style.color = isVisible ? "#10b981" : "#64748b";
|
| }
|
| }
|
| }
|
|
|
| async function updateAccountUI() {
|
| await loadDashboardState();
|
|
|
| const lvl = document.getElementById('account-level');
|
| const xp = document.getElementById('account-xp');
|
| const fbB = document.getElementById('fb-shares-balance');
|
| const waB = document.getElementById('wa-shares-balance');
|
| const sPts = document.getElementById('store-pts-balance');
|
|
|
| const dLvl = document.getElementById('acc-details-level');
|
| const dXp = document.getElementById('acc-details-xp');
|
| const dNXp = document.getElementById('acc-details-next-xp');
|
| const dRank = document.getElementById('acc-details-rank');
|
| const dFill = document.getElementById('acc-details-xp-fill');
|
|
|
| const lFb = document.getElementById('acc-lifetime-fb');
|
| const lWa = document.getElementById('acc-lifetime-wa');
|
| const lTsk = document.getElementById('acc-lifetime-tasks');
|
|
|
| const wUsd = document.getElementById('wallet-usd-balance');
|
| const wFb = document.getElementById('wallet-fb-cards');
|
| const wWa = document.getElementById('wallet-wa-cards');
|
|
|
| if (lvl) lvl.textContent = DashboardState.userLevel.toLocaleString('en-US');
|
| if (xp) xp.textContent = DashboardState.userXP.toLocaleString('en-US');
|
| if (fbB) fbB.textContent = DashboardState.fbShares.toLocaleString('en-US');
|
| if (waB) waB.textContent = DashboardState.waShares.toLocaleString('en-US');
|
| if (sPts) sPts.textContent = DashboardState.userPoints.toFixed(2);
|
|
|
| const nextXP = DashboardState.userLevel * 1000;
|
| if (dLvl) dLvl.textContent = DashboardState.userLevel.toLocaleString('en-US');
|
| if (dXp) dXp.textContent = DashboardState.userXP.toLocaleString('en-US');
|
| if (dNXp) dNXp.textContent = nextXP.toLocaleString('en-US');
|
| if (dFill) dFill.style.width = `${(DashboardState.userXP / nextXP) * 100}%`;
|
|
|
| if (wUsd) wUsd.textContent = DashboardState.userPoints.toFixed(2);
|
| if (wFb) wFb.textContent = DashboardState.fbShares.toLocaleString('en-US');
|
| if (wWa) wWa.textContent = DashboardState.waShares.toLocaleString('en-US');
|
|
|
| if (dRank) {
|
| if (DashboardState.userLevel < 5) dRank.textContent = 'Novice Marketer';
|
| else if (DashboardState.userLevel < 15) dRank.textContent = 'Pro Marketer';
|
| else if (DashboardState.userLevel < 30) dRank.textContent = 'Broadcast Expert';
|
| else dRank.textContent = 'Certified Automation Master';
|
| }
|
|
|
| if (lFb) lFb.textContent = DashboardState.lifetimeFb.toLocaleString('en-US');
|
| if (lWa) lWa.textContent = DashboardState.lifetimeWa.toLocaleString('en-US');
|
| if (lTsk) lTsk.textContent = DashboardState.lifetimeTasks.toLocaleString('en-US');
|
|
|
| renderTasksUI();
|
| updateStealthShieldWidgetUI();
|
| }
|
|
|
|
|
|
|
|
|
| function renderStoreUI() {
|
| const fbPackContainer = document.getElementById('store-fb-packages');
|
| const waPackContainer = document.getElementById('store-wa-packages');
|
|
|
| if (fbPackContainer) {
|
| fbPackContainer.innerHTML = `
|
| <div class="store-package-card" style="background: linear-gradient(180deg, #ffffff, #f0f9ff); margin-bottom: 10px; border-radius: 12px; padding: 15px; direction: ltr; text-align: left;">
|
| <div style="display:flex; justify-content:space-between; align-items:center;">
|
| <strong style="color: var(--primary);"><i class="fa-solid fa-calculator" style="margin-right:5px;"></i>Facebook Card Exchange</strong>
|
| <span class="badge" style="background: #dbeafe; color: #1d4ed8; font-weight: 800;">Min $0.01 (20 Cards)</span>
|
| </div>
|
| <div style="margin-top: 10px; display: flex; flex-direction: column; gap: 8px;">
|
| <label style="font-size: 11px; font-weight: bold; color: #475569;">Enter USD purchase amount ($):</label>
|
| <div style="display: flex; gap: 8px; align-items: center;">
|
| <input type="number" id="calc-fb-points" class="search-box" value="1.00" min="0.01" step="0.01" style="flex: 1; padding: 8px; text-align: center; font-weight: bold;">
|
| <span style="font-weight: bold; color: #64748b;">=</span>
|
| <div style="flex: 1.5; background: white; border: 1px solid var(--border); padding: 8px; border-radius: var(--radius-md); text-align: center; font-weight: 900; color: #1d4ed8;" id="calc-fb-result">2000 Cards</div>
|
| </div>
|
| </div>
|
| <button class="btn-primary" style="font-size: 12px; padding: 10px; width:100%; margin-top:8px; border-radius: 8px;" id="btn-calc-buy-fb"><i class="fa-solid fa-cart-shopping"></i>Confirm FB Card Purchase</button>
|
| </div>
|
| `;
|
|
|
| const calcFbPoints = document.getElementById('calc-fb-points');
|
| const calcFbResult = document.getElementById('calc-fb-result');
|
| const btnCampBuyFb = document.getElementById('btn-calc-buy-fb');
|
|
|
| const updateFbCalc = () => {
|
| const usd = Math.max(0.01, parseFloat(calcFbPoints.value) || 0.00);
|
| const cards = Math.round(usd * 2000);
|
| calcFbResult.textContent = `${cards.toLocaleString('en-US')} Cards`;
|
| };
|
|
|
| if (calcFbPoints) {
|
| calcFbPoints.addEventListener('input', updateFbCalc);
|
| calcFbPoints.addEventListener('change', updateFbCalc);
|
| }
|
|
|
| if (btnCampBuyFb) {
|
| btnCampBuyFb.addEventListener('click', () => {
|
| const usd = Math.max(0.01, parseFloat(calcFbPoints.value) || 0.00);
|
| const cards = Math.round(usd * 2000);
|
| buyPackage('fb', cards, usd);
|
| });
|
| }
|
| }
|
|
|
| if (waPackContainer) {
|
| waPackContainer.innerHTML = `
|
| <div class="store-package-card" style="background: linear-gradient(180deg, #ffffff, #eefdf3); margin-bottom: 10px; border-radius: 12px; padding: 15px; direction: ltr; text-align: left;">
|
| <div style="display:flex; justify-content:space-between; align-items:center;">
|
| <strong style="color: #10b981;"><i class="fa-solid fa-calculator" style="margin-right:5px;"></i>WhatsApp Card Exchange</strong>
|
| <span class="badge" style="background: #e8f5e9; color: #15803d; font-weight: 800;">Min $0.01 (10 Cards)</span>
|
| </div>
|
| <div style="margin-top: 10px; display: flex; flex-direction: column; gap: 8px;">
|
| <label style="font-size: 11px; font-weight: bold; color: #475569;">Enter USD purchase amount ($):</label>
|
| <div style="display: flex; gap: 8px; align-items: center;">
|
| <input type="number" id="calc-wa-points" class="search-box" value="1.00" min="0.01" step="0.01" style="flex: 1; padding: 8px; text-align: center; font-weight: bold;">
|
| <span style="font-weight: bold; color: #64748b;">=</span>
|
| <div style="flex: 1.5; background: white; border: 1px solid var(--border); padding: 8px; border-radius: var(--radius-md); text-align: center; font-weight: 900; color: #10b981;" id="calc-wa-result">1000 Cards</div>
|
| </div>
|
| </div>
|
| <button class="btn-primary" style="background: #10b981; border: none; font-size: 12px; padding: 10px; width:100%; margin-top:8px; border-radius: 8px;" id="btn-calc-buy-wa"><i class="fa-solid fa-cart-shopping"></i>Confirm WA Card Purchase</button>
|
| </div>
|
| `;
|
|
|
| const calcWaPoints = document.getElementById('calc-wa-points');
|
| const calcWaResult = document.getElementById('calc-wa-result');
|
| const btnCampBuyWa = document.getElementById('btn-calc-buy-wa');
|
|
|
| const updateWaCalc = () => {
|
| const usd = Math.max(0.01, parseFloat(calcWaPoints.value) || 0.00);
|
| const cards = Math.round(usd * 1000);
|
| calcWaResult.textContent = `${cards.toLocaleString('en-US')} Cards`;
|
| };
|
|
|
| if (calcWaPoints) {
|
| calcWaPoints.addEventListener('input', updateWaCalc);
|
| calcWaPoints.addEventListener('change', updateWaCalc);
|
| }
|
|
|
| if (btnCampBuyWa) {
|
| btnCampBuyWa.addEventListener('click', () => {
|
| const usd = Math.max(0.01, parseFloat(calcWaPoints.value) || 0.00);
|
| const cards = Math.round(usd * 1000);
|
| buyPackage('wa', cards, usd);
|
| });
|
| }
|
| }
|
| }
|
|
|
| async function buyPackage(type, amount, cost) {
|
| if (parseFloat(cost) < 0.01) {
|
| alert('Minimum required exchange transaction value is $0.01 USD.');
|
| return;
|
| }
|
| if (DashboardState.userPoints < cost) {
|
| alert(`Insufficient USD balance. Exchange requires $${cost.toFixed(2)} USD.`);
|
| return;
|
| }
|
| const success = await deductPoints(cost, `Purchased: +${amount} ${type.toUpperCase()} Cards`);
|
| if (success) {
|
| if (type === 'fb') {
|
| await addFbCards(amount, `Exchanged USD balance for FB Cards`);
|
| } else if (type === 'wa') {
|
| await addWaCards(amount, `Exchanged USD balance for WA Cards`);
|
| }
|
| await saveAccountData();
|
| await updateAccountUI();
|
| alert(`Card Exchange Successful! Purchased +${amount} campaign cards for $${cost.toFixed(2)} USD.`);
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| async function handleAccountRegeneration() {
|
| if (!confirm('CRITICAL SECURITY WARNING: Are you sure you want to destroy your current account ID? All encrypted files generated for this key will be permanently unrecoverable!')) {
|
| return;
|
| }
|
|
|
| DashboardState.currentAccountId = await FritreeCrypto.regenerateAccountId();
|
| const accountIdDisplay = document.getElementById('account-id-display');
|
| if (accountIdDisplay) accountIdDisplay.value = DashboardState.currentAccountId;
|
|
|
| const supportWorkspaceIdDisplay = document.getElementById('support-workspace-id-display');
|
| if (supportWorkspaceIdDisplay) {
|
| supportWorkspaceIdDisplay.value = DashboardState.currentAccountId;
|
| }
|
|
|
| if (typeof window.addLog === 'function') {
|
| window.addLog('Security Notice: Account identifier destroyed. Generated new 128-character public key.', 'warn');
|
| }
|
| alert('Workspace public key regenerated successfully.');
|
| }
|
|
|
| function showPointsModal() {
|
| const modal = document.getElementById('points-modal');
|
| if (modal) {
|
| modal.style.display = 'flex';
|
| updatePointsUI();
|
| }
|
| }
|
|
|
| function hidePointsModal() {
|
| const modal = document.getElementById('points-modal');
|
| if (modal) modal.style.display = 'none';
|
| }
|
|
|
| async function updatePointsUI() {
|
| await loadDashboardState();
|
|
|
| const badgeBalance = document.getElementById('points-badge');
|
| const modalBalance = document.getElementById('modal-points-balance');
|
| const storeBalance = document.getElementById('store-pts-balance');
|
| const txHistoryList = document.getElementById('points-history-list');
|
| const walletUsdTbody = document.getElementById('wallet-transactions-tbody');
|
|
|
| if (badgeBalance) badgeBalance.textContent = DashboardState.userPoints.toFixed(2);
|
| if (modalBalance) modalBalance.textContent = DashboardState.userPoints.toFixed(2);
|
| if (storeBalance) storeBalance.textContent = DashboardState.userPoints.toFixed(2);
|
|
|
|
|
| if (txHistoryList) {
|
| txHistoryList.innerHTML = '';
|
| if (DashboardState.pointsTransactions.length === 0) {
|
| txHistoryList.innerHTML = '<div style="color:var(--text-muted); text-align:center; font-size:11px; padding:10px;">Transaction ledger is completely empty.</div>';
|
| } else {
|
| const displayedTx = [...DashboardState.pointsTransactions].reverse().slice(0, 15);
|
| displayedTx.forEach(tx => {
|
| const row = document.createElement('div');
|
| row.style.cssText = 'display: flex; justify-content: space-between; padding: 10px; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 11px; align-items:center; background: linear-gradient(180deg, #ffffff, #f7fbfe); margin-bottom: 4px; direction: ltr; text-align: left;';
|
|
|
| const isAdd = tx.type === 'add' || tx.type === 'add_fb' || tx.type === 'add_wa';
|
| const typeColor = isAdd ? '#16a34a' : '#ef4444';
|
| const prefix = isAdd ? '+' : '-';
|
|
|
| let icon = '';
|
| let displayAmt = '';
|
| let assetName = 'USD';
|
|
|
| if (tx.type.includes('fb')) {
|
| assetName = 'FB Cards';
|
| displayAmt = `${tx.amount} Cards`;
|
| icon = `<i class="fa-brands fa-facebook" style="color:${typeColor}; font-size:14px; margin-right:5px;"></i>`;
|
| } else if (tx.type.includes('wa')) {
|
| assetName = 'WA Cards';
|
| displayAmt = `${tx.amount} Cards`;
|
| icon = `<i class="fa-brands fa-whatsapp" style="color:${typeColor}; font-size:14px; margin-right:5px;"></i>`;
|
| } else {
|
| displayAmt = `$${parseFloat(tx.amount).toFixed(2)}`;
|
| icon = isAdd ? '<i class="fa-solid fa-circle-plus" style="color:#16a34a; font-size:14px; margin-right:5px;"></i>' : '<i class="fa-solid fa-circle-minus" style="color:#ef4444; font-size:14px; margin-right:5px;"></i>';
|
| }
|
|
|
| row.innerHTML = `
|
| <div style="display:flex; align-items:center; gap: 8px;">
|
| ${icon}
|
| <div style="display:flex; flex-direction:column; gap: 2px;">
|
| <span style="font-weight:bold; color:#1e293b; font-size: 12px;">${sanitizeText(tx.desc)}</span>
|
| <span style="color:#64748b; font-size:10px;">${new Date(tx.date).toLocaleString('en-US')}</span>
|
| </div>
|
| </div>
|
| <div style="font-weight:900; color:${typeColor}; font-size: 14px;">
|
| ${prefix}${displayAmt}
|
| </div>
|
| `;
|
| txHistoryList.appendChild(row);
|
| });
|
| }
|
| }
|
|
|
|
|
| if (walletUsdTbody) {
|
| walletUsdTbody.innerHTML = '';
|
| if (DashboardState.pointsTransactions.length === 0) {
|
| walletUsdTbody.innerHTML = '<tr><td colspan="4" style="text-align: center; color: var(--text-muted); padding: 15px;"><i class="fa-solid fa-circle-info"></i>No transaction records found in this vault.</td></tr>';
|
| } else {
|
| const chronologicalList = [...DashboardState.pointsTransactions].reverse();
|
| chronologicalList.forEach(tx => {
|
| const tr = document.createElement('tr');
|
|
|
| const isAdd = tx.type === 'add' || tx.type === 'add_fb' || tx.type === 'add_wa';
|
| const typeColor = isAdd ? '#16a34a' : '#ef4444';
|
| const prefix = isAdd ? '+' : '-';
|
|
|
| let assetName = 'USD';
|
| let displayAmt = '';
|
| if (tx.type.includes('fb')) {
|
| assetName = 'Facebook Cards';
|
| displayAmt = `${tx.amount} Cards`;
|
| } else if (tx.type.includes('wa')) {
|
| assetName = 'WhatsApp Cards';
|
| displayAmt = `${tx.amount} Cards`;
|
| } else {
|
| displayAmt = `$${parseFloat(tx.amount).toFixed(2)}`;
|
| }
|
|
|
| tr.innerHTML = `
|
| <td style="color:#64748b; font-family: monospace; font-size: 10px; padding: 10px;">${new Date(tx.date).toLocaleString('en-US')}</td>
|
| <td style="font-weight:600; color:#1e293b; font-size: 9px; padding: 5px;">${sanitizeText(tx.desc)}</td>
|
| <td style="text-align:center; font-size: 9px; padding: 5px; "><span class="badge" style="font-size:9px; color:#475569; font-weight:700; padding: 3px 6px;">${assetName}</span></td>
|
| <td style="text-align:right; font-weight:900; color:${typeColor}; font-size:9px; padding: 10px;">${prefix}${displayAmt}</td>
|
| `;
|
| walletUsdTbody.appendChild(tr);
|
| });
|
| }
|
| }
|
| }
|
|
|
| async function addPointsTransaction(desc, amount, type) {
|
| const txId = 'tx_' + Date.now() + '_' + Math.random().toString(36).substr(2, 4);
|
| const timestamp = new Date().toISOString();
|
|
|
| let signature = "";
|
| if (typeof FritreeWallet !== 'undefined' && typeof FritreeWallet.signReceipt === 'function') {
|
| signature = await FritreeWallet.signReceipt(txId, amount, type, desc, timestamp);
|
| }
|
|
|
| DashboardState.pointsTransactions.push({
|
| id: txId,
|
| desc: desc,
|
| amount: amount,
|
| type: type,
|
| date: timestamp,
|
| signature: signature
|
| });
|
|
|
| if (DashboardState.pointsTransactions.length > 200) DashboardState.pointsTransactions.shift();
|
|
|
| await FritreeStorage.set('pointsTransactions', DashboardState.pointsTransactions);
|
| await updatePointsUI();
|
| }
|
|
|
| async function deductPoints(amount, desc) {
|
| const costUSD = parseFloat(amount);
|
| const computedSig = await computeBalanceSignature(DashboardState.userPoints);
|
|
|
| if (!DashboardState.balanceSignature || DashboardState.balanceSignature === '') {
|
| DashboardState.balanceSignature = computedSig;
|
| await FritreeStorage.set('userPointsSig', DashboardState.balanceSignature);
|
| }
|
|
|
| if (DashboardState.balanceSignature !== computedSig && DashboardState.userPoints !== 100000.00) {
|
| console.log("[Fritree Storage] Resetting modified USD balance signatures.");
|
| DashboardState.balanceSignature = computedSig;
|
| await FritreeStorage.set('userPointsSig', DashboardState.balanceSignature);
|
| }
|
|
|
| if (DashboardState.userPoints < costUSD) return false;
|
| DashboardState.userPoints = parseFloat(Math.max(0.00, DashboardState.userPoints - costUSD));
|
|
|
| DashboardState.balanceSignature = await computeBalanceSignature(DashboardState.userPoints);
|
| await FritreeStorage.set('userPoints', DashboardState.userPoints);
|
| await FritreeStorage.set('userPointsSig', DashboardState.balanceSignature);
|
|
|
| await addPointsTransaction(desc, costUSD, 'deduct');
|
| return true;
|
| }
|
|
|
| async function addPoints(amount, desc) {
|
| const addUSD = parseFloat(amount);
|
| DashboardState.userPoints = parseFloat(DashboardState.userPoints + addUSD);
|
| DashboardState.balanceSignature = await computeBalanceSignature(DashboardState.userPoints);
|
| await FritreeStorage.set('userPoints', DashboardState.userPoints);
|
| await FritreeStorage.set('userPointsSig', DashboardState.balanceSignature);
|
|
|
| await addPointsTransaction(desc, addUSD, 'add');
|
| }
|
|
|
| async function deductFbCards(amount, desc) {
|
| const computedSig = await computeFbCardsSignature(DashboardState.fbShares);
|
|
|
| if (!DashboardState.fbSharesSignature || DashboardState.fbSharesSignature === '') {
|
| DashboardState.fbSharesSignature = computedSig;
|
| await FritreeStorage.set('acc_fbShares_sig', DashboardState.fbSharesSignature);
|
| }
|
|
|
| if (DashboardState.fbSharesSignature !== computedSig && DashboardState.fbShares !== 25) {
|
| console.log("[Fritree Storage] Resetting FB cards balance signature.");
|
| DashboardState.fbSharesSignature = computedSig;
|
| await FritreeStorage.set('acc_fbShares_sig', DashboardState.fbSharesSignature);
|
| }
|
|
|
| if (DashboardState.fbShares < amount) return false;
|
| DashboardState.fbShares = Math.max(0, DashboardState.fbShares - amount);
|
|
|
| DashboardState.fbSharesSignature = await computeFbCardsSignature(DashboardState.fbShares);
|
| await FritreeStorage.set('acc_fbShares', DashboardState.fbShares);
|
| await FritreeStorage.set('acc_fbShares_sig', DashboardState.fbSharesSignature);
|
|
|
| await addPointsTransaction(desc, amount, 'deduct_fb');
|
| return true;
|
| }
|
|
|
| async function addFbCards(amount, desc) {
|
| DashboardState.fbShares = (parseInt(DashboardState.fbShares) || 0) + amount;
|
| DashboardState.fbSharesSignature = await computeFbCardsSignature(DashboardState.fbShares);
|
| await FritreeStorage.set('acc_fbShares', DashboardState.fbShares);
|
| await FritreeStorage.set('acc_fbShares_sig', DashboardState.fbSharesSignature);
|
|
|
| await addPointsTransaction(desc, amount, 'add_fb');
|
| }
|
|
|
| async function deductWaCards(amount, desc) {
|
| const computedSig = await computeWaCardsSignature(DashboardState.waShares);
|
|
|
| if (!DashboardState.waSharesSignature || DashboardState.waSharesSignature === '') {
|
| DashboardState.waSharesSignature = computedSig;
|
| await FritreeStorage.set('acc_waShares_sig', DashboardState.waSharesSignature);
|
| }
|
|
|
| if (DashboardState.waSharesSignature !== computedSig && DashboardState.waShares !== 25) {
|
| console.log("[Fritree Storage] Resetting WA cards balance signature.");
|
| DashboardState.waSharesSignature = computedSig;
|
| await FritreeStorage.set('acc_waShares_sig', DashboardState.waSharesSignature);
|
| }
|
|
|
| if (DashboardState.waShares < amount) return false;
|
| DashboardState.waShares = Math.max(0, DashboardState.waShares - amount);
|
|
|
| DashboardState.waSharesSignature = await computeWaCardsSignature(DashboardState.waShares);
|
| await FritreeStorage.set('acc_waShares', DashboardState.waShares);
|
| await FritreeStorage.set('acc_waShares_sig', DashboardState.waSharesSignature);
|
|
|
| await addPointsTransaction(desc, amount, 'deduct_wa');
|
| return true;
|
| }
|
|
|
| async function addWaCards(amount, desc) {
|
| DashboardState.waShares = (parseInt(DashboardState.waShares) || 0) + amount;
|
| DashboardState.waSharesSignature = await computeWaCardsSignature(DashboardState.waShares);
|
| await FritreeStorage.set('acc_waShares', DashboardState.waShares);
|
| await FritreeStorage.set('acc_waShares_sig', DashboardState.waSharesSignature);
|
|
|
| await addPointsTransaction(desc, amount, 'add_wa');
|
| }
|
|
|
| function syncDashboardWidgets(storageData) {
|
| const waDashSent = document.getElementById('wa-dash-sent-today');
|
| if (waDashSent && storageData.wa_sent_today !== undefined) {
|
| waDashSent.textContent = (storageData.wa_sent_today || 0).toLocaleString('en-US');
|
| }
|
| }
|
|
|
| function startAutomaticSyncLoop() {
|
| setInterval(() => {
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
| chrome.storage.local.get(['wa_sent_today'], (res) => {
|
| syncDashboardWidgets(res);
|
| });
|
| }
|
| updateStealthShieldWidgetUI();
|
| }, 5000);
|
|
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.onChanged) {
|
| chrome.storage.onChanged.addListener(async (changes, namespace) => {
|
| if (namespace === 'local') {
|
| await updateAccountUI();
|
| await updatePointsUI();
|
| }
|
| });
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| global.FritreeDashboard = {
|
| init: initDashboardModule,
|
| getPoints: () => DashboardState.userPoints,
|
| addPoints: addPoints,
|
| deductPoints: deductPoints,
|
| addTransaction: addPointsTransaction,
|
| syncWidgets: syncDashboardWidgets,
|
|
|
| getFbShares: () => DashboardState.fbShares,
|
| getWaShares: () => DashboardState.waShares,
|
| buyPackage: buyPackage,
|
| recordActionSuccess: recordActionSuccess,
|
| updateAccountUI: updateAccountUI,
|
| getSubscriptionFeatures: getSubscriptionFeatures,
|
|
|
| generateLicenseKey: generateLicenseSerialKey,
|
| redeemLicenseKey: redeemLicenseSerialKey
|
| };
|
|
|
| if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| initDashboardModule();
|
| } else {
|
| document.addEventListener('DOMContentLoaded', () => initDashboardModule());
|
| }
|
|
|
| })(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this); |