| |
| |
| |
| |
|
|
|
|
| (global => {
|
| 'use strict';
|
|
|
| const DB_NAME = global.FritreeAppConstants?.STORAGE_CONSTANTS?.DB_NAME || "9a8b7c6d5e4f3a2b1c0";
|
| const DB_VERSION = global.FritreeAppConstants?.STORAGE_CONSTANTS?.DB_VERSION || 6;
|
|
|
| const STORES = global.FritreeAppConstants?.STORAGE_CONSTANTS?.STORES || Object.freeze({
|
| MEDIA: "t7a8b9c0d",
|
| HISTORY: "q1e2f3a4b",
|
| SYSTEM: "a5c6d7e8f"
|
| });
|
|
|
| const SECURE_SIS_KEYS = new Set(
|
| global.FritreeAppConstants?.STORAGE_CONSTANTS?.SECURE_SIS_KEYS || [
|
| "acc_userLevel",
|
| "acc_userXP",
|
| "acc_lifetimeFb",
|
| "acc_lifetimeWa",
|
| "acc_lifetimeTasks",
|
| "local_previous_posts",
|
| "wa_connected",
|
| "wa_sent_today",
|
| "acc_fbPoints",
|
| "acc_waPoints",
|
| "usdPoints",
|
| "local_contacts_database",
|
| "local_calendar_events_db",
|
| "local_post_groups",
|
| "scheduledCampaignsData"
|
| ]
|
| );
|
|
|
| const INTEGRITY_SEAL_SALT = global.FritreeAppConstants?.SECURITY_SALTS?.STORAGE_SEAL_SALT ||
|
| "FritreeStorageIntegritySealSalt_V5_AEAD_AES256";
|
|
|
| const HASH_KEY_SALT = global.FritreeAppConstants?.SECURITY_SALTS?.HASH_KEY_SALT ||
|
| "FritreeStorageKeyObfuscationSalt_2026_StrictSHA256_AEADMasterLock";
|
|
|
| let dbInstance = null;
|
| let isDbInitializing = false;
|
| const dbInitPromiseResolvers = [];
|
|
|
| |
| |
|
|
| function silentTrap(err) {
|
| if (err && console && console.warn) {
|
| console.warn("[Fritree Storage Core SafeTrap]", err);
|
| }
|
| return null;
|
| }
|
|
|
| |
| |
| |
|
|
| 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) {
|
| silentTrap(err);
|
| resolve(false);
|
| } 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) => {
|
| try {
|
| const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
|
| request.onupgradeneeded = event => {
|
| try {
|
| const db = event.target.result;
|
| Object.values(STORES).forEach(storeName => {
|
| if (!db.objectStoreNames.contains(storeName)) {
|
| db.createObjectStore(storeName, { keyPath: "id" });
|
| }
|
| });
|
| } catch (e) {
|
| silentTrap(e);
|
| }
|
| };
|
|
|
| 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;
|
| resolve(null);
|
| dbInitPromiseResolvers.forEach(r => r.resolve(null));
|
| dbInitPromiseResolvers.length = 0;
|
| };
|
| } catch (err) {
|
| isDbInitializing = false;
|
| resolve(null);
|
| }
|
| });
|
| }
|
|
|
| |
| |
|
|
| function base64ToBlob(base64Data, mimeType = 'image/jpeg') {
|
| try {
|
| if (!base64Data || typeof base64Data !== 'string') {
|
| return new Blob([], { type: mimeType });
|
| }
|
| const parts = base64Data.split(",");
|
| const rawB64 = parts.length > 1 ? parts[1] : parts[0];
|
| const detectedMime = parts.length > 1 ? parts[0].split(":")[1].split(";")[0] : mimeType;
|
| const byteCharacters = atob(rawB64);
|
| 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: detectedMime });
|
| } catch (e) {
|
| silentTrap(e);
|
| return new Blob([], { type: mimeType });
|
| }
|
| }
|
|
|
| |
| |
|
|
| async function blobToBase64(blob) {
|
| try {
|
| if (!(blob instanceof Blob)) return null;
|
| if (typeof FileReader !== 'undefined') {
|
| return new Promise((resolve) => {
|
| const reader = new FileReader();
|
| reader.onloadend = () => resolve(reader.result);
|
| reader.onerror = () => resolve(null);
|
| reader.readAsDataURL(blob);
|
| });
|
| }
|
|
|
| const buffer = await blob.arrayBuffer();
|
| const bytes = new Uint8Array(buffer);
|
| let binary = '';
|
| const len = bytes.byteLength;
|
| const chunk = 8192;
|
|
|
| for (let i = 0; i < len; i += chunk) {
|
| const slice = bytes.subarray(i, i + chunk);
|
| binary += String.fromCharCode.apply(null, slice);
|
| }
|
| return `data:${blob.type || 'image/jpeg'};base64,${btoa(binary)}`;
|
| } catch (err) {
|
| silentTrap(err);
|
| return null;
|
| }
|
| }
|
|
|
| |
| |
|
|
| async function asyncHashKey(key) {
|
| try {
|
| if (global.FritreeCrypto && typeof global.FritreeCrypto.sha256 === 'function') {
|
| const hashHex = await global.FritreeCrypto.sha256(String(key) + HASH_KEY_SALT);
|
| return "fte_sh_v5_" + hashHex.substring(0, 32);
|
| }
|
| let hash = 0;
|
| const input = String(key) + HASH_KEY_SALT;
|
| for (let i = 0; i < input.length; i++) {
|
| hash = ((hash << 5) - hash) + input.charCodeAt(i);
|
| hash |= 0;
|
| }
|
| return "fte_sh_v5_" + Math.abs(hash).toString(16);
|
| } catch (e) {
|
| silentTrap(e);
|
| return "fallback_hash_" + String(key).length;
|
| }
|
| }
|
|
|
| function syncHashKey(key) {
|
| try {
|
| let hash = 0;
|
| const input = String(key) + HASH_KEY_SALT;
|
| for (let i = 0; i < input.length; i++) {
|
| hash = ((hash << 5) - hash) + input.charCodeAt(i);
|
| hash |= 0;
|
| }
|
| return "fte_sh_v5_" + Math.abs(hash).toString(16);
|
| } catch (e) {
|
| silentTrap(e);
|
| return "fallback_sync_" + String(key).length;
|
| }
|
| }
|
|
|
| |
| |
|
|
| async function writeIndexedDBEntry(storeName, key, encryptedValue) {
|
| try {
|
| const db = await initIndexedDB();
|
| if (!db) return false;
|
| const obfuscatedKey = await asyncHashKey(key);
|
|
|
| return new Promise((resolve) => {
|
| const transaction = db.transaction([storeName], "readwrite");
|
| const store = transaction.objectStore(storeName);
|
| const putRequest = store.put({ id: obfuscatedKey, rawKeyName: key, payload: encryptedValue, updated_at: Date.now() });
|
|
|
| putRequest.onsuccess = () => resolve(true);
|
| putRequest.onerror = () => resolve(false);
|
| });
|
| } catch (e) {
|
| silentTrap(e);
|
| return false;
|
| }
|
| }
|
|
|
| |
| |
|
|
| async function readIndexedDBEntry(storeName, key) {
|
| try {
|
| const db = await initIndexedDB();
|
| if (!db) return null;
|
| const obfuscatedKey = await asyncHashKey(key);
|
|
|
| return new Promise((resolve) => {
|
| const transaction = db.transaction([storeName], "readonly");
|
| const store = transaction.objectStore(storeName);
|
| const getRequest = store.get(obfuscatedKey);
|
|
|
| getRequest.onsuccess = e => {
|
| const record = e.target.result;
|
| resolve(record ? record.payload : null);
|
| };
|
| getRequest.onerror = () => resolve(null);
|
| });
|
| } catch (e) {
|
| silentTrap(e);
|
| return null;
|
| }
|
| }
|
|
|
| |
| |
|
|
| async function deleteIndexedDBEntry(storeName, key) {
|
| try {
|
| const db = await initIndexedDB();
|
| if (!db) return false;
|
| const obfuscatedKey = await asyncHashKey(key);
|
|
|
| return new Promise((resolve) => {
|
| const transaction = db.transaction([storeName], "readwrite");
|
| const store = transaction.objectStore(storeName);
|
| const deleteRequest = store.delete(obfuscatedKey);
|
|
|
| deleteRequest.onsuccess = () => resolve(true);
|
| deleteRequest.onerror = () => resolve(false);
|
| });
|
| } catch (e) {
|
| silentTrap(e);
|
| return false;
|
| }
|
| }
|
|
|
| |
| |
|
|
| function routeIndexedDBStore(key) {
|
| try {
|
| if (key === "campaignHistoryData") {
|
| return STORES.HISTORY;
|
| } else if (key === "local_previous_posts" || key.startsWith("media_") || key.startsWith("rot_media_") || key.startsWith("wa_composer_")) {
|
| return STORES.MEDIA;
|
| }
|
| return STORES.SYSTEM;
|
| } catch (e) {
|
| return STORES.SYSTEM;
|
| }
|
| }
|
|
|
| |
| |
|
|
| async function computeSecureSymmetricChecksum(key, data) {
|
| try {
|
| let stringData = "";
|
| if (key === 'usdPoints') {
|
| stringData = parseFloat(data || 0).toFixed(2);
|
| } else if (typeof data === 'object' && data !== null) {
|
| stringData = JSON.stringify(data);
|
| } else {
|
| stringData = String(data !== undefined && data !== null ? data : "");
|
| }
|
|
|
| const payloadString = `${key}:${stringData}:${INTEGRITY_SEAL_SALT}`;
|
| if (global.FritreeCrypto && typeof global.FritreeCrypto.sha256 === 'function') {
|
| return await global.FritreeCrypto.sha256(payloadString);
|
| }
|
|
|
| let hash = 0;
|
| for (let i = 0; i < payloadString.length; i++) {
|
| hash = ((hash << 5) - hash) + payloadString.charCodeAt(i);
|
| hash |= 0;
|
| }
|
| return "fallback_sha256_" + Math.abs(hash).toString(16);
|
| } catch (e) {
|
| silentTrap(e);
|
| return "hash_error";
|
| }
|
| }
|
|
|
| async function writeSecondarySignatureAnchor(key, signature) {
|
| try {
|
| 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;
|
| }
|
| } catch (e) {
|
| silentTrap(e);
|
| return false;
|
| }
|
| }
|
|
|
| async function readSecondarySignatureAnchor(key) {
|
| try {
|
| 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 ? payload[obfuscatedSigKey] : null);
|
| });
|
| });
|
| } else {
|
| return localStorage.getItem(syncHashKey(`sis_anchor_sig_${key}`)) || null;
|
| }
|
| } catch (e) {
|
| silentTrap(e);
|
| return null;
|
| }
|
| }
|
|
|
| async function deleteSecondarySignatureAnchor(key) {
|
| try {
|
| 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;
|
| }
|
| } catch (e) {
|
| silentTrap(e);
|
| return false;
|
| }
|
| }
|
|
|
| |
| |
|
|
| const UnifiedStorageManager = {
|
| |
| |
| |
| |
| |
|
|
| set: function(key, value) {
|
| return storageWriteQueue.enqueue(async () => {
|
| try {
|
| if (global.FritreeCrypto && typeof global.FritreeCrypto.validateData === 'function') {
|
| if (!global.FritreeCrypto.validateData(value, key)) {
|
| console.error("[Fritree Storage] Data validation failed for key:", 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 (value instanceof Blob) {
|
| try {
|
| const targetStore = STORES.MEDIA;
|
| await writeIndexedDBEntry(targetStore, key, value);
|
| return true;
|
| } catch (dbErr) {
|
| silentTrap(dbErr);
|
| return false;
|
| }
|
| }
|
|
|
| const obfuscatedKey = await asyncHashKey(key);
|
| const encryptedValue = await global.FritreeCrypto.encryptAndSignPayload(value, key);
|
|
|
| if (!encryptedValue) {
|
| console.error("[Fritree Storage] Encryption failed for key:", key);
|
| return false;
|
| }
|
|
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
| const payload = {};
|
| payload[obfuscatedKey] = encryptedValue;
|
| await new Promise(resolve => chrome.storage.local.set(payload, resolve));
|
| } else {
|
| localStorage.setItem(obfuscatedKey, encryptedValue);
|
| }
|
|
|
| const targetStore = routeIndexedDBStore(key);
|
| await writeIndexedDBEntry(targetStore, key, encryptedValue);
|
| return true;
|
| } catch (ex) {
|
| silentTrap(ex);
|
| return false;
|
| }
|
| });
|
| },
|
|
|
| |
| |
| |
| |
| |
|
|
| get: function(key, defaultValue = null) {
|
| return new Promise(async resolve => {
|
| try {
|
| let decryptedValue = null;
|
| let cipherPacketFound = false;
|
| const obfuscatedKey = await asyncHashKey(key);
|
| let cipherPacket = null;
|
|
|
| if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
|
| cipherPacket = await new Promise(r => {
|
| chrome.storage.local.get(obfuscatedKey, result => {
|
| r(result ? result[obfuscatedKey] : null);
|
| });
|
| });
|
| } else {
|
| cipherPacket = localStorage.getItem(obfuscatedKey);
|
| }
|
|
|
| if (cipherPacket !== null && cipherPacket !== undefined) {
|
| cipherPacketFound = true;
|
| } else {
|
| const targetStore = routeIndexedDBStore(key);
|
| cipherPacket = await readIndexedDBEntry(targetStore, key);
|
| if (cipherPacket !== null && cipherPacket !== undefined) {
|
| cipherPacketFound = true;
|
| }
|
| }
|
|
|
| if (cipherPacket instanceof Blob) {
|
| resolve(cipherPacket);
|
| return;
|
| }
|
|
|
| if (cipherPacketFound && typeof cipherPacket === 'string') {
|
| decryptedValue = await global.FritreeCrypto.verifyAndDecryptPayload(cipherPacket, key);
|
| }
|
|
|
|
|
| if (SECURE_SIS_KEYS.has(key)) {
|
| if (decryptedValue === null) {
|
| if (cipherPacketFound) {
|
| console.warn("[Fritree Storage] Primary storage corrupted for key:", key, "- Restoring uncorrupted backup...");
|
| }
|
| const restoredBackup = await readIndexedDBEntry(STORES.SYSTEM, `local_sis_backup_${key}`);
|
| if (restoredBackup !== null) {
|
| decryptedValue = restoredBackup;
|
| const restoredSig = await computeSecureSymmetricChecksum(key, restoredBackup);
|
| await writeIndexedDBEntry(STORES.SYSTEM, `local_sis_sig_${key}`, restoredSig);
|
| await writeSecondarySignatureAnchor(key, restoredSig);
|
| await this.set(key, restoredBackup);
|
| }
|
| } else {
|
| const actualComputedSig = await computeSecureSymmetricChecksum(key, decryptedValue);
|
| const savedAnchorASig = await readIndexedDBEntry(STORES.SYSTEM, `local_sis_sig_${key}`);
|
| const savedAnchorBSig = await readSecondarySignatureAnchor(key);
|
|
|
| if (!savedAnchorASig && !savedAnchorBSig) {
|
| await writeIndexedDBEntry(STORES.SYSTEM, `local_sis_sig_${key}`, actualComputedSig);
|
| await writeSecondarySignatureAnchor(key, actualComputedSig);
|
| await writeIndexedDBEntry(STORES.SYSTEM, `local_sis_backup_${key}`, decryptedValue);
|
| } else if (savedAnchorASig && savedAnchorBSig) {
|
| const isAValid = savedAnchorASig === actualComputedSig;
|
| const isBValid = savedAnchorBSig === actualComputedSig;
|
|
|
| if (!isAValid || !isBValid) {
|
| console.error("[Fritree Storage] TAMPER DETECTED via SIS Seals for key:", key, "- Restoring uncorrupted backup.");
|
| const restoredValue = await readIndexedDBEntry(STORES.SYSTEM, `local_sis_backup_${key}`);
|
| if (restoredValue !== null) {
|
| decryptedValue = restoredValue;
|
| const restoredSig = await computeSecureSymmetricChecksum(key, restoredValue);
|
| await writeIndexedDBEntry(STORES.SYSTEM, `local_sis_sig_${key}`, restoredSig);
|
| await writeSecondarySignatureAnchor(key, restoredSig);
|
| await this.set(key, restoredValue);
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
| resolve(decryptedValue !== null ? decryptedValue : defaultValue);
|
| } catch (e) {
|
| silentTrap(e);
|
| resolve(defaultValue);
|
| }
|
| });
|
| },
|
|
|
| |
| |
|
|
| remove: function(key) {
|
| return storageWriteQueue.enqueue(async () => {
|
| try {
|
| 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 targetStore = routeIndexedDBStore(key);
|
| await deleteIndexedDBEntry(targetStore, key);
|
|
|
| if (SECURE_SIS_KEYS.has(key)) {
|
| await deleteIndexedDBEntry(STORES.SYSTEM, `local_sis_sig_${key}`);
|
| await deleteSecondarySignatureAnchor(key);
|
| await deleteIndexedDBEntry(STORES.SYSTEM, `local_sis_backup_${key}`);
|
| }
|
|
|
| return true;
|
| } catch (e) {
|
| silentTrap(e);
|
| return false;
|
| }
|
| });
|
| },
|
|
|
| |
| |
|
|
| clear: function() {
|
| return storageWriteQueue.enqueue(async () => {
|
| try {
|
| 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);
|
| });
|
| } catch (e) {
|
| silentTrap(e);
|
| return false;
|
| }
|
| });
|
| },
|
|
|
| b64ToBlob: base64ToBlob,
|
| blobToB64: blobToBase64
|
| };
|
|
|
| Object.freeze(UnifiedStorageManager);
|
|
|
| Object.defineProperty(global, 'FritreeStorage', {
|
| value: UnifiedStorageManager,
|
| writable: false,
|
| configurable: false
|
| });
|
|
|
| initIndexedDB().catch(err => silentTrap(err));
|
|
|
| })(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this); |