UNPKG

qevo

Version:

Cross-browser extension toolkit - Unified API for Chrome & Firefox extension development with messaging, storage, webRequest, and tab management

2 lines (1 loc) 40.7 kB
class N{debug=!1;listeners={add:new Set,update:new Set,remove:new Set};cleanupIntervalId;CLEANUP_INTERVAL_MS=30000;constructor(){this.initializeCleanup()}addListener(H,J){this.listeners[H].add(J)}removeListener(H,J){this.listeners[H].delete(J)}triggerListeners(H,J,X,Y){try{switch(H){case"add":this.listeners.add.forEach((_)=>_(J,Y));break;case"remove":this.listeners.remove.forEach((_)=>_(J));break;case"update":this.listeners.update.forEach((_)=>_(J,X,Y));break}}catch(_){if(this.debug)throw console.error(`Error in storage change handler for key ${J}:`,_),_}}initializeCleanup(){if(this.shouldRunCleanup())this.startCleanup()}shouldRunCleanup(){return!0}isServiceWorker(){return typeof self<"u"&&"ServiceWorkerGlobalScope"in self&&self instanceof self.ServiceWorkerGlobalScope}startCleanup(){if(this.cleanupIntervalId)clearInterval(this.cleanupIntervalId);this.cleanupIntervalId=setInterval(()=>{this.cleanupExpired().catch((H)=>{if(this.debug)throw console.error("Cleanup interval error:",H),H})},this.CLEANUP_INTERVAL_MS)}stopCleanup(){if(this.cleanupIntervalId)clearInterval(this.cleanupIntervalId),this.cleanupIntervalId=void 0}destroy(){this.stopCleanup(),this.listeners.add.clear(),this.listeners.update.clear(),this.listeners.remove.clear()}}class x extends N{STORAGE_QUOTA_BYTES=8388608;boundStorageHandler=null;constructor(){super();if(this.isContextValid()&&chrome?.storage?.onChanged)this.boundStorageHandler=this.handleStorageChange.bind(this),chrome.storage.onChanged.addListener(this.boundStorageHandler)}isContextValid(){try{return!!chrome.runtime?.id}catch{return!1}}shouldRunCleanup(){return this.isBackgroundScript()&&!this.isServiceWorker()}async put(H,J,X){if(!this.isContextValid()){if(this.debug)console.warn(`Cannot set key ${H}: Extension context invalidated`);return}try{let{ttl:Y,expires:_}=X||{},G=Y?Date.now()+Y*1000:_?_.getTime():void 0,W={value:J,expires:G};await chrome.storage.local.set({[H]:W})}catch(Y){if(Y.message?.includes("context invalidated")){if(this.debug)console.warn(`Cannot set key ${H}: Extension context invalidated`)}else{if(this.debug)console.error(`Failed to set key ${H}:`,Y);throw Error(`Storage set operation failed for key ${H}`)}}}async get(H,J){if(!this.isContextValid()){if(this.debug)console.warn(`Cannot get key ${H}: Extension context invalidated`);return null}try{if(J){let _=await this.listKeys(H);if(_.length===0)return null;H=_[0]}let Y=(await chrome.storage.local.get(H))[H];if(!Y)return null;if(Y.expires&&Date.now()>Y.expires)return await this.remove(H),null;return Y.value}catch(X){if(X.message?.includes("context invalidated")){if(this.debug)console.warn(`Cannot get key ${H}: Extension context invalidated`);return null}if(this.debug)console.error(`Failed to get key ${H}:`,X);return null}}async getWithMetadata(H){if(!this.isContextValid()){if(this.debug)console.warn(`Cannot get metadata for key ${H}: Extension context invalidated`);return null}try{let X=(await chrome.storage.local.get(H))[H];if(!X)return null;if(X.expires&&Date.now()>X.expires)return await this.remove(H),null;return X}catch(J){if(J.message?.includes("context invalidated")){if(this.debug)console.warn(`Cannot get metadata for key ${H}: Extension context invalidated`);return null}if(this.debug)console.error(`Failed to get metadata for key ${H}:`,J);return null}}async has(H){if(!this.isContextValid()){if(this.debug)console.warn(`Cannot check key ${H}: Extension context invalidated`);return!1}try{let X=(await chrome.storage.local.get(H))[H];if(!X)return!1;if(X.expires&&Date.now()>X.expires)return await this.remove(H),!1;return!0}catch(J){if(J.message?.includes("context invalidated")){if(this.debug)console.warn(`Cannot check key ${H}: Extension context invalidated`);return!1}if(this.debug)console.error(`Failed to check key ${H}:`,J);return!1}}async remove(H){if(!this.isContextValid()){if(this.debug)console.warn(`Cannot remove key ${H}: Extension context invalidated`);return}try{await chrome.storage.local.remove(H)}catch(J){if(J.message?.includes("context invalidated")){if(this.debug)console.warn(`Cannot remove key ${H}: Extension context invalidated`)}else if(this.debug)console.error(`Failed to remove key ${H}:`,J)}}async length(){if(!this.isContextValid()){if(this.debug)console.warn("Cannot get storage length: Extension context invalidated");return 0}try{let H=await chrome.storage.local.get(null);return Object.keys(H).length}catch(H){if(H.message?.includes("context invalidated")){if(this.debug)console.warn("Cannot get storage length: Extension context invalidated");return 0}if(this.debug)console.error("Failed to get storage length:",H);return 0}}async listKeys(H=""){if(!this.isContextValid()){if(this.debug)console.warn("Cannot list keys: Extension context invalidated");return[]}try{let J=await chrome.storage.local.get(null);return Object.keys(J).filter((X)=>H.length>0?X.startsWith(H):!0)}catch(J){if(J.message?.includes("context invalidated")){if(this.debug)console.warn("Cannot list keys: Extension context invalidated");return[]}if(this.debug)console.error("Failed to list keys:",J);return[]}}async getKeyByPrefix(H){if(!this.isContextValid()){if(this.debug)console.warn("Cannot list keys: Extension context invalidated");return}try{let J=await chrome.storage.local.get(null);return Object.keys(J).find((X)=>X.startsWith(H))}catch(J){if(J.message?.includes("context invalidated")){if(this.debug)console.warn("Cannot list keys: Extension context invalidated");return}if(this.debug)console.error("Failed to list keys:",J);return}}async getKeyBySuffix(H){if(!this.isContextValid()){if(this.debug)console.warn("Cannot list keys: Extension context invalidated");return}try{let J=await chrome.storage.local.get(null);return Object.keys(J).find((X)=>X.endsWith(H))}catch(J){if(J.message?.includes("context invalidated")){if(this.debug)console.warn("Cannot list keys: Extension context invalidated");return}if(this.debug)console.error("Failed to list keys:",J);return}}async batch(H){if(!this.isContextValid()){if(this.debug)console.warn("Cannot perform batch operations: Extension context invalidated");return H.map(()=>{return})}try{let J=[],X={},Y=[];for(let _ of H)switch(_.type){case"set":if(_.value!==void 0){let W=_.ttl?Date.now()+_.ttl*1000:_.expires?_.expires.getTime():void 0;X[_.key]={value:_.value,expires:W}}J.push(void 0);break;case"remove":Y.push(_.key),J.push(void 0);break;case"get":let G=await this.get(_.key);J.push(G);break}if(Object.keys(X).length>0){if(!this.isContextValid()){if(this.debug)console.warn("Cannot perform batch set: Extension context invalidated");return H.map(()=>{return})}await chrome.storage.local.set(X)}if(Y.length>0){if(!this.isContextValid()){if(this.debug)console.warn("Cannot perform batch remove: Extension context invalidated");return H.map(()=>{return})}try{await chrome.storage.local.remove(Y)}catch(_){if(_.message?.includes("context invalidated")){if(this.debug)console.warn("Cannot perform batch remove: Extension context invalidated");return H.map(()=>{return})}throw _}}return J}catch(J){if(J.message?.includes("context invalidated")){if(this.debug)console.warn("Cannot perform batch operations: Extension context invalidated");return H.map(()=>{return})}if(this.debug)console.error("Batch operation failed:",J);if(this.debug)throw Error("Batch operation failed");else return H.map(()=>{return})}}async getStorageUsage(){if(!this.isContextValid()){if(this.debug)console.warn("Cannot get storage usage: Extension context invalidated");return 0}try{return await chrome.storage.local.getBytesInUse()}catch(H){if(H.message?.includes("context invalidated")){if(this.debug)console.warn("Cannot get storage usage: Extension context invalidated");return 0}if(this.debug)console.error("Failed to get storage usage:",H);return 0}}async cleanupExpired(){if(!this.isContextValid()){if(this.debug)console.warn("Cannot clean up expired keys: Extension context invalidated");this.stopCleanup();return}try{let H=await chrome.storage.local.get(null),J=Date.now(),X=[];for(let Y in H){let _=H[Y];if(_?.expires&&J>_.expires)X.push(Y)}if(X.length>0){if(!this.isContextValid()){if(this.debug)console.warn("Cannot clean up expired keys: Extension context invalidated");return}await chrome.storage.local.remove(X)}}catch(H){if(H.message?.includes("context invalidated")){if(this.debug)console.warn("Cannot clean up expired keys: Extension context invalidated");this.stopCleanup()}else if(this.debug)throw console.error("Failed to clean up expired keys:",H),H}}handleStorageChange(H,J){if(J!=="local"||!this.isContextValid())return;for(let[X,{oldValue:Y,newValue:_}]of Object.entries(H))if(Y===void 0&&_!==void 0)this.triggerListeners("add",X,void 0,_?.value||_);else if(_===void 0&&Y!==void 0)this.triggerListeners("remove",X);else if(Y!==void 0&&_!==void 0)this.triggerListeners("update",X,Y?.value||Y,_?.value||_)}isBackgroundScript(){try{return!!chrome.runtime?.getManifest()?.background}catch{return!1}}destroy(){if(this.boundStorageHandler&&chrome?.storage?.onChanged){try{chrome.storage.onChanged.removeListener(this.boundStorageHandler)}catch(H){if(this.debug)console.error("Failed to remove storage listener:",H)}this.boundStorageHandler=null}super.destroy()}}class A extends N{boundStorageHandler=null;constructor(){super();if(this.isContextValid()&&browser?.storage?.onChanged)this.boundStorageHandler=this.handleStorageChange.bind(this),browser.storage.onChanged.addListener(this.boundStorageHandler)}isContextValid(){try{return typeof browser<"u"&&!!browser.runtime?.id}catch{return!1}}shouldRunCleanup(){return this.isBackgroundScript()&&!this.isServiceWorker()}async put(H,J,X){if(!this.isContextValid()){if(this.debug)console.warn(`Cannot set key ${H}: Extension context invalidated`);return}try{let{ttl:Y,expires:_}=X||{},G=Y?Date.now()+Y*1000:_?_.getTime():void 0,W={value:J,expires:G};await browser.storage.local.set({[H]:W})}catch(Y){if(this.debug)console.error(`Failed to set key ${H}:`,Y);if(this.debug)throw Error(`Storage set operation failed for key ${H}`)}}async get(H,J){if(!this.isContextValid()){if(this.debug)console.warn(`Cannot get key ${H}: Extension context invalidated`);return null}try{if(J){let _=await this.listKeys(H);if(_.length===0)return null;H=_[0]}let Y=(await browser.storage.local.get(H))[H];if(!Y)return null;if(Y.expires&&Date.now()>Y.expires)return await this.remove(H),null;return Y.value}catch(X){if(this.debug)console.error(`Failed to get key ${H}:`,X);return null}}async getWithMetadata(H){if(!this.isContextValid()){if(this.debug)console.warn(`Cannot get metadata for key ${H}: Extension context invalidated`);return null}try{let X=(await browser.storage.local.get(H))[H];if(!X)return null;if(X.expires&&Date.now()>X.expires)return await this.remove(H),null;return X}catch(J){if(this.debug)console.error(`Failed to get metadata for key ${H}:`,J);return null}}async has(H){if(!this.isContextValid()){if(this.debug)console.warn(`Cannot check key ${H}: Extension context invalidated`);return!1}try{let X=(await browser.storage.local.get(H))[H];if(!X)return!1;if(X.expires&&Date.now()>X.expires)return await this.remove(H),!1;return!0}catch(J){if(this.debug)console.error(`Failed to check key ${H}:`,J);return!1}}async remove(H){if(!this.isContextValid()){if(this.debug)console.warn(`Cannot remove key ${H}: Extension context invalidated`);return}try{await browser.storage.local.remove(H)}catch(J){if(this.debug)console.error(`Failed to remove key ${H}:`,J)}}async length(){if(!this.isContextValid()){if(this.debug)console.warn("Cannot get storage length: Extension context invalidated");return 0}try{let H=await browser.storage.local.get(null);return Object.keys(H).length}catch(H){if(this.debug)console.error("Failed to get storage length:",H);return 0}}async listKeys(H=""){if(!this.isContextValid()){if(this.debug)console.warn("Cannot list keys: Extension context invalidated");return[]}try{let J=await browser.storage.local.get(null);return Object.keys(J).filter((X)=>H.length>0?X.startsWith(H):!0)}catch(J){if(this.debug)console.error("Failed to list keys:",J);return[]}}async getKeyByPrefix(H){if(!this.isContextValid()){if(this.debug)console.warn("Cannot list keys: Extension context invalidated");return}try{let J=await browser.storage.local.get(null);return Object.keys(J).find((X)=>X.startsWith(H))}catch(J){if(this.debug)console.error("Failed to list keys:",J);return}}async getKeyBySuffix(H){if(!this.isContextValid()){if(this.debug)console.warn("Cannot list keys: Extension context invalidated");return}try{let J=await browser.storage.local.get(null);return Object.keys(J).find((X)=>X.endsWith(H))}catch(J){if(this.debug)console.error("Failed to list keys:",J);return}}async batch(H){if(!this.isContextValid()){if(this.debug)console.warn("Cannot perform batch operations: Extension context invalidated");return H.map(()=>{return})}let J=[];try{let X={},Y=[];for(let _ of H)switch(_.type){case"set":if(_.value!==void 0){let W=_.ttl?Date.now()+_.ttl*1000:_.expires?_.expires.getTime():void 0;X[_.key]={value:_.value,expires:W}}J.push(void 0);break;case"remove":Y.push(_.key),J.push(void 0);break;case"get":let G=await this.get(_.key);J.push(G);break}if(Object.keys(X).length>0)await browser.storage.local.set(X);if(Y.length>0)await browser.storage.local.remove(Y);return J}catch(X){if(this.debug)console.error("Batch operation failed:",X);if(this.debug)throw Error("Batch operation failed");return J}}async getStorageUsage(){if(!this.isContextValid()){if(this.debug)console.warn("Cannot get storage usage: Extension context invalidated");return 0}try{let H=await browser.storage.local.get(null),J=JSON.stringify(H);return new Blob([J]).size}catch(H){if(this.debug)console.error("Failed to get storage usage:",H);return 0}}async cleanupExpired(){if(!this.isContextValid()){if(this.debug)console.warn("Cannot clean up expired keys: Extension context invalidated");this.stopCleanup();return}try{let H=await browser.storage.local.get(null),J=Date.now(),X=[];for(let Y in H){let _=H[Y];if(_?.expires&&J>_.expires)X.push(Y)}if(X.length>0)await browser.storage.local.remove(X)}catch(H){if(this.debug)throw console.error("Failed to clean up expired keys:",H),H}}handleStorageChange(H,J){if(J!=="local"||!this.isContextValid())return;for(let[X,{oldValue:Y,newValue:_}]of Object.entries(H))if(Y===void 0&&_!==void 0)this.triggerListeners("add",X,void 0,_?.value||_);else if(_===void 0&&Y!==void 0)this.triggerListeners("remove",X);else if(Y!==void 0&&_!==void 0)this.triggerListeners("update",X,Y?.value||Y,_?.value||_)}isBackgroundScript(){try{return!!browser.runtime?.getManifest()?.background}catch{return!1}}destroy(){if(this.boundStorageHandler&&browser?.storage?.onChanged){try{browser.storage.onChanged.removeListener(this.boundStorageHandler)}catch(H){if(this.debug)console.error("Failed to remove storage listener:",H)}this.boundStorageHandler=null}super.destroy()}}class f extends N{dbName="QevoKVStore";dbVersion=1;storeName="keyvalue";db=null;initPromise=null;constructor(){super();this.initPromise=this.initDB()}isContextValid(){return typeof self<"u"&&"indexedDB"in self}shouldRunCleanup(){return!this.isServiceWorker()&&typeof window<"u"}async initDB(){if(!this.isContextValid())throw Error("IndexedDB not available");return new Promise((H,J)=>{let X=indexedDB.open(this.dbName,this.dbVersion);X.onerror=()=>J(X.error),X.onsuccess=()=>{this.db=X.result,H()},X.onupgradeneeded=(Y)=>{let _=Y.target.result;if(!_.objectStoreNames.contains(this.storeName))_.createObjectStore(this.storeName)}})}async ensureDB(){if(this.initPromise)await this.initPromise;if(!this.db)throw Error("Database not initialized");return this.db}async transaction(H,J){let X=await this.ensureDB();return new Promise((Y,_)=>{let W=X.transaction([this.storeName],H).objectStore(this.storeName),B=J(W);B.onsuccess=()=>Y(B.result),B.onerror=()=>_(B.error)})}async put(H,J,X){let{ttl:Y,expires:_}=X||{},G=Y?Date.now()+Y*1000:_?_.getTime():void 0,W={value:J,expires:G},B=await this.getWithMetadata(H);if(await this.transaction("readwrite",(E)=>E.put(W,H)),B===null)this.triggerListeners("add",H,void 0,J);else this.triggerListeners("update",H,B.value,J)}async get(H,J){try{if(J){let Y=await this.listKeys(H);if(Y.length===0)return null;H=Y[0]}let X=await this.transaction("readonly",(Y)=>Y.get(H));if(!X)return null;if(X.expires&&Date.now()>X.expires)return await this.remove(H),null;return X.value}catch(X){if(this.debug)console.error(`Failed to get key ${H}:`,X);return null}}async getWithMetadata(H){try{let J=await this.transaction("readonly",(X)=>X.get(H));if(!J)return null;if(J.expires&&Date.now()>J.expires)return await this.remove(H),null;return J}catch(J){if(this.debug)console.error(`Failed to get metadata for key ${H}:`,J);return null}}async has(H){try{let J=await this.transaction("readonly",(X)=>X.get(H));if(!J)return!1;if(J.expires&&Date.now()>J.expires)return await this.remove(H),!1;return!0}catch(J){if(this.debug)console.error(`Failed to check key ${H}:`,J);return!1}}async remove(H){try{await this.transaction("readwrite",(J)=>J.delete(H)),this.triggerListeners("remove",H)}catch(J){if(this.debug)console.error(`Failed to remove key ${H}:`,J)}}async length(){try{return await this.transaction("readonly",(H)=>H.count())}catch(H){if(this.debug)console.error("Failed to get storage length:",H);return 0}}async listKeys(H=""){try{let J=await this.ensureDB();return new Promise((X,Y)=>{let W=J.transaction([this.storeName],"readonly").objectStore(this.storeName).getAllKeys();W.onsuccess=()=>{let B=W.result,E=H.length>0?B.filter((U)=>U.startsWith(H)):B;X(E)},W.onerror=()=>Y(W.error)})}catch(J){if(this.debug)console.error("Failed to list keys:",J);return[]}}async getKeyByPrefix(H){let J=await this.listKeys(H);return J.length>0?J[0]:void 0}async getKeyBySuffix(H){return(await this.listKeys()).find((X)=>X.endsWith(H))}async batch(H){let J=[];try{for(let X of H)switch(X.type){case"set":if(X.value!==void 0)await this.put(X.key,X.value,{ttl:X.ttl,expires:X.expires});J.push(void 0);break;case"remove":await this.remove(X.key),J.push(void 0);break;case"get":let Y=await this.get(X.key);J.push(Y);break}return J}catch(X){if(this.debug)console.error("Batch operation failed:",X);if(this.debug)throw Error("Batch operation failed");return J}}async getStorageUsage(){try{let H=await this.ensureDB();return new Promise((J,X)=>{let G=H.transaction([this.storeName],"readonly").objectStore(this.storeName).getAll();G.onsuccess=()=>{let W=G.result,B=JSON.stringify(W),E=new Blob([B]).size;J(E)},G.onerror=()=>X(G.error)})}catch(H){if(this.debug)console.error("Failed to get storage usage:",H);return 0}}async cleanupExpired(){try{let H=await this.ensureDB(),J=Date.now(),X=[];if(await new Promise((Y,_)=>{let B=H.transaction([this.storeName],"readonly").objectStore(this.storeName).openCursor();B.onsuccess=(E)=>{let U=E.target.result;if(U){let Z=U.value;if(Z?.expires&&J>Z.expires)X.push(U.key);U.continue()}else Y()},B.onerror=()=>_(B.error)}),X.length>0){let _=H.transaction([this.storeName],"readwrite").objectStore(this.storeName);for(let G of X)_.delete(G)}}catch(H){if(this.debug)throw console.error("Failed to clean up expired keys:",H),H}}destroy(){if(this.db){try{this.db.close()}catch(H){if(this.debug)console.error("Failed to close IndexedDB connection:",H)}this.db=null}this.initPromise=null,super.destroy()}}function g(){if(typeof chrome<"u"&&chrome.runtime&&chrome.storage)return new x;if(typeof browser<"u"&&browser.runtime&&browser.storage)return new A;if(typeof window<"u"&&"indexedDB"in window)return new f;throw Error("No suitable storage backend available")}var w=g();var M=typeof browser<"u"&&typeof browser.runtime<"u",$=M?browser:chrome;class q{_debug=!1;constructor(H=!1){this._debug=H}set debug(H){this._debug=H}get debug(){return this._debug}log(...H){if(this._debug)console.log("[QevoTabs]",...H)}error(...H){if(this._debug)console.error("[QevoTabs]",...H)}async create(H){try{if(M)return await $.tabs.create(H);else return await $.tabs.create(H)}catch(J){throw this.error("Error creating tab:",J),J}}async get(H){try{if(M)return await $.tabs.get(H);else return await $.tabs.get(H)}catch(J){throw this.error("Error getting tab:",J),J}}async getCurrent(){try{if(M){let H=await $.tabs.getCurrent();if(!H)throw Error("No current tab found");return H}else{let H=await $.tabs.getCurrent();if(!H)throw Error("No current tab found");return H}}catch(H){throw this.error("Error getting current tab:",H),H}}async query(H){try{if(M)return await $.tabs.query(H);else return await $.tabs.query(H)}catch(J){throw this.error("Error querying tabs:",J),J}}async update(H,J){try{if(typeof H==="number")if(M){let X=await $.tabs.update(H,J);if(!X)throw Error("Failed to update tab");return X}else{let X=await $.tabs.update(H,J);if(!X)throw Error("Failed to update tab");return X}else if(M){let X=await $.tabs.update(H);if(!X)throw Error("Failed to update tab");return X}else{let X=await $.tabs.update(H);if(!X)throw Error("Failed to update tab");return X}}catch(X){throw this.error("Error updating tab:",X),X}}async remove(H){try{if(M)if(Array.isArray(H))await $.tabs.remove(H);else await $.tabs.remove(H);else if(Array.isArray(H))await $.tabs.remove(H);else await $.tabs.remove(H)}catch(J){throw this.error("Error removing tab:",J),J}}async duplicate(H){try{if(M)return await $.tabs.duplicate(H);else return await $.tabs.duplicate(H)}catch(J){throw this.error("Error duplicating tab:",J),J}}async move(H,J){try{if(M)if(Array.isArray(H))return await $.tabs.move(H,J);else return await $.tabs.move(H,J);else if(Array.isArray(H))return await $.tabs.move(H,J);else return await $.tabs.move(H,J)}catch(X){throw this.error("Error moving tab:",X),X}}async reload(H,J){try{if(M)if(H!==void 0)await $.tabs.reload(H,J);else await $.tabs.reload(J);else if(H!==void 0)await $.tabs.reload(H,J);else await $.tabs.reload(J)}catch(X){throw this.error("Error reloading tab:",X),X}}async highlight(H){try{if(M)return await $.tabs.highlight(H);else return await $.tabs.highlight(H)}catch(J){throw this.error("Error highlighting tabs:",J),J}}async discard(H){try{if(M){await $.tabs.discard(H);let J=await $.tabs.get(H);if(!J)throw Error("Failed to get discarded tab");return J}else{let J=await $.tabs.discard(H);if(!J)throw Error("Failed to discard tab");return J}}catch(J){throw this.error("Error discarding tab:",J),J}}async getZoom(H){try{if(M)if(H!==void 0)return await $.tabs.getZoom(H);else return await $.tabs.getZoom();else if(H!==void 0)return await $.tabs.getZoom(H);else return await $.tabs.getZoom()}catch(J){throw this.error("Error getting zoom:",J),J}}async setZoom(H,J){try{if(typeof J==="number")if(M)if(H!==void 0)await $.tabs.setZoom(H,J);else await $.tabs.setZoom(J);else if(H!==void 0)await $.tabs.setZoom(H,J);else await $.tabs.setZoom(J);else if(M)await $.tabs.setZoom(H);else await $.tabs.setZoom(H)}catch(X){throw this.error("Error setting zoom:",X),X}}async getZoomSettings(H){try{if(M)return await $.tabs.getZoomSettings(H);else return await $.tabs.getZoomSettings(H)}catch(J){throw this.error("Error getting zoom settings:",J),J}}async setZoomSettings(H,J){try{if(J)if(M)return await $.tabs.setZoomSettings(H,J);else return await $.tabs.setZoomSettings(H,J);else if(M)return await $.tabs.setZoomSettings(H);else return await $.tabs.setZoomSettings(H)}catch(X){throw this.error("Error setting zoom settings:",X),X}}async getAll(){return this.query({})}async getAllInCurrentWindow(){return this.query({currentWindow:!0})}async getActive(){let[H]=await this.query({active:!0,currentWindow:!0});return H}async findByUrl(H){return(await this.getAll()).find((X)=>X.url===H||X.url?.includes(H))}async findByTitle(H){return(await this.getAll()).find((X)=>X.title?.toLowerCase().includes(H.toLowerCase()))}async goBack(H){try{if(M)await $.tabs.goBack(H);else await $.tabs.goBack(H)}catch(J){throw this.error("Error navigating back:",J),J}}async goForward(H){try{if(M)await $.tabs.goForward(H);else await $.tabs.goForward(H)}catch(J){throw this.error("Error navigating forward:",J),J}}async group(H){try{if(M)throw Error("Tab groups are not supported in Firefox");else return await $.tabs.group(H)}catch(J){throw this.error("Error grouping tabs:",J),J}}async ungroup(H){try{if(M)throw Error("Tab groups are not supported in Firefox");else if(Array.isArray(H))await $.tabs.ungroup(H);else await $.tabs.ungroup(H)}catch(J){throw this.error("Error ungrouping tabs:",J),J}}async captureVisibleTab(H,J){try{if(M)if(H!==void 0)return await $.tabs.captureVisibleTab(H,J);else return await $.tabs.captureVisibleTab(J);else if(H!==void 0)return await $.tabs.captureVisibleTab(H,J);else return await $.tabs.captureVisibleTab(J)}catch(X){throw this.error("Error capturing visible tab:",X),X}}async detectLanguage(H){try{if(M)return await $.tabs.detectLanguage(H);else return await $.tabs.detectLanguage(H)}catch(J){throw this.error("Error detecting language:",J),J}}connect(H,J){try{if(M)return $.tabs.connect(H,J);else return $.tabs.connect(H,J)}catch(X){throw this.error("Error connecting to tab:",X),X}}async getUrl(H){try{return(await this.get(H)).url}catch(J){this.error("Error getting tab URL:",J);return}}async getTitle(H){try{return(await this.get(H)).title}catch(J){this.error("Error getting tab title:",J);return}}async getFavIcon(H){try{return(await this.get(H)).favIconUrl}catch(J){this.error("Error getting tab favicon:",J);return}}async getWindow(H){try{return(await this.get(H)).windowId}catch(J){throw this.error("Error getting tab window:",J),J}}onActivated={addListener:(H)=>{$.tabs.onActivated.addListener(H)},removeListener:(H)=>{$.tabs.onActivated.removeListener(H)},hasListener:(H)=>{return $.tabs.onActivated.hasListener(H)}};onAttached={addListener:(H)=>{$.tabs.onAttached.addListener(H)},removeListener:(H)=>{$.tabs.onAttached.removeListener(H)},hasListener:(H)=>{return $.tabs.onAttached.hasListener(H)}};onCreated={addListener:(H)=>{$.tabs.onCreated.addListener(H)},removeListener:(H)=>{$.tabs.onCreated.removeListener(H)},hasListener:(H)=>{return $.tabs.onCreated.hasListener(H)}};onDetached={addListener:(H)=>{$.tabs.onDetached.addListener(H)},removeListener:(H)=>{$.tabs.onDetached.removeListener(H)},hasListener:(H)=>{return $.tabs.onDetached.hasListener(H)}};onHighlighted={addListener:(H)=>{$.tabs.onHighlighted.addListener(H)},removeListener:(H)=>{$.tabs.onHighlighted.removeListener(H)},hasListener:(H)=>{return $.tabs.onHighlighted.hasListener(H)}};onMoved={addListener:(H)=>{$.tabs.onMoved.addListener(H)},removeListener:(H)=>{$.tabs.onMoved.removeListener(H)},hasListener:(H)=>{return $.tabs.onMoved.hasListener(H)}};onRemoved={addListener:(H)=>{$.tabs.onRemoved.addListener(H)},removeListener:(H)=>{$.tabs.onRemoved.removeListener(H)},hasListener:(H)=>{return $.tabs.onRemoved.hasListener(H)}};onReplaced={addListener:(H)=>{$.tabs.onReplaced.addListener(H)},removeListener:(H)=>{$.tabs.onReplaced.removeListener(H)},hasListener:(H)=>{return $.tabs.onReplaced.hasListener(H)}};onUpdated={addListener:(H)=>{$.tabs.onUpdated.addListener(H)},removeListener:(H)=>{$.tabs.onUpdated.removeListener(H)},hasListener:(H)=>{return $.tabs.onUpdated.hasListener(H)}};onZoomChange={addListener:(H)=>{$.tabs.onZoomChange.addListener(H)},removeListener:(H)=>{$.tabs.onZoomChange.removeListener(H)},hasListener:(H)=>{return $.tabs.onZoomChange.hasListener(H)}}}var C=typeof browser<"u"&&browser.runtime,L=C?browser:chrome;function S(){if(typeof process<"u"&&process.env)return!0;if(typeof __webpack_require__<"u")try{if(process.env?.NODE_ENV!=="production")return!0}catch{}if(typeof globalThis<"u"){if(globalThis.__DEV__)return!0;if(globalThis.DEBUG)return!0}return!1}class z{_debug=!1;constructor(H){this._debug=H??S()}set debug(H){this._debug=H}get debug(){return this._debug}log(H,...J){if(this._debug)console.log(`[${H}]`,...J)}error(H,...J){if(this._debug)console.error(`[${H}]`,...J)}warn(H,...J){if(this._debug)console.warn(`[${H}]`,...J)}}function O(){return`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}function c(H,J="Operation timed out"){return new Promise((X,Y)=>{setTimeout(()=>Y(Error(J)),H)})}async function v(H,J,X){return Promise.race([H,c(J,X)])}function u(H){return new Promise((J)=>setTimeout(J,H))}class V extends z{cookieChangeListeners=new Set;browserListenerInitialized=!1;constructor(H=!1){super(H)}async get(H){try{if(C)return await L.cookies.get(H);else return await L.cookies.get(H)}catch(J){return this.error("QevoCookies","Error getting cookie:",J),null}}async getAll(H){try{if(C)return await L.cookies.getAll(H);else return await L.cookies.getAll(H)}catch(J){return this.error("QevoCookies","Error getting all cookies:",J),[]}}async set(H){try{if(C)return await L.cookies.set(H);else return await L.cookies.set(H)}catch(J){return this.error("QevoCookies","Error setting cookie:",J),null}}async remove(H){try{if(C)await L.cookies.remove(H);else await L.cookies.remove(H);return H}catch(J){throw this.error("QevoCookies","Error removing cookie:",J),J}}async getAllCookieStores(){try{if(C)return await L.cookies.getAllCookieStores();else return await L.cookies.getAllCookieStores()}catch(H){return this.error("QevoCookies","Error getting cookie stores:",H),[]}}addListener(H){if(!this.cookieChangeListeners.has(H)){if(this.cookieChangeListeners.add(H),this.cookieChangeListeners.size===1&&!this.browserListenerInitialized)this.initializeBrowserListener()}}removeListener(H){this.cookieChangeListeners.delete(H)}hasListener(H){return this.cookieChangeListeners.has(H)}initializeBrowserListener(){if(this.browserListenerInitialized)return;let H=(J)=>{this.cookieChangeListeners.forEach((X)=>{try{X(J)}catch(Y){this.error("QevoCookies","Error in cookie change listener:",Y)}})};if(C)L.cookies.onChanged.addListener(H);else L.cookies.onChanged.addListener(H);this.browserListenerInitialized=!0}get api(){return{get:this.get.bind(this),getAll:this.getAll.bind(this),set:this.set.bind(this),remove:this.remove.bind(this),getAllCookieStores:this.getAllCookieStores.bind(this),onChanged:{addListener:this.addListener.bind(this),removeListener:this.removeListener.bind(this),hasListener:this.hasListener.bind(this)}}}}class F extends z{messageListeners=new Map;listenerInitialized=!1;constructor(H=!1){super(H);this.initializeGlobalListener()}initializeGlobalListener(){if(this.listenerInitialized)return;L.runtime.onMessage.addListener((H,J,X)=>{let{type:Y,data:_,id:G,timestamp:W}=H,B=Date.now();if(!Y)return X(this.createErrorResponse("Message type is required",G,B-(W||B))),!1;let E=this.messageListeners.get(Y);if(!E||E.length===0)return X(this.createErrorResponse(`No listeners registered for message type: ${Y}`,G,B-(W||B))),!1;let U=!1,Z=(j)=>{if(!U){U=!0;let R=Date.now()-B;X(this.createSuccessResponse(j,G,R))}};try{let j=E[0],R=j.constructor.name==="AsyncFunction",K=j(_,J,Z);if(K&&typeof K==="object"&&typeof K.then==="function")return K.then(()=>{}).catch((h)=>{if(this.error("QevoMessages","Error in async message listener:",h),!U){let P=Date.now()-B;X(this.createErrorResponse(h instanceof Error?h.message:"Unknown error in message listener",G,P))}}),!0;if(K===!0)return!0;if(R)return!0;return!1}catch(j){if(this.error("QevoMessages","Error in message listener:",j),!U){let R=Date.now()-B;X(this.createErrorResponse(j instanceof Error?j.message:"Unknown error in message listener",G,R))}return!1}}),this.listenerInitialized=!0}on(H,J){if(!this.messageListeners.has(H))this.messageListeners.set(H,[]);this.messageListeners.get(H).push(J)}off(H,J){let X=this.messageListeners.get(H);if(X){let Y=X.indexOf(J);if(Y>-1)X.splice(Y,1);if(X.length===0)this.messageListeners.delete(H)}}clear(H){let J=this.messageListeners.get(H);if(J)J.length=0,this.messageListeners.delete(H)}getTypes(){return Array.from(this.messageListeners.keys())}hasListeners(H){let J=this.messageListeners.get(H);return!!(J&&J.length>0)}async sendToBackground(H,J,X={}){let Y=O(),_={type:H,data:J,id:Y,timestamp:Date.now()};return this.sendWithRetry(()=>{if(C)return L.runtime.sendMessage(_);else return L.runtime.sendMessage(_)},X,Y)}async sendToTab(H,J,X,Y={}){let _=O(),G={type:J,data:X,id:_,timestamp:Date.now()};return this.sendWithRetry(()=>L.tabs.sendMessage(H,G),Y,_)}async sendToAllTabs(H,J,X={}){try{let _=(await L.tabs.query({})).map((G)=>{if(G.id)return this.sendToTab(G.id,H,J,X).catch((W)=>this.createErrorResponse(W.message,void 0,0));return Promise.resolve(this.createErrorResponse("Tab has no ID",void 0,0))});return Promise.all(_)}catch(Y){return this.error("QevoMessages","Error sending to all tabs:",Y),[this.createErrorResponse(Y instanceof Error?Y.message:"Failed to send to all tabs",void 0,0)]}}async broadcast(H,J,X={}){return this.sendToAllTabs(H,J,X)}async sendWithRetry(H,J,X){let{timeout:Y=5000,retries:_=0,retryDelay:G=1000}=J,W=Date.now(),B=null;for(let U=0;U<=_;U++)try{return await v(H(),Y,"Message timeout")}catch(Z){if(B=Z instanceof Error?Z:Error(String(Z)),U<_)await u(G)}let E=Date.now()-W;return this.createErrorResponse(B?.message||"Failed to send message after retries",X,E)}createSuccessResponse(H,J,X=0){return{success:!0,data:H,timestamp:Date.now(),messageId:J||O(),duration:X}}createErrorResponse(H,J,X=0){return{success:!1,error:H,timestamp:Date.now(),messageId:J||O(),duration:X}}}class T extends z{webRequestListeners=new Map;simpleWebRequestListeners=new Map;maxListeners=10;constructor(H=!1){super(H)}on(H,J,X,Y){let G={BeforeRequest:"onBeforeRequest",BeforeSendHeaders:"onBeforeSendHeaders",SendHeaders:"onSendHeaders",HeadersReceived:"onHeadersReceived",AuthRequired:"onAuthRequired",ResponseStarted:"onResponseStarted",BeforeRedirect:"onBeforeRedirect",Completed:"onCompleted",ErrorOccurred:"onErrorOccurred"}[H];if(!G)throw Error(`Unsupported event type: ${H}`);if(!this.simpleWebRequestListeners.has(H))this.simpleWebRequestListeners.set(H,new Set);this.simpleWebRequestListeners.get(H).add(J),this.addListener(G,J,X,Y)}off(H,J){let Y={BeforeRequest:"onBeforeRequest",BeforeSendHeaders:"onBeforeSendHeaders",SendHeaders:"onSendHeaders",HeadersReceived:"onHeadersReceived",AuthRequired:"onAuthRequired",ResponseStarted:"onResponseStarted",BeforeRedirect:"onBeforeRedirect",Completed:"onCompleted",ErrorOccurred:"onErrorOccurred"}[H];if(!Y)throw Error(`Unsupported event type: ${H}`);let _=this.simpleWebRequestListeners.get(H);if(_&&_.has(J)){if(_.delete(J),_.size===0)this.simpleWebRequestListeners.delete(H)}this.removeListener(Y,J)}clear(H){let J=this.simpleWebRequestListeners.get(H);if(J){let X=Array.from(J);for(let Y of X)this.off(H,Y);J.clear()}}isAvailable(){try{return!!L.webRequest}catch{return!1}}getMaxListeners(){return this.maxListeners}setMaxListeners(H){this.maxListeners=H}addListener(H,J,X,Y){try{if(!this.isAvailable())throw Error("WebRequest API is not available in this context");let _=this.convertFilter(X),G=this.convertExtraInfoSpec(Y);if(!this.webRequestListeners.has(H))this.webRequestListeners.set(H,new Set);if(this.webRequestListeners.get(H).add(J),C){let W=L;if(W.webRequest&&W.webRequest[H])W.webRequest[H].addListener(J,_,G)}else{let W=L;if(W.webRequest&&W.webRequest[H])W.webRequest[H].addListener(J,_,G)}}catch(_){throw this.error("QevoWebRequest",`Error adding listener for ${H}:`,_),_}}removeListener(H,J){try{let X=this.webRequestListeners.get(H);if(X&&X.has(J))if(X.delete(J),C){let Y=L;if(Y.webRequest&&Y.webRequest[H])Y.webRequest[H].removeListener(J)}else{let Y=L;if(Y.webRequest&&Y.webRequest[H])Y.webRequest[H].removeListener(J)}}catch(X){throw this.error("QevoWebRequest",`Error removing listener for ${H}:`,X),X}}convertFilter(H){if(C){let J={urls:H.urls};if(H.types)J.types=H.types;if(H.tabId!==void 0)J.tabId=H.tabId;if(H.windowId!==void 0)J.windowId=H.windowId;return J}return H}convertExtraInfoSpec(H){if(!H)return;if(C)return H.map((J)=>{switch(J){case"asyncBlocking":return"blocking";case"extraHeaders":return"extraHeaders";default:return J}}).filter(Boolean);return H}get api(){return{on:this.on.bind(this),off:this.off.bind(this),clear:this.clear.bind(this),isAvailable:this.isAvailable.bind(this),getMaxListeners:this.getMaxListeners.bind(this)}}}class Q{static instance;_debug;currentWindowId=null;currentTabId=null;_tabsInstance;_cookiesInstance;_messagesInstance;_webRequestInstance;constructor(){this._debug=S(),this.updateCurrentContext(),this.log("\uD83D\uDE80 Qevo initialized")}async updateCurrentContext(){try{if(L.windows&&L.windows.getCurrent){let H=await L.windows.getCurrent();this.currentWindowId=H.id||null}if(L.tabs&&L.tabs.query){let[H]=await L.tabs.query({active:!0,currentWindow:!0});this.currentTabId=H?.id||null}}catch(H){console.warn("Could not update current context:",H)}}set debug(H){if(this._debug=H,this._tabsInstance)this._tabsInstance.debug=H;if(this._cookiesInstance)this._cookiesInstance.debug=H;if(this._messagesInstance)this._messagesInstance.debug=H;if(this._webRequestInstance)this._webRequestInstance.debug=H;this.log(`\uD83D\uDD27 Qevo Debug Mode: ${H?"ENABLED":"DISABLED"}`)}get debug(){return this._debug}get tabs(){if(!this._tabsInstance)this._tabsInstance=new q(this._debug);return this._tabsInstance}get cookies(){if(!this._cookiesInstance)this._cookiesInstance=new V(this._debug);return this._cookiesInstance}get messages(){if(!this._messagesInstance)this._messagesInstance=new F(this._debug);return this._messagesInstance}get webRequest(){if(!this._webRequestInstance)this._webRequestInstance=new T(this._debug);return this._webRequestInstance}get storage(){return w}isBackgroundScript(){try{return!!L.runtime?.getManifest()?.background}catch{return!1}}isContentScript(){return typeof window<"u"&&window.location!==void 0}getBrowserType(){if(typeof browser<"u"&&browser.runtime)return"firefox";if(typeof chrome<"u")return"chrome";return"unknown"}addMessageListener(H,J){this.messages.on(H,J)}removeMessageListener(H,J){this.messages.off(H,J)}async sendMessageToBackground(H,J,X){return this.messages.sendToBackground(H,J,X)}async sendMessageToTab(H,J,X,Y){return this.messages.sendToTab(H,J,X,Y)}async sendMessageToTabs(H,J,X,Y){if(X){let W=(await L.tabs.query({})).filter((B)=>B.url?.includes(X)).map((B)=>{if(B.id)return this.messages.sendToTab(B.id,H,J,Y);return Promise.resolve({success:!1,error:"Tab has no ID",timestamp:Date.now(),messageId:`error-${Date.now()}`,duration:0})});return Promise.all(W)}return this.messages.sendToAllTabs(H,J,Y)}async broadcastMessage(H,J,X){return this.messages.broadcast(H,J,X)}async sendMessage(H,J,X="background",Y={}){if(!X||X==="background")return this.sendMessageToBackground(H,J,Y);else if(X==="all-tabs")return this.sendMessageToTabs(H,J,void 0,Y);else if(typeof X==="number")return this.sendMessageToTab(X,H,J,Y);return this.sendMessageToBackground(H,J,Y)}async getAllTabs(){try{return await this.updateCurrentContext(),(await L.tabs.query({})).map((J)=>this.mapTabToTabInfo(J))}catch(H){return console.error("Error getting all tabs:",H),[]}}async getTabsInCurrentWindow(){try{return await this.updateCurrentContext(),(await L.tabs.query({currentWindow:!0})).map((J)=>this.mapTabToTabInfo(J))}catch(H){return console.error("Error getting tabs in current window:",H),[]}}async getTabByUrl(H){try{return(await this.getAllTabs()).find((X)=>X.url===H||X.url.includes(H))||null}catch(J){return console.error("Error getting tab by URL:",J),null}}async getTabById(H){try{let J=await L.tabs.get(H);return this.mapTabToTabInfo(J)}catch(J){return console.error("Error getting tab by ID:",J),null}}async getTabByTitle(H){try{return(await this.getAllTabs()).find((X)=>X.title.toLowerCase().includes(H.toLowerCase()))||null}catch(J){return console.error("Error getting tab by title:",J),null}}async getCurrentTab(){try{let[H]=await L.tabs.query({active:!0,currentWindow:!0});return H?this.mapTabToTabInfo(H):null}catch(H){return console.error("Error getting current tab:",H),null}}mapTabToTabInfo(H){return{url:H.url||"",title:H.title||"",tabId:H.id||-1,windowId:H.windowId||-1,isInCurrentWindow:H.windowId===this.currentWindowId,isCurrentTab:H.id===this.currentTabId,active:H.active||!1,pinned:H.pinned||!1,audible:H.audible||!1,muted:H.mutedInfo?.muted||!1,incognito:H.incognito||!1,status:H.status||"complete",favIconUrl:H.favIconUrl,index:H.index||0}}get message(){return{on:this.addMessageListener.bind(this),off:this.removeMessageListener.bind(this),clear:(H)=>this.messages.clear(H),getTypes:()=>this.messages.getTypes(),hasListeners:(H)=>this.messages.hasListeners(H)}}log(...H){if(this._debug)console.log("[Qevo]",...H)}static getInstance(){if(!Q.instance)Q.instance=new Q;return Q.instance}}var D=Q.getInstance(),WH=D,HH=D.isBackgroundScript.bind(D),JH=D.isContentScript.bind(D),XH=D.getBrowserType.bind(D),YH=D.storage,_H=D.cookies,$H=D.tabs,GH=D.webRequest;export{GH as webRequest,$H as tabs,YH as storage,D as qevo,JH as isContentScript,HH as isBackgroundScript,XH as getBrowserType,WH as default,_H as cookies,Q as Qevo};