@lo-fi/local-data-lock
Advanced tools
Comparing version 0.14.2 to 0.14.3
/*! mylofi/Local-Data-Lock: ldl.js | ||
v0.14.2 (c) 2024 Kyle Simpson | ||
v0.14.3 (c) 2024 Kyle Simpson | ||
MIT License: http://getify.mit-license.org | ||
*/ | ||
import{supportsWebAuthn as e,regDefaults as t,register as r,authDefaults as a,auth as n,verifyAuthResponse as o,packPublicKeyJSON as i,unpackPublicKeyJSON as s,toBase64String as c,fromBase64String as l,toUTF8String as y,fromUTF8String as u,resetAbortReason as p}from"@lo-fi/webauthn-local-client";const f=1,d=sodium.crypto_sign_SEEDBYTES;var g=setLockKeyCacheLifetime(18e5),k="idb",b=null,h=null,w={},m=null,K=new WeakMap;export{e as supportsWebAuthn,i as packPublicKeyJSON,s as unpackPublicKeyJSON,c as toBase64String,l as fromBase64String,y as toUTF8String,u as fromUTF8String,p as resetAbortReason,listLocalIdentities,clearLockKeyCache,removeLocalAccount,getLockKey,generateEntropy,deriveLockKey,lockData,unlockData,signData,verifySignature,configure};var L={supportsWebAuthn:e,packPublicKeyJSON:i,unpackPublicKeyJSON:s,toBase64String:c,fromBase64String:l,toUTF8String:y,fromUTF8String:u,resetAbortReason:p,listLocalIdentities:listLocalIdentities,clearLockKeyCache:clearLockKeyCache,removeLocalAccount:removeLocalAccount,getLockKey:getLockKey,generateEntropy:generateEntropy,deriveLockKey:deriveLockKey,lockData:lockData,unlockData:unlockData,signData:signData,verifySignature:verifySignature,configure:configure};export default L;async function listLocalIdentities(){return await checkStorage(),Object.keys(h)}function cacheLockKey(e,t,r=!1){e in w&&!r||(w[e]={...t,timestamp:Date.now()})}function clearLockKeyCache(e){null!=e?delete w[e]:w={}}async function removeLocalAccount(e){return await checkStorage(),delete w[e],delete h[e],storeLocalIdentities()}async function getLockKey({localIdentity:e=c(generateEntropy(15)),username:i="local-user",displayName:s="Local User",relyingPartyID:l=document.location.hostname,relyingPartyName:y="Local Data Lock",addNewPasskey:u=!1,resetLockKey:p=!1,useLockKey:f=null,verify:k=!0,signal:b}={}){await checkStorage();var K=null!=e?h[e]:null;if(null!=K){let t=function getCachedLockKey(e){var t=Date.now();if(e in w&&w[e].timestamp>=t-Math.min(g,t)){let{timestamp:t,...r}=w[e];return r}}(e);if(null!=t&&!p){if(u){resetAbortToken(b);let{record:e}=await registerLocalIdentity(t)||{};cleanupExternalSignalHandler(m),m=null,null!=e&&(K.lastSeq=e.lastSeq,K.passkeys=[...K.passkeys,...e.passkeys],await storeLocalIdentities())}return Object.freeze({...t,localIdentity:e})}if(delete w[e],p){if(resetAbortToken(b),({record:h[e],lockKey:t}=await registerLocalIdentity(f&&"object"==typeof f?checkLockKey(f):void 0)),cleanupExternalSignalHandler(m),m=null,null==h[e])delete h[e];else if(null!=t)return await storeLocalIdentities(),cacheLockKey(e,t),Object.freeze({...t,localIdentity:e})}else{if(u)throw new Error("Encryption/Decryption key not currently cached, unavailable for new passkey");{resetAbortToken(b);let t=a({relyingPartyID:l,mediation:"optional",allowCredentials:K.passkeys.map((({credentialID:e})=>({type:"public-key",id:e}))),signal:m.signal}),r=await n(t);if(cleanupExternalSignalHandler(m),m=null,null!=r){if(k){let e=K.passkeys.find((e=>e.credentialID==r.response.credentialID)),t=e?.publicKey;if(!(null!=t&&await o(r.response,t)))throw new Error("Auth verification failed")}return{...extractLockKey(r),localIdentity:e}}}}}else if(u){resetAbortToken(b);let{record:t,lockKey:r}=await registerLocalIdentity(f&&"object"==typeof f?checkLockKey(f):void 0)||{};if(cleanupExternalSignalHandler(m),m=null,null!=t&&null!=r)return h[e]=t,cacheLockKey(e,r),await storeLocalIdentities(),Object.freeze({...r,localIdentity:e})}else{resetAbortToken(b);let t=a({relyingPartyID:l,mediation:"optional",signal:m.signal}),r=await n(t);if(cleanupExternalSignalHandler(m),m=null,null!=r){let t=extractLockKey(r),[a]=Object.entries(h).find((([,e])=>null!=e.passkeys.find((e=>e.credentialID==r.response.credentialID))))||[];if(null!=a){if(delete w[e],K=h[e=a],k){let e=K.passkeys.find((e=>e.credentialID==r.response.credentialID)),t=e?.publicKey;if(!(null!=t&&await o(r.response,t)))throw new Error("Auth verification failed")}cacheLockKey(e,t)}else if(k)throw new Error("Auth verification requested but skipped, against unrecognized passkey (no matching local-identity)");return Object.freeze({...t,localIdentity:e})}}async function registerLocalIdentity(a=deriveLockKey()){try{let o=((h[e]||{}).lastSeq||0)+1,c=new Uint8Array(a.iv.byteLength+2),u=new DataView(new ArrayBuffer(2));u.setInt16(0,o,!1),c.set(a.iv,0),c.set(new Uint8Array(u.buffer),a.iv.byteLength);let p=t({relyingPartyID:l,relyingPartyName:y,user:{id:c,name:i,displayName:s},signal:m.signal}),f=await r(p);if(null!=f)return{record:{lastSeq:o,passkeys:[(n={seq:o,credentialID:f.response.credentialID,publicKey:f.response.publicKey},{...n,hash:computePasskeyEntryHash(n)})]},lockKey:a}}catch(e){throw new Error("Identity/Passkey registration failed",{cause:e})}var n}function extractLockKey(t){try{if(t&&t.response&&isByteArray(t.response.userID)&&t.response.userID.byteLength==d+2){let r=deriveLockKey(t.response.userID.subarray(0,d));return cacheLockKey(e,r),r}throw new Error("Passkey info missing")}catch(e){throw new Error("Chosen passkey did not provide a valid encryption/decryption key",{cause:e})}}}function resetAbortToken(e){if(m&&(cleanupExternalSignalHandler(m),m.aborted||m.abort("Passkey operation abandoned.")),m=new AbortController,null!=e)if(e.aborted)m.abort(e.reason);else{let handlerFn=()=>{cleanupExternalSignalHandler(m),m.abort(e.reason),m=e=handlerFn=null};e.addEventListener("abort",handlerFn),K.set(m,[e,handlerFn])}}function cleanupExternalSignalHandler(e){if(null!=e&&K.has(e)){let[t,r]=K.get(e);t.removeEventListener("abort",r),K.delete(e)}}function generateEntropy(e=16){return sodium.randombytes_buf(e)}function deriveLockKey(e=generateEntropy(d)){try{let t=sodium.crypto_sign_seed_keypair(e);return{keyFormatVersion:f,iv:e,publicKey:t.publicKey,privateKey:t.privateKey,encPK:sodium.crypto_sign_ed25519_pk_to_curve25519(t.publicKey),encSK:sodium.crypto_sign_ed25519_sk_to_curve25519(t.privateKey)}}catch(e){throw new Error("Encryption/decryption key derivation failed.",{cause:e})}}function checkLockKey(e){if(e&&"object"==typeof e){if(e.keyFormatVersion===f)return e;if(isByteArray(e.iv)&&e.iv.byteLength==d)return deriveLockKey(e.iv)}throw new Error("Unrecongnized lock-key")}function lockData(e,t,{outputFormat:r="base64"}={}){try{let a=dataToBuffer(e),n=sodium.crypto_box_seal(a,t.encPK);return["base64","base-64"].includes(r.toLowerCase())?c(n):n}catch(e){throw new Error("Data encryption failed.",{cause:e})}}function unlockData(e,t,{outputFormat:r="utf8",parseJSON:a=!0}={}){try{let n=sodium.crypto_box_seal_open("string"==typeof e?l(e):e,t.encPK,t.encSK);if(["utf8","utf-8"].includes(r.toLowerCase())){let e=y(n);return a?JSON.parse(e):e}return n}catch(e){throw new Error("Data decryption failed.",{cause:e})}}function signData(e,{privateKey:t}={},{outputFormat:r="base64"}={}){try{let a=sodium.crypto_sign_detached(dataToBuffer(e),t);return["base64","base-64"].includes(r.toLowerCase())?c(a):a}catch(e){throw new Error("Data signature failed.",{cause:e})}}function verifySignature(e,{publicKey:t}={},r){try{return sodium.crypto_sign_verify_detached("string"==typeof r?l(r):r,dataToBuffer(e),t)}catch(e){throw new Error("Data signature failed.",{cause:e})}}async function storeLocalIdentities(){await checkStorage();var e=Object.fromEntries(Object.entries(h).map((([e,t])=>[e,{...t,passkeys:t.passkeys.map((e=>({...e,publicKey:i(e.publicKey)})))}])));Object.keys(e).length>0?await b.set("local-identities",e):await b.remove("local-identities")}function setLockKeyCacheLifetime(e){return g=Math.max(0,Number(e)||0)}function configure({accountStorage:e,cacheLifetime:t}={}){null!=e&&function configureStorage(e){if(["idb","local-storage","session-storage","cookie","opfs","opfs-worker"].includes(e))k=e,h=b=null;else{if("object"!=typeof e||"string"!=typeof e.storageType||!["has","get","set","remove","keys","entries"].every((t=>"function"==typeof e[t])))throw new Error(`Unrecognized storage type ('${storageType}')`);b=e,k=e.storageType,h=null}}(e),null!=t&&setLockKeyCacheLifetime(t)}function dataToBuffer(e){var t=null==e?null:e instanceof ArrayBuffer?new Uint8Array(e):isByteArray(e)?e:u("object"==typeof e?JSON.stringify(e):"string"==typeof e?e:String(e));if(null==t)throw new Error("Non-empty data required.");return t}function isByteArray(e){return e instanceof Uint8Array&&e.buffer instanceof ArrayBuffer}function computePasskeyEntryHash(e){let{hash:t,...r}=e;return c(sodium.crypto_hash(JSON.stringify({...r,publicKey:i(r.publicKey)})))}async function checkStorage(){if(null==b){if(!["idb","local-storage","session-storage","cookie","opfs","opfs-worker"].includes(k))throw new Error(`Unrecognized storage type ('${k}')`);b="idb"==k?await import("@byojs/storage/idb"):"local-storage"==k?await import("@byojs/storage/local-storage"):"session-storage"==k?await import("@byojs/storage/session-storage"):"cookie"==k?await import("@byojs/storage/cookie"):"opfs"==k?await import("@byojs/storage/opfs"):"opfs-worker"==k?await import("@byojs/storage/opfs-worker"):null}null!=b&&null==h&&(h=await async function loadLocalIdentities(){return Object.fromEntries(Object.entries(await b.get("local-identities")||{}).filter((([e,t])=>"number"==typeof t.lastSeq&&Array.isArray(t.passkeys)&&t.passkeys.length>0&&t.passkeys.every((e=>"string"==typeof e.credentialID&&""!=e.credentialID&&"number"==typeof e.seq&&null!=e.publicKey&&"object"==typeof e.publicKey&&"number"==typeof e.publicKey.algoCOSE&&"string"==typeof e.publicKey.raw&&""!=e.publicKey.raw&&"string"==typeof e.publicKey.spki&&""!=e.publicKey.spki&&"string"==typeof e.hash&&""!=e.hash&&e.hash==computePasskeyEntryHash(e))))).map((([e,t])=>[e,{...t,passkeys:t.passkeys.map((e=>({...e,publicKey:s(e.publicKey)})))}])))}())} | ||
import{supportsWebAuthn as e,regDefaults as t,register as r,authDefaults as a,auth as n,verifyAuthResponse as i,packPublicKeyJSON as o,unpackPublicKeyJSON as s,toBase64String as c,fromBase64String as l,toUTF8String as u,fromUTF8String as y,resetAbortReason as p}from"@lo-fi/webauthn-local-client";const f=1,d=sodium.crypto_sign_SEEDBYTES;var g=null,k=null,h={},b=null,m=new WeakMap,w=null,K=setLockKeyCacheLifetime(18e5),L="idb";export{e as supportsWebAuthn,o as packPublicKeyJSON,s as unpackPublicKeyJSON,c as toBase64String,l as fromBase64String,u as toUTF8String,y as fromUTF8String,p as resetAbortReason,listLocalIdentities,clearLockKeyCache,removeLocalAccount,getLockKey,generateEntropy,deriveLockKey,lockData,unlockData,signData,verifySignature,configure};var v={supportsWebAuthn:e,packPublicKeyJSON:o,unpackPublicKeyJSON:s,toBase64String:c,fromBase64String:l,toUTF8String:u,fromUTF8String:y,resetAbortReason:p,listLocalIdentities:listLocalIdentities,clearLockKeyCache:clearLockKeyCache,removeLocalAccount:removeLocalAccount,getLockKey:getLockKey,generateEntropy:generateEntropy,deriveLockKey:deriveLockKey,lockData:lockData,unlockData:unlockData,signData:signData,verifySignature:verifySignature,configure:configure};export default v;async function listLocalIdentities(){return await checkStorage(),Object.keys(k)}function cacheLockKey(e,t,r=!1){e in h&&!r||(h[e]={...t,timestamp:Date.now()},resetCachePurgeTimer())}function clearLockKeyCache(e){null!=e?delete h[e]:h={},resetCachePurgeTimer()}function resetCachePurgeTimer(){null!=w&&(clearTimeout(w),w=null),function setCachePurgeTimer(){if(null==w){let e=function nextCacheTimestamp(){return(Object.values(h).map((e=>e.timestamp))||[])[0]}();if(null!=e){let t=Math.max(6e4,Math.max(e+K-Date.now(),0)+Math.round(1e4*Math.random()));w=setTimeout(purgeExpiredCacheEntries,t)}}}()}function purgeExpiredCacheEntries(){w=null;var e=Date.now(),t=e-Math.min(K,e);Object.entries(h).filter((([e,r])=>r.timestamp<t)).forEach((([e])=>{delete h[e]})),resetCachePurgeTimer()}async function removeLocalAccount(e){return await checkStorage(),delete h[e],delete k[e],storeLocalIdentities()}async function getLockKey({localIdentity:e=c(generateEntropy(15)),username:o="local-user",displayName:s="Local User",relyingPartyID:l=document.location.hostname,relyingPartyName:u="Local Data Lock",addNewPasskey:y=!1,resetLockKey:p=!1,useLockKey:f=null,verify:g=!0,signal:m}={}){await checkStorage();var w=null!=e?k[e]:null;if(null!=w){let t=function getCachedLockKey(e){var t=Date.now();if(e in h&&h[e].timestamp>=t-Math.min(K,t)){let{timestamp:t,...r}=h[e];return r}}(e);if(null!=t&&!p){if(y){resetAbortToken(m);let{record:e}=await registerLocalIdentity(t)||{};cleanupExternalSignalHandler(b),b=null,null!=e&&(w.lastSeq=e.lastSeq,w.passkeys=[...w.passkeys,...e.passkeys],await storeLocalIdentities())}return Object.freeze({...t,localIdentity:e})}if(delete h[e],p){if(resetAbortToken(m),({record:k[e],lockKey:t}=await registerLocalIdentity(f&&"object"==typeof f?checkLockKey(f):void 0)),cleanupExternalSignalHandler(b),b=null,null==k[e])delete k[e];else if(null!=t)return await storeLocalIdentities(),cacheLockKey(e,t),Object.freeze({...t,localIdentity:e})}else{if(y)throw new Error("Encryption/Decryption key not currently cached, unavailable for new passkey");{resetAbortToken(m);let t=a({relyingPartyID:l,mediation:"optional",allowCredentials:w.passkeys.map((({credentialID:e})=>({type:"public-key",id:e}))),signal:b.signal}),r=await n(t);if(cleanupExternalSignalHandler(b),b=null,null!=r){if(g){let e=w.passkeys.find((e=>e.credentialID==r.response.credentialID)),t=e?.publicKey;if(!(null!=t&&await i(r.response,t)))throw new Error("Auth verification failed")}return{...extractLockKey(r),localIdentity:e}}}}}else if(y){resetAbortToken(m);let{record:t,lockKey:r}=await registerLocalIdentity(f&&"object"==typeof f?checkLockKey(f):void 0)||{};if(cleanupExternalSignalHandler(b),b=null,null!=t&&null!=r)return k[e]=t,cacheLockKey(e,r),await storeLocalIdentities(),Object.freeze({...r,localIdentity:e})}else{resetAbortToken(m);let t=a({relyingPartyID:l,mediation:"optional",signal:b.signal}),r=await n(t);if(cleanupExternalSignalHandler(b),b=null,null!=r){let t=extractLockKey(r),[a]=Object.entries(k).find((([,e])=>null!=e.passkeys.find((e=>e.credentialID==r.response.credentialID))))||[];if(null!=a){if(delete h[e],w=k[e=a],g){let e=w.passkeys.find((e=>e.credentialID==r.response.credentialID)),t=e?.publicKey;if(!(null!=t&&await i(r.response,t)))throw new Error("Auth verification failed")}cacheLockKey(e,t)}else if(g)throw new Error("Auth verification requested but skipped, against unrecognized passkey (no matching local-identity)");return Object.freeze({...t,localIdentity:e})}}async function registerLocalIdentity(a=deriveLockKey()){try{let i=((k[e]||{}).lastSeq||0)+1,c=new Uint8Array(a.iv.byteLength+2),y=new DataView(new ArrayBuffer(2));y.setInt16(0,i,!1),c.set(a.iv,0),c.set(new Uint8Array(y.buffer),a.iv.byteLength);let p=t({relyingPartyID:l,relyingPartyName:u,user:{id:c,name:o,displayName:s},signal:b.signal}),f=await r(p);if(null!=f)return{record:{lastSeq:i,passkeys:[(n={seq:i,credentialID:f.response.credentialID,publicKey:f.response.publicKey},{...n,hash:computePasskeyEntryHash(n)})]},lockKey:a}}catch(e){throw new Error("Identity/Passkey registration failed",{cause:e})}var n}function extractLockKey(t){try{if(t&&t.response&&isByteArray(t.response.userID)&&t.response.userID.byteLength==d+2){let r=deriveLockKey(t.response.userID.subarray(0,d));return cacheLockKey(e,r),r}throw new Error("Passkey info missing")}catch(e){throw new Error("Chosen passkey did not provide a valid encryption/decryption key",{cause:e})}}}function resetAbortToken(e){if(b&&(cleanupExternalSignalHandler(b),b.aborted||b.abort("Passkey operation abandoned.")),b=new AbortController,null!=e)if(e.aborted)b.abort(e.reason);else{let handlerFn=()=>{cleanupExternalSignalHandler(b),b.abort(e.reason),b=e=handlerFn=null};e.addEventListener("abort",handlerFn),m.set(b,[e,handlerFn])}}function cleanupExternalSignalHandler(e){if(null!=e&&m.has(e)){let[t,r]=m.get(e);t.removeEventListener("abort",r),m.delete(e)}}function generateEntropy(e=16){return sodium.randombytes_buf(e)}function deriveLockKey(e=generateEntropy(d)){try{let t=sodium.crypto_sign_seed_keypair(e);return{keyFormatVersion:f,iv:e,publicKey:t.publicKey,privateKey:t.privateKey,encPK:sodium.crypto_sign_ed25519_pk_to_curve25519(t.publicKey),encSK:sodium.crypto_sign_ed25519_sk_to_curve25519(t.privateKey)}}catch(e){throw new Error("Encryption/decryption key derivation failed.",{cause:e})}}function checkLockKey(e){if(e&&"object"==typeof e){if(e.keyFormatVersion===f)return e;if(isByteArray(e.iv)&&e.iv.byteLength==d)return deriveLockKey(e.iv)}throw new Error("Unrecongnized lock-key")}function lockData(e,t,{outputFormat:r="base64"}={}){try{let a=dataToBuffer(e),n=sodium.crypto_box_seal(a,t.encPK);return["base64","base-64"].includes(r.toLowerCase())?c(n):n}catch(e){throw new Error("Data encryption failed.",{cause:e})}}function unlockData(e,t,{outputFormat:r="utf8",parseJSON:a=!0}={}){try{let n=sodium.crypto_box_seal_open("string"==typeof e?l(e):e,t.encPK,t.encSK);if(["utf8","utf-8"].includes(r.toLowerCase())){let e=u(n);return a?JSON.parse(e):e}return n}catch(e){throw new Error("Data decryption failed.",{cause:e})}}function signData(e,{privateKey:t}={},{outputFormat:r="base64"}={}){try{let a=sodium.crypto_sign_detached(dataToBuffer(e),t);return["base64","base-64"].includes(r.toLowerCase())?c(a):a}catch(e){throw new Error("Data signature failed.",{cause:e})}}function verifySignature(e,{publicKey:t}={},r){try{return sodium.crypto_sign_verify_detached("string"==typeof r?l(r):r,dataToBuffer(e),t)}catch(e){throw new Error("Data signature failed.",{cause:e})}}async function storeLocalIdentities(){await checkStorage();var e=Object.fromEntries(Object.entries(k).map((([e,t])=>[e,{...t,passkeys:t.passkeys.map((e=>({...e,publicKey:o(e.publicKey)})))}])));Object.keys(e).length>0?await g.set("local-identities",e):await g.remove("local-identities")}function setLockKeyCacheLifetime(e){try{return K=Math.max(0,Number(e)||0)}finally{resetCachePurgeTimer()}}function configure({accountStorage:e,cacheLifetime:t}={}){null!=e&&function configureStorage(e){if(["idb","local-storage","session-storage","cookie","opfs","opfs-worker"].includes(e))L=e,k=g=null;else{if("object"!=typeof e||"string"!=typeof e.storageType||!["has","get","set","remove","keys","entries"].every((t=>"function"==typeof e[t])))throw new Error(`Unrecognized storage type ('${storageType}')`);g=e,L=e.storageType,k=null}}(e),null!=t&&setLockKeyCacheLifetime(t)}function dataToBuffer(e){var t=null==e?null:e instanceof ArrayBuffer?new Uint8Array(e):isByteArray(e)?e:y("object"==typeof e?JSON.stringify(e):"string"==typeof e?e:String(e));if(null==t)throw new Error("Non-empty data required.");return t}function isByteArray(e){return e instanceof Uint8Array&&e.buffer instanceof ArrayBuffer}function computePasskeyEntryHash(e){let{hash:t,...r}=e;return c(sodium.crypto_hash(JSON.stringify({...r,publicKey:o(r.publicKey)})))}async function checkStorage(){if(null==g){if(!["idb","local-storage","session-storage","cookie","opfs","opfs-worker"].includes(L))throw new Error(`Unrecognized storage type ('${L}')`);g="idb"==L?await import("@byojs/storage/idb"):"local-storage"==L?await import("@byojs/storage/local-storage"):"session-storage"==L?await import("@byojs/storage/session-storage"):"cookie"==L?await import("@byojs/storage/cookie"):"opfs"==L?await import("@byojs/storage/opfs"):"opfs-worker"==L?await import("@byojs/storage/opfs-worker"):null}null!=g&&null==k&&(k=await async function loadLocalIdentities(){return Object.fromEntries(Object.entries(await g.get("local-identities")||{}).filter((([e,t])=>"number"==typeof t.lastSeq&&Array.isArray(t.passkeys)&&t.passkeys.length>0&&t.passkeys.every((e=>"string"==typeof e.credentialID&&""!=e.credentialID&&"number"==typeof e.seq&&null!=e.publicKey&&"object"==typeof e.publicKey&&"number"==typeof e.publicKey.algoCOSE&&"string"==typeof e.publicKey.raw&&""!=e.publicKey.raw&&"string"==typeof e.publicKey.spki&&""!=e.publicKey.spki&&"string"==typeof e.hash&&""!=e.hash&&e.hash==computePasskeyEntryHash(e))))).map((([e,t])=>[e,{...t,passkeys:t.passkeys.map((e=>({...e,publicKey:s(e.publicKey)})))}])))}())} |
{ | ||
"name": "@lo-fi/local-data-lock", | ||
"description": "Protect local-first app data with encryption/decryption key secured in WebAuthn (biometric) passkeys", | ||
"version": "0.14.2", | ||
"version": "0.14.3", | ||
"exports": { | ||
@@ -6,0 +6,0 @@ ".": "./dist/bundlers/ldl.mjs", |
@@ -22,4 +22,2 @@ import { | ||
const IV_BYTE_LENGTH = sodium.crypto_sign_SEEDBYTES; | ||
var LOCK_KEY_CACHE_LIFETIME = setLockKeyCacheLifetime(30 * 60 * 1000); // 30 min (default) | ||
var DEFAULT_STORAGE_TYPE = "idb"; | ||
var store = null; | ||
@@ -30,2 +28,5 @@ var localIdentities = null; | ||
var externalSignalCache = new WeakMap(); | ||
var cachePurgeIntv = null; | ||
var LOCK_KEY_CACHE_LIFETIME = setLockKeyCacheLifetime(30 * 60 * 1000); // 30 min (default) | ||
var DEFAULT_STORAGE_TYPE = "idb"; | ||
@@ -95,2 +96,3 @@ | ||
var now = Date.now(); | ||
if ( | ||
@@ -105,3 +107,3 @@ // lock-key currently in cache? | ||
) { | ||
// discard cache-internal timestamp field | ||
// hide cache-internal timestamp field | ||
let { timestamp, ...lockKey } = lockKeyCache[localID]; | ||
@@ -121,2 +123,3 @@ return lockKey; | ||
}; | ||
resetCachePurgeTimer(); | ||
} | ||
@@ -127,3 +130,3 @@ } | ||
if (localID != null) { | ||
delete lockKeyCache[localID] | ||
delete lockKeyCache[localID]; | ||
} | ||
@@ -133,4 +136,51 @@ else { | ||
} | ||
resetCachePurgeTimer(); | ||
} | ||
function resetCachePurgeTimer() { | ||
if (cachePurgeIntv != null) { | ||
clearTimeout(cachePurgeIntv); | ||
cachePurgeIntv = null; | ||
} | ||
setCachePurgeTimer(); | ||
} | ||
function setCachePurgeTimer() { | ||
if (cachePurgeIntv == null) { | ||
let nextTimestamp = nextCacheTimestamp(); | ||
if (nextTimestamp != null) { | ||
let when = Math.max( | ||
// at least 1 minute | ||
60_000, | ||
( | ||
// time left until expiration (if any) | ||
Math.max(nextTimestamp + LOCK_KEY_CACHE_LIFETIME - Date.now(),0) + | ||
// up to 10 more seconds | ||
Math.round(Math.random() * 1E4) | ||
) | ||
); | ||
cachePurgeIntv = setTimeout(purgeExpiredCacheEntries,when); | ||
} | ||
} | ||
} | ||
function nextCacheTimestamp() { | ||
return (( | ||
Object.values(lockKeyCache).map(entry => entry.timestamp) | ||
) || [])[0]; | ||
} | ||
function purgeExpiredCacheEntries() { | ||
cachePurgeIntv = null; | ||
var now = Date.now(); | ||
var when = now - Math.min(LOCK_KEY_CACHE_LIFETIME,now); | ||
Object.entries(lockKeyCache) | ||
.filter(([ localID, entry, ]) => entry.timestamp < when) | ||
.forEach(([ localID, ]) => { delete lockKeyCache[localID]; }); | ||
resetCachePurgeTimer(); | ||
} | ||
async function removeLocalAccount(localID) { | ||
@@ -697,3 +747,8 @@ await checkStorage(); | ||
function setLockKeyCacheLifetime(ms) { | ||
return (LOCK_KEY_CACHE_LIFETIME = Math.max(0,Number(ms) || 0)); | ||
try { | ||
return (LOCK_KEY_CACHE_LIFETIME = Math.max(0,Number(ms) || 0)); | ||
} | ||
finally { | ||
resetCachePurgeTimer(); | ||
} | ||
} | ||
@@ -700,0 +755,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
898310
5251