|
|
|
|
|
|
|
|
| (global => {
|
| 'use strict';
|
|
|
| const DB_NAME = "FritreeEnterpriseDB";
|
| const DB_VERSION = 3;
|
|
|
| const STORES = {
|
| MEDIA: "media_assets",
|
| HISTORY: "campaign_history",
|
| SYSTEM: "system_metadata"
|
| };
|
|
|
|
|
|
|
| const SECURE_SIS_KEYS = new Set([
|
| "acc_userLevel",
|
| "acc_userXP",
|
| "acc_lifetimeFb",
|
| "acc_lifetimeWa",
|
| "acc_lifetimeTasks",
|
| "local_previous_posts",
|
| "wa_connected",
|
| "wa_sent_today",
|
| "acc_fbShares",
|
| "acc_waShares",
|
| "userPoints"
|
| ]);
|
|
|
|
|
| const INTEGRITY_SEAL_SALT = "FritreeSymmetricIntegritySeal_WebCrypto_Strict_SHA256_Production_Salt_2026";
|
|
|
|
|
| const LIGHTWEIGHT_CONFIG_KEYS = new Set([
|
| "userPoints",
|
| "userPointsSig",
|
| "fritree_account_id",
|
| "acc_fbShares",
|
| "acc_fbShares_sig",
|
| "acc_waShares",
|
| "acc_waShares_sig",
|
| "acc_userLevel",
|
| "acc_userXP",
|
| "acc_lifetimeFb",
|
| "acc_lifetimeWa",
|
| "acc_lifetimeTasks",
|
| "acc_activeSub",
|
| "acc_subExpiry",
|
| "local_sleep_intervals",
|
| "local_protection_settings",
|
| "local_shield_config_matrix",
|
| "wa_connected",
|
| "wa_phone_number",
|
| "wa_profile_name",
|
| "wa_sent_today"
|
| ]);
|
|
|
| let dbInstance = null;
|
| let isDbInitializing = false;
|
| const dbInitPromiseResolvers = [];
|
|
|
|
|
|
|
|
|
| class AsyncWriteQueue {
|
| constructor() {
|
| this.queue = [];
|
| this.isProcessing = false;
|
| }
|
|
|
| enqueue(task) {
|
| return new Promise((resolve, reject) => {
|
| this.queue.push({ task, resolve, reject });
|
| this.processNext();
|
| });
|
| }
|
|
|
| async processNext() {
|
| if (this.isProcessing || this.queue.length === 0) return;
|
| this.isProcessing = true;
|
|
|
| const { task, resolve, reject } = this.queue.shift();
|
| try {
|
| const result = await task();
|
| resolve(result);
|
| } catch (err) {
|
| reject(err);
|
| } finally {
|
| this.isProcessing = false;
|
| this.processNext();
|
| }
|
| }
|
| }
|
|
|
| const storageWriteQueue = new AsyncWriteQueue();
|
|
|
|
|
|
|
|
|
| function initIndexedDB() {
|
| if (dbInstance) return Promise.resolve(dbInstance);
|
| if (isDbInitializing) {
|
| return new Promise((resolve, reject) => {
|
| dbInitPromiseResolvers.push({ resolve, reject });
|
| });
|
| }
|
|
|
| isDbInitializing = true;
|
| return new Promise((resolve, reject) => {
|
| const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
|
| request.onupgradeneeded = event => {
|
| const db = event.target.result;
|
| Object.values(STORES).forEach(storeName => {
|
| if (!db.objectStoreNames.contains(storeName)) {
|
| db.createObjectStore(storeName, { keyPath: "id" });
|
| }
|
| });
|
| };
|
|
|
| request.onsuccess = event => {
|
| dbInstance = event.target.result;
|
| isDbInitializing = false;
|
| resolve(dbInstance);
|
|
|
| dbInitPromiseResolvers.forEach(r => r.resolve(dbInstance));
|
| dbInitPromiseResolvers.length = 0;
|
| };
|
|
|
| request.onerror = event => {
|
| isDbInitializing = false;
|
| reject(event.target.error);
|
|
|
| dbInitPromiseResolvers.forEach(r => r.reject(event.target.error));
|
| dbInitPromiseResolvers.length = 0;
|
| };
|
| });
|
| }
|
|
|
| function base64ToBlob(base64Data, mimeType) {
|
| const byteCharacters = atob(base64Data.split(",")[1] || base64Data);
|
| const byteArrays = [];
|
|
|
| for (let offset = 0; offset < byteCharacters.length; offset += 512) {
|
| const slice = byteCharacters.slice(offset, offset + 512);
|
| const byteNumbers = new Array(slice.length);
|
| for (let i = 0; i < slice.length; i++) {
|
| byteNumbers[i] = slice.charCodeAt(i);
|
| }
|
| const byteArray = new Uint8Array(byteNumbers);
|
| byteArrays.push(byteArray);
|
| }
|
|
|
| return new Blob(byteArrays, { type: mimeType });
|
| }
|
|
|
| function blobToBase64(blob) {
|
| return new Promise((resolve, reject) => {
|
| const reader = new FileReader();
|
| reader.onloadend = () => resolve(reader.result);
|
| reader.onerror = reject;
|
| reader.readAsDataURL(blob);
|
| });
|
| }
|
|
|
| async function writeIndexedDBEntry(storeName, key, encryptedValue) {
|
| const db = await initIndexedDB();
|
| return new Promise((resolve, reject) => {
|
| const transaction = db.transaction([storeName], "readwrite");
|
| const store = transaction.objectStore(storeName);
|
|
|
| const putRequest = store.put({ id: key, payload: encryptedValue, updated_at: Date.now() });
|
|
|
| putRequest.onsuccess = () => resolve(true);
|
| putRequest.onerror = e => reject(e.target.error);
|
| });
|
| }
|
|
|
| async function readIndexedDBEntry(storeName, key) {
|
| const db = await initIndexedDB();
|
| return new Promise(resolve => {
|
| const transaction = db.transaction([storeName], "readonly");
|
| const store = transaction.objectStore(storeName);
|
| const getRequest = store.get(key);
|
|
|
| getRequest.onsuccess = e => {
|
| const record = e.target.result;
|
| resolve(record ? record.payload : null);
|
| };
|
|
|
| getRequest.onerror = () => {
|
| resolve(null);
|
| };
|
| });
|
| }
|
|
|
| async function deleteIndexedDBEntry(storeName, key) {
|
| const db = await initIndexedDB();
|
| return new Promise((resolve, reject) => {
|
| const transaction = db.transaction([storeName], "readwrite");
|
| const store = transaction.objectStore(storeName);
|
| const deleteRequest = store.delete(key);
|
|
|
| deleteRequest.onsuccess = () => resolve(true);
|
| deleteRequest.onerror = e => reject(e.target.error);
|
| });
|
| }
|
|
|
| function routeIndexedDBStore(key) {
|
| if (key === "campaignHistoryData") {
|
| return STORES.HISTORY;
|
| } else if (key === "local_previous_posts") {
|
| return STORES.MEDIA;
|
| }
|
| return STORES.SYSTEM;
|
| }
|
|
|
|
|
|
|
|
|
| async function computeSecureSymmetricChecksum(key, data) {
|
| let stringData = "";
|
| if (typeof data === 'number') {
|
|
|
| stringData = key === 'userPoints' ? data.toFixed(2) : String(data);
|
| } else if (typeof data === 'object') {
|
| stringData = JSON.stringify(data);
|
| } else {
|
| stringData = String(data);
|
| }
|
|
|
| const payloadString = `${key}:${stringData}:${INTEGRITY_SEAL_SALT}`;
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.sha256 === 'function') {
|
| return await FritreeCrypto.sha256(payloadString);
|
| }
|
|
|
| let hash = 0;
|
| const inputStr = payloadString;
|
| for (let i = 0; i < inputStr.length; i++) {
|
| const char = inputStr.charCodeAt(i);
|
| hash = ((hash << 5) - hash) + char;
|
| hash |= 0;
|
| }
|
| return "fallback_sha256_" + Math.abs(hash).toString(16);
|
| }
|
|
|
| async function writeSecondarySignatureAnchor(key, signature) {
|
| const obfuscatedSigKey = await asyncHashKey(`sis_anchor_sig_${key}`);
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
| const payload = {};
|
| payload[obfuscatedSigKey] = signature;
|
| return new Promise(res => chrome.storage.local.set(payload, () => res(true)));
|
| } else {
|
| localStorage.setItem(syncHashKey(`sis_anchor_sig_${key}`), signature);
|
| return true;
|
| }
|
| }
|
|
|
| async function readSecondarySignatureAnchor(key) {
|
| const obfuscatedSigKey = await asyncHashKey(`sis_anchor_sig_${key}`);
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
| return new Promise(res => {
|
| chrome.storage.local.get(obfuscatedSigKey, payload => {
|
| res(payload[obfuscatedSigKey] || null);
|
| });
|
| });
|
| } else {
|
| return localStorage.getItem(syncHashKey(`sis_anchor_sig_${key}`)) || null;
|
| }
|
| }
|
|
|
| async function deleteSecondarySignatureAnchor(key) {
|
| const obfuscatedSigKey = await asyncHashKey(`sis_anchor_sig_${key}`);
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
| return new Promise(res => chrome.storage.local.remove(obfuscatedSigKey, () => res(true)));
|
| } else {
|
| localStorage.removeItem(syncHashKey(`sis_anchor_sig_${key}`));
|
| return true;
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| const UnifiedStorageManager = {
|
| set: function(key, value) {
|
| return storageWriteQueue.enqueue(async () => {
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.validateData === 'function') {
|
| if (!FritreeCrypto.validateData(value, key)) {
|
| return false;
|
| }
|
| }
|
|
|
|
|
| if (SECURE_SIS_KEYS.has(key)) {
|
| const dynamicSig = await computeSecureSymmetricChecksum(key, value);
|
| await writeIndexedDBEntry(STORES.SYSTEM, `local_sis_sig_${key}`, dynamicSig);
|
| await writeSecondarySignatureAnchor(key, dynamicSig);
|
|
|
|
|
| await writeIndexedDBEntry(STORES.SYSTEM, `local_sis_backup_${key}`, value);
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.setStorage === 'function') {
|
| await FritreeCrypto.setStorage(`sis_backup_${key}`, value);
|
| }
|
| }
|
|
|
|
|
| if (value instanceof Blob) {
|
| try {
|
| const targetStore = STORES.MEDIA;
|
| await writeIndexedDBEntry(targetStore, key, value);
|
| return true;
|
| } catch (dbErr) {
|
| console.error("[Fritree Storage] Binary media write failure:", dbErr);
|
| return false;
|
| }
|
| }
|
|
|
|
|
| if (LIGHTWEIGHT_CONFIG_KEYS.has(key)) {
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.setStorage === 'function') {
|
| return await FritreeCrypto.setStorage(key, value);
|
| }
|
|
|
| const localBackupKey = syncHashKey(key);
|
| const plainSerialized = JSON.stringify(value);
|
| const localMockCipher = btoa(unescape(encodeURIComponent(plainSerialized)));
|
| localStorage.setItem(localBackupKey, `fte_fallback_enc:${localMockCipher}`);
|
| return true;
|
| }
|
|
|
|
|
| const targetStore = routeIndexedDBStore(key);
|
| try {
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.setStorage === 'function') {
|
| await FritreeCrypto.setStorage(key, value);
|
| } else {
|
| throw new Error("Cryptographic module is offline.");
|
| }
|
|
|
| const obfuscatedKey = await asyncHashKey(key);
|
| const freshCipher = await new Promise(res => {
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
| chrome.storage.local.get(obfuscatedKey, payload => res(payload[obfuscatedKey]));
|
| } else {
|
| res(localStorage.getItem(obfuscatedKey));
|
| }
|
| });
|
|
|
| if (freshCipher) {
|
| await writeIndexedDBEntry(targetStore, key, freshCipher);
|
|
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
| chrome.storage.local.remove(obfuscatedKey);
|
| } else {
|
| localStorage.removeItem(obfuscatedKey);
|
| }
|
| return true;
|
| }
|
| } catch (dbErr) {
|
| console.warn("[Fritree Storage] IndexedDB route missed. Fallback to LocalStorage:", dbErr);
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.setStorage === 'function') {
|
| return await FritreeCrypto.setStorage(key, value);
|
| }
|
| }
|
| return false;
|
| });
|
| },
|
|
|
| get: function(key, defaultValue = null) {
|
| return new Promise(async resolve => {
|
| try {
|
| let decryptedValue = null;
|
|
|
| if (LIGHTWEIGHT_CONFIG_KEYS.has(key)) {
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.getStorage === 'function') {
|
| decryptedValue = await FritreeCrypto.getStorage(key, defaultValue);
|
| } else {
|
| const raw = localStorage.getItem(syncHashKey(key));
|
| if (raw) {
|
| if (raw.startsWith("fte_fallback_enc:")) {
|
| const cipherPart = raw.slice("fte_fallback_enc:".length);
|
| const plain = decodeURIComponent(escape(atob(cipherPart)));
|
| decryptedValue = JSON.parse(plain);
|
| } else {
|
| decryptedValue = JSON.parse(raw);
|
| }
|
| } else {
|
| decryptedValue = defaultValue;
|
| }
|
| }
|
| } else {
|
| const isMediaKey = key.startsWith("media_blob_") || key.startsWith("media_thumb_") || key.startsWith("media_") || key.startsWith("rot_media_") || key.startsWith("wa_composer_");
|
| const targetStore = isMediaKey ? STORES.MEDIA : routeIndexedDBStore(key);
|
| const cipherPacket = await readIndexedDBEntry(targetStore, key);
|
|
|
| if (cipherPacket === null) {
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.getStorage === 'function') {
|
| decryptedValue = await FritreeCrypto.getStorage(key, defaultValue);
|
| } else {
|
| decryptedValue = defaultValue;
|
| }
|
| } else {
|
| if (cipherPacket instanceof Blob) {
|
| resolve(cipherPacket);
|
| return;
|
| }
|
|
|
| const obfuscatedKey = await asyncHashKey(key);
|
| const tempPayload = {};
|
| tempPayload[obfuscatedKey] = cipherPacket;
|
|
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
| await new Promise(r => chrome.storage.local.set(tempPayload, r));
|
| decryptedValue = await FritreeCrypto.getStorage(key, defaultValue);
|
| chrome.storage.local.remove(obfuscatedKey);
|
| } else {
|
| localStorage.setItem(obfuscatedKey, cipherPacket);
|
| decryptedValue = await FritreeCrypto.getStorage(key, defaultValue);
|
| localStorage.removeItem(obfuscatedKey);
|
| }
|
| }
|
| }
|
|
|
|
|
| if (SECURE_SIS_KEYS.has(key) && decryptedValue !== null && decryptedValue !== defaultValue) {
|
| const actualComputedSig = await computeSecureSymmetricChecksum(key, decryptedValue);
|
|
|
| const savedAnchorASig = await readIndexedDBEntry(STORES.SYSTEM, `local_sis_sig_${key}`);
|
| const savedAnchorBSig = await readSecondarySignatureAnchor(key);
|
|
|
| const isAnchorAValid = savedAnchorASig === actualComputedSig;
|
| const isAnchorBValid = savedAnchorBSig === actualComputedSig;
|
|
|
| if (!isAnchorAValid || !isAnchorBValid) {
|
| console.warn(`[Fritree Storage] Security Guard: Tampering detected for key: "${key}"! Attempting secure recovery...`);
|
|
|
| let recoveredValue = null;
|
|
|
|
|
| const backupA = await readIndexedDBEntry(STORES.SYSTEM, `local_sis_backup_${key}`);
|
| if (backupA !== null) {
|
| const sigA = await computeSecureSymmetricChecksum(key, backupA);
|
| if (sigA === savedAnchorASig) {
|
| recoveredValue = backupA;
|
| console.log(`[Fritree Storage] Restored tampered "${key}" from secure IndexedDB backup.`);
|
| }
|
| }
|
|
|
|
|
| if (recoveredValue === null) {
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.getStorage === 'function') {
|
| const backupB = await FritreeCrypto.getStorage(`sis_backup_${key}`, null);
|
| if (backupB !== null) {
|
| const sigB = await computeSecureSymmetricChecksum(key, backupB);
|
| if (sigB === savedAnchorBSig) {
|
| recoveredValue = backupB;
|
| console.log(`[Fritree Storage] Restored tampered "${key}" from secure LocalStorage backup.`);
|
| }
|
| }
|
| }
|
| }
|
|
|
| if (recoveredValue !== null) {
|
| decryptedValue = recoveredValue;
|
|
|
| const restoredSig = await computeSecureSymmetricChecksum(key, recoveredValue);
|
| await writeIndexedDBEntry(STORES.SYSTEM, `local_sis_sig_${key}`, restoredSig);
|
| await writeSecondarySignatureAnchor(key, restoredSig);
|
| } else {
|
|
|
| console.error(`[Fritree Storage] Critical Violation: Recovery failed for "${key}". Reverting to safe default.`);
|
| decryptedValue = defaultValue;
|
|
|
| const defaultSig = await computeSecureSymmetricChecksum(key, defaultValue);
|
| await writeIndexedDBEntry(STORES.SYSTEM, `local_sis_sig_${key}`, defaultSig);
|
| await writeSecondarySignatureAnchor(key, defaultSig);
|
| await writeIndexedDBEntry(STORES.SYSTEM, `local_sis_backup_${key}`, defaultValue);
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.setStorage === 'function') {
|
| await FritreeCrypto.setStorage(`sis_backup_${key}`, defaultValue);
|
| }
|
| }
|
| }
|
| }
|
|
|
| resolve(decryptedValue !== null ? decryptedValue : defaultValue);
|
|
|
| } catch (e) {
|
| console.error(`[Fritree Storage] Key retrieval failure for "${key}":`, e);
|
| resolve(defaultValue);
|
| }
|
| });
|
| },
|
|
|
| remove: function(key) {
|
| return storageWriteQueue.enqueue(async () => {
|
| const obfuscatedKey = await asyncHashKey(key);
|
|
|
| localStorage.removeItem(syncHashKey(key));
|
| localStorage.removeItem(obfuscatedKey);
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
| chrome.storage.local.remove(obfuscatedKey);
|
| }
|
|
|
| const isMediaKey = key.startsWith("media_blob_") || key.startsWith("media_thumb_") || key.startsWith("media_") || key.startsWith("rot_media_") || key.startsWith("wa_composer_");
|
| const targetStore = isMediaKey ? STORES.MEDIA : routeIndexedDBStore(key);
|
| try {
|
| await deleteIndexedDBEntry(targetStore, key);
|
| } catch (e) {
|
| console.error("[Fritree Storage] Store deletion error:", e);
|
| }
|
|
|
| if (SECURE_SIS_KEYS.has(key)) {
|
| try {
|
| await deleteIndexedDBEntry(STORES.SYSTEM, `local_sis_sig_${key}`);
|
| await deleteSecondarySignatureAnchor(key);
|
| await deleteIndexedDBEntry(STORES.SYSTEM, `local_sis_backup_${key}`);
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
| const obfuscatedBackupKey = await asyncHashKey(`sis_backup_${key}`);
|
| chrome.storage.local.remove(obfuscatedBackupKey);
|
| }
|
| } catch (e) {
|
| console.error("[Fritree Storage] Anchor removal error:", e);
|
| }
|
| }
|
|
|
| if (!isMediaKey) {
|
| try {
|
| await deleteIndexedDBEntry(STORES.MEDIA, key);
|
| } catch (e) {
|
| console.error("[Fritree Storage] Fallback store removal error:", e);
|
| }
|
| }
|
| return true;
|
| });
|
| },
|
|
|
| clear: function() {
|
| return storageWriteQueue.enqueue(async () => {
|
| localStorage.clear();
|
|
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
| await new Promise(r => chrome.storage.local.clear(r));
|
| }
|
|
|
| if (dbInstance) {
|
| dbInstance.close();
|
| dbInstance = null;
|
| }
|
|
|
| return new Promise(resolve => {
|
| const deleteReq = indexedDB.deleteDatabase(DB_NAME);
|
| deleteReq.onsuccess = () => resolve(true);
|
| deleteReq.onerror = () => resolve(false);
|
| });
|
| });
|
| },
|
|
|
| b64ToBlob: base64ToBlob,
|
| blobToB64: blobToBase64
|
| };
|
|
|
| async function asyncHashKey(key) {
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.sha256 === 'function') {
|
| return "" + await FritreeCrypto.sha256(key + "FritreeKeySalt_2026_StrictSHA256_Hashed_Production_WebCrypto_Salt");
|
| }
|
| const input = key + "FritreeKeySalt_2026_StrictSHA256_Hashed_Production_WebCrypto_Salt";
|
| let hash = 0;
|
| for (let i = 0; i < input.length; i++) {
|
| const char = input.charCodeAt(i);
|
| hash = ((hash << 5) - hash) + char;
|
| hash |= 0;
|
| }
|
| return "fte_sh_sha256_" + Math.abs(hash).toString(16);
|
| }
|
|
|
| function syncHashKey(key) {
|
| const input = key + "FritreeKeySalt_2026_StrictSHA256_Hashed_Production_WebCrypto_Salt";
|
| let hash = 0;
|
| for (let i = 0; i < input.length; i++) {
|
| const char = input.charCodeAt(i);
|
| hash = ((hash << 5) - hash) + char;
|
| hash |= 0;
|
| }
|
| return "fte_sh_sha256_" + Math.abs(hash).toString(16);
|
| }
|
|
|
| global.FritreeStorage = UnifiedStorageManager;
|
|
|
| initIndexedDB().then(() => {
|
| console.log("[Fritree Storage] Unified IndexedDB system online.");
|
| }).catch(err => {
|
| console.error("[Fritree Storage] Database upgrade/initialization failed:", err);
|
| });
|
|
|
| })(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this); |