UNPKG

@plasmohq/storage

Version:

Safely and securely store data and share them across your extension and websites

2 lines (1 loc) 6.77 kB
import m from"pify";var g=()=>{try{let e=(globalThis.navigator?.userAgent).match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i)||[];if(e[1]==="Chrome")return parseInt(e[2])<100||globalThis.chrome.runtime?.getManifest()?.manifest_version===2}catch{return!1}return!1};var l=class{#a;#t;get primaryClient(){return this.#t}#e;get secondaryClient(){return this.#e}#n;get area(){return this.#n}get hasWebApi(){try{return typeof window<"u"&&!!window.localStorage}catch(e){return console.error(e),!1}}#s=new Map;#h;get copiedKeySet(){return this.#h}isCopied=e=>this.hasWebApi&&(this.allCopied||this.copiedKeySet.has(e));#o=!1;get allCopied(){return this.#o}getExtStorageApi=()=>globalThis.browser?.storage||globalThis.chrome?.storage;get hasExtensionApi(){try{return!!this.getExtStorageApi()}catch(e){return console.error(e),!1}}isWatchSupported=()=>this.hasExtensionApi;keyNamespace="";isValidKey=e=>e.startsWith(this.keyNamespace);getNamespacedKey=e=>`${this.keyNamespace}${e}`;getUnnamespacedKey=e=>e.slice(this.keyNamespace.length);serde={serializer:JSON.stringify,deserializer:JSON.parse};constructor({area:e="sync",allCopied:t=!1,copiedKeyList:s=[],serde:a={}}={}){this.setCopiedKeySet(s),this.#n=e,this.#o=t,this.serde={...this.serde,...a};try{this.hasWebApi&&(t||s.length>0)&&(this.#e=window.localStorage)}catch{}try{this.hasExtensionApi&&(this.#a=this.getExtStorageApi(),g()?this.#t=m(this.#a[this.area],{exclude:["getBytesInUse"],errorFirst:!1}):this.#t=this.#a[this.area])}catch{}}setCopiedKeySet(e){this.#h=new Set(e)}rawGetAll=()=>this.#t?.get();getAll=async()=>{let e=await this.rawGetAll();return Object.entries(e).filter(([t])=>this.isValidKey(t)).reduce((t,[s,a])=>(t[this.getUnnamespacedKey(s)]=a,t),{})};copy=async e=>{let t=e===void 0;if(!t&&!this.copiedKeySet.has(e)||!this.allCopied||!this.hasExtensionApi)return!1;let s=this.allCopied?await this.rawGetAll():await this.#t.get((t?[...this.copiedKeySet]:[e]).map(this.getNamespacedKey));if(!s)return!1;let a=!1;for(let r in s){let i=s[r],n=this.#e?.getItem(r);this.#e?.setItem(r,i),a||=i!==n}return a};rawGet=async e=>(await this.rawGetMany([e]))[e];rawGetMany=async e=>this.hasExtensionApi?await this.#t.get(e):e.filter(this.isCopied).reduce((t,s)=>(t[s]=this.#e?.getItem(s),t),{});rawSet=async(e,t)=>await this.rawSetMany({[e]:t});rawSetMany=async e=>(this.#e&&Object.entries(e).filter(([t])=>this.isCopied(t)).forEach(([t,s])=>this.#e.setItem(t,s)),this.hasExtensionApi&&await this.#t.set(e),null);clear=async(e=!1)=>{e&&this.#e?.clear(),await this.#t.clear()};rawRemove=async e=>{await this.rawRemoveMany([e])};rawRemoveMany=async e=>{this.#e&&e.filter(this.isCopied).forEach(t=>this.#e.removeItem(t)),this.hasExtensionApi&&await this.#t.remove(e)};removeAll=async()=>{let e=await this.getAll(),t=Object.keys(e);await this.removeMany(t)};watch=e=>{let t=this.isWatchSupported();return t&&this.#r(e),t};#r=e=>{for(let t in e){let s=this.getNamespacedKey(t),a=this.#s.get(s)?.callbackSet||new Set;if(a.add(e[t]),a.size>1)continue;let r=(i,n)=>{if(n!==this.area||!i[s])return;let c=this.#s.get(s);if(!c)throw new Error(`Storage comms does not exist for nsKey: ${s}`);Promise.all([this.parseValue(i[s].newValue),this.parseValue(i[s].oldValue)]).then(([y,p])=>{for(let u of c.callbackSet)u({newValue:y,oldValue:p},n)})};this.#a.onChanged.addListener(r),this.#s.set(s,{callbackSet:a,listener:r})}};unwatch=e=>{let t=this.isWatchSupported();return t&&this.#c(e),t};#c(e){for(let t in e){let s=this.getNamespacedKey(t),a=e[t],r=this.#s.get(s);r&&(r.callbackSet.delete(a),r.callbackSet.size===0&&(this.#s.delete(s),this.#a.onChanged.removeListener(r.listener)))}}unwatchAll=()=>this.#i();#i(){this.#s.forEach(({listener:e})=>this.#a.onChanged.removeListener(e)),this.#s.clear()}async getItem(e){return this.get(e)}async getItems(e){return await this.getMany(e)}async setItem(e,t){await this.set(e,t)}async setItems(e){await await this.setMany(e)}async removeItem(e){return this.remove(e)}async removeItems(e){return await this.removeMany(e)}};var{crypto:h}=globalThis,w=o=>Array.from(new Uint8Array(o),e=>e.toString(16).padStart(2,"0")).join(""),f=o=>globalThis.btoa(String.fromCharCode.apply(null,o)),S=o=>Uint8Array.from(globalThis.atob(o),e=>e.charCodeAt(0)),b=147e3,K=16,A=32,T=8,v="|:|",d=class extends l{#a=new TextEncoder;#t=new TextDecoder;#e="PBKDF2";#n="SHA-256";#s="AES-GCM";#h=256;#o;#r;#c;get#i(){return this.#r+this.#c}#l;get passwordKey(){if(!this.#l)throw new Error("Password not set, please first call setPassword.");return this.#l}setPassword=async(e,{iterations:t=b,saltSize:s=K,ivSize:a=A,namespace:r="",nsSize:i=T,nsSeparator:n=v}={})=>{this.#o=t,this.#r=s,this.#c=a;let c=this.#a.encode(e);if(this.#l=await h.subtle.importKey("raw",c,{name:this.#e},!1,["deriveKey"]),r)this.keyNamespace=`${r}${n}`;else{let y=await h.subtle.digest(this.#n,c);this.keyNamespace=`${w(y).slice(-i)}${n}`}};migrate=async e=>{let t=await this.getAll(),s=Object.keys(t).filter(a=>this.isValidKey(a)).map(a=>this.getUnnamespacedKey(a));return await Promise.all(s.map(async a=>{let r=await this.get(a);await e.set(a,r)})),e};decrypt=async e=>{let t=this.passwordKey,s=S(e),a=s.slice(0,this.#r),r=s.slice(this.#r,this.#i),i=s.slice(this.#i),n=await this.#y(a,t,["decrypt"]),c=await h.subtle.decrypt({name:this.#s,iv:r},n,i);return this.#t.decode(c)};encrypt=async e=>{let t=this.passwordKey,s=h.getRandomValues(new Uint8Array(this.#r)),a=h.getRandomValues(new Uint8Array(this.#c)),r=await this.#y(s,t,["encrypt"]),i=new Uint8Array(await h.subtle.encrypt({name:this.#s,iv:a},r,this.#a.encode(e))),n=new Uint8Array(this.#i+i.byteLength);return n.set(s,0),n.set(a,this.#r),n.set(i,this.#i),f(n)};get=async e=>{let t=this.getNamespacedKey(e),s=await this.rawGet(t);return this.parseValue(s)};getMany=async e=>{let t=e.map(this.getNamespacedKey),s=await this.rawGetMany(t),a=await Promise.all(Object.values(s).map(this.parseValue));return Object.keys(s).reduce((r,i,n)=>(r[this.getUnnamespacedKey(i)]=a[n],r),{})};set=async(e,t)=>{let s=this.getNamespacedKey(e),a=this.serde.serializer(t),r=await this.encrypt(a);return await this.rawSet(s,r)};setMany=async e=>{let t=await Promise.all(Object.values(e).map(a=>this.encrypt(this.serde.serializer(a)))),s=Object.keys(e).reduce((a,r,i)=>(a[this.getNamespacedKey(r)]=t[i],a),{});return await this.rawSetMany(s)};remove=async e=>{let t=this.getNamespacedKey(e);return await this.rawRemove(t)};removeMany=async e=>{let t=e.map(this.getNamespacedKey);return await this.rawRemoveMany(t)};parseValue=async e=>{if(e!=null){let t=await this.decrypt(e);return this.serde.deserializer(t)}};#y=(e,t,s)=>h.subtle.deriveKey({name:this.#e,hash:this.#n,salt:e,iterations:this.#o},t,{name:this.#s,length:this.#h},!1,s)};export{v as DEFAULT_NS_SEPARATOR,d as SecureStorage};