@lo-fi/local-vault
Advanced tools
Comparing version 0.10.0 to 0.11.0
/*! Local-Vault: adapter.cookie.js | ||
v0.10.0 (c) 2024 Kyle Simpson | ||
v0.11.0 (c) 2024 Kyle Simpson | ||
MIT License: http://getify.mit-license.org | ||
@@ -4,0 +4,0 @@ */ |
/*! Local-Vault: adapter.idb.js | ||
v0.10.0 (c) 2024 Kyle Simpson | ||
v0.11.0 (c) 2024 Kyle Simpson | ||
MIT License: http://getify.mit-license.org | ||
@@ -4,0 +4,0 @@ */ |
/*! Local-Vault: adapter.local-storage.js | ||
v0.10.0 (c) 2024 Kyle Simpson | ||
v0.11.0 (c) 2024 Kyle Simpson | ||
MIT License: http://getify.mit-license.org | ||
@@ -4,0 +4,0 @@ */ |
/*! Local-Vault: adapter.opfs.js | ||
v0.10.0 (c) 2024 Kyle Simpson | ||
v0.11.0 (c) 2024 Kyle Simpson | ||
MIT License: http://getify.mit-license.org | ||
@@ -4,0 +4,0 @@ */ |
/*! Local-Vault: adapter.session-storage.js | ||
v0.10.0 (c) 2024 Kyle Simpson | ||
v0.11.0 (c) 2024 Kyle Simpson | ||
MIT License: http://getify.mit-license.org | ||
@@ -4,0 +4,0 @@ */ |
/*! Local-Data-Lock: ldl.js | ||
v0.10.0 (c) 2024 Kyle Simpson | ||
v0.11.0 (c) 2024 Kyle Simpson | ||
MIT License: http://getify.mit-license.org | ||
*/ | ||
import{supportsWebAuthn as e,regDefaults as t,register as r,authDefaults as n,auth as o,verifyAuthResponse as i,packPublicKeyJSON as a,unpackPublicKeyJSON as c,toBase64String as s,fromBase64String as l,toUTF8String as y,fromUTF8String as u,resetAbortReason as p}from"@lo-fi/webauthn-local-client";const d=1,f=sodium.crypto_sign_SEEDBYTES;var k=setMaxLockKeyCacheLifetime(),K=function loadLocalIdentities(){return Object.fromEntries(Object.entries(JSON.parse(window.localStorage.getItem("local-identities")||null)||{}).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:c(e.publicKey)})))}])))}(),b={},h=null;export{e as supportsWebAuthn,a as packPublicKeyJSON,c as unpackPublicKeyJSON,s as toBase64String,l as fromBase64String,y as toUTF8String,u as fromUTF8String,listLocalIdentities,clearLockKeyCache,removeLocalAccount,getLockKey,generateEntropy,deriveLockKey,lockData,unlockData,setMaxLockKeyCacheLifetime};var L={supportsWebAuthn:e,packPublicKeyJSON:a,unpackPublicKeyJSON:c,toBase64String:s,fromBase64String:l,toUTF8String:y,fromUTF8String:u,listLocalIdentities:listLocalIdentities,clearLockKeyCache:clearLockKeyCache,removeLocalAccount:removeLocalAccount,getLockKey:getLockKey,generateEntropy:generateEntropy,deriveLockKey:deriveLockKey,lockData:lockData,unlockData:unlockData,setMaxLockKeyCacheLifetime:setMaxLockKeyCacheLifetime};export default L;function listLocalIdentities(){return Object.keys(K)}function cacheLockKey(e,t,r=!1){e in b&&!r||(b[e]={...t,timestamp:Date.now()})}function clearLockKeyCache(e){null!=e?delete b[e]:b={}}function removeLocalAccount(e){delete b[e],delete K[e],storeLocalIdentities()}async function getLockKey({localIdentity:e=s(generateEntropy(15)),username:a="local-user",displayName:c="Local User",relyingPartyID:l=document.location.hostname,relyingPartyName:y="Local Data Lock",addNewPasskey:u=!1,resetLockKey:p=!1,useLockKey:d=null,verify:L=!0}={}){var m=null!=e?K[e]:null;if(null!=m){let t=function getCachedLockKey(e){if(e in b&&b[e].timestamp>=Date.now()-k){let{timestamp:t,...r}=b[e];return r}}(e);if(null==t||p){if(delete b[e],p)return resetAbortToken(),({record:K[e],lockKey:t}=await registerLocalIdentity(d&&"object"==typeof d?checkLockKey(d):void 0)),storeLocalIdentities(),cacheLockKey(e,t),{...t,localIdentity:e};if(u)throw new Error("Encryption/Decryption key not currently cached, unavailable for new passkey");{resetAbortToken();let t=n({relyingPartyID:l,mediation:"optional",allowCredentials:m.passkeys.map((({credentialID:e})=>({type:"public-key",id:e}))),signal:h.signal}),r=await o(t);if(L){let e=m.passkeys.find((e=>e.credentialID==r.response.credentialID)),t=null!=e?e.publicKey:null;if(!(null!=t&&await i(r.response,t)))throw new Error("Auth verification failed")}return{...extractLockKey(r),localIdentity:e}}}if(u){resetAbortToken();let{record:e}=await registerLocalIdentity(t);m.lastSeq=e.lastSeq,m.passkeys=[...m.passkeys,...e.passkeys],storeLocalIdentities()}return{...t,localIdentity:e}}if(u){resetAbortToken();let{record:t,lockKey:r}=await registerLocalIdentity(d&&"object"==typeof d?checkLockKey(d):void 0);return K[e]=t,cacheLockKey(e,r),storeLocalIdentities(),{...r,localIdentity:e}}{resetAbortToken();let t=n({relyingPartyID:l,mediation:"optional",signal:h.signal}),r=await o(t),a=extractLockKey(r),[c]=Object.entries(K).find((([,e])=>null!=e.passkeys.find((e=>e.credentialID==r.response.credentialID))))||[];if(null!=c){if(delete b[e],m=K[e=c],L){let e=m.passkeys.find((e=>e.credentialID==r.response.credentialID)),t=null!=e?e.publicKey:null;if(!(null!=t&&await i(r.response,t)))throw new Error("Auth verification failed")}cacheLockKey(e,a)}else if(L)throw new Error("Auth verification requested but skipped, against unrecognized passkey (no matching local-identity)");return{...a,localIdentity:e}}async function registerLocalIdentity(n=deriveLockKey()){try{let i=((K[e]||{}).lastSeq||0)+1,s=new Uint8Array(n.iv.byteLength+2),u=new DataView(new ArrayBuffer(2));u.setInt16(0,i,!1),s.set(n.iv,0),s.set(new Uint8Array(u.buffer),n.iv.byteLength);let p=t({relyingPartyID:l,relyingPartyName:y,user:{id:s,name:a,displayName:c},signal:h.signal}),d=await r(p);return{record:{lastSeq:i,passkeys:[(o={seq:i,credentialID:d.response.credentialID,publicKey:d.response.publicKey},{...o,hash:computePasskeyEntryHash(o)})]},lockKey:n}}catch(e){throw new Error("Identity/Passkey registration failed",{cause:e})}var o}function extractLockKey(t){try{if(t&&t.response&&isByteArray(t.response.userID)&&t.response.userID.byteLength==f+2){let r=deriveLockKey(t.response.userID.subarray(0,f));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(){h&&h.abort("Passkey operation abandoned."),h=new AbortController}function generateEntropy(e=16){return sodium.randombytes_buf(e)}function deriveLockKey(e=generateEntropy(f)){try{let t=sodium.crypto_sign_seed_keypair(e);return{keyFormatVersion:d,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===d)return e;if(isByteArray(e.iv)&&e.iv.byteLength==f)return deriveLockKey(e.iv)}throw new Error("Unrecongnized lock-key")}function lockData(e,t,{outputFormat:r="base64"}={}){try{let n=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==e)throw new Error("Non-empty data required.");let o=sodium.crypto_box_seal(n,t.encPK);return["base64","base-64"].includes(r.toLowerCase())?s(o):o}catch(e){throw new Error("Data encryption failed.",{cause:e})}}function unlockData(e,t,{outputFormat:r="utf8",parseJSON:n=!0}={}){try{let o=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(o);return n?JSON.parse(e):e}return o}catch(e){throw new Error("Data decryption failed.",{cause:e})}}function storeLocalIdentities(){var e=Object.fromEntries(Object.entries(K).map((([e,t])=>[e,{...t,passkeys:t.passkeys.map((e=>({...e,publicKey:a(e.publicKey)})))}])));Object.keys(e).length>0?window.localStorage.setItem("local-identities",JSON.stringify(e)):window.localStorage.removeItem("local-identities")}function setMaxLockKeyCacheLifetime(e=18e5){return k=Math.max(0,Number(e)||0)}function isByteArray(e){return e instanceof Uint8Array&&e.buffer instanceof ArrayBuffer}function computePasskeyEntryHash(e){let{hash:t,...r}=e;return s(sodium.crypto_hash(JSON.stringify({...r,publicKey:a(r.publicKey)})))} | ||
import{supportsWebAuthn as e,regDefaults as t,register as r,authDefaults as n,auth as a,verifyAuthResponse as i,packPublicKeyJSON as o,unpackPublicKeyJSON as l,toBase64String as c,fromBase64String as s,toUTF8String as y,fromUTF8String as u,resetAbortReason as p}from"@lo-fi/webauthn-local-client";const d=1,f=sodium.crypto_sign_SEEDBYTES;var k=setMaxLockKeyCacheLifetime(),b=function loadLocalIdentities(){return Object.fromEntries(Object.entries(JSON.parse(window.localStorage.getItem("local-identities")||null)||{}).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:l(e.publicKey)})))}])))}(),g={},L=null,h=new WeakMap;export{e as supportsWebAuthn,o as packPublicKeyJSON,l as unpackPublicKeyJSON,c as toBase64String,s as fromBase64String,y as toUTF8String,u as fromUTF8String,p as resetAbortReason,listLocalIdentities,clearLockKeyCache,removeLocalAccount,getLockKey,generateEntropy,deriveLockKey,lockData,unlockData,setMaxLockKeyCacheLifetime};var K={supportsWebAuthn:e,packPublicKeyJSON:o,unpackPublicKeyJSON:l,toBase64String:c,fromBase64String:s,toUTF8String:y,fromUTF8String:u,resetAbortReason:p,listLocalIdentities:listLocalIdentities,clearLockKeyCache:clearLockKeyCache,removeLocalAccount:removeLocalAccount,getLockKey:getLockKey,generateEntropy:generateEntropy,deriveLockKey:deriveLockKey,lockData:lockData,unlockData:unlockData,setMaxLockKeyCacheLifetime:setMaxLockKeyCacheLifetime};export default K;function listLocalIdentities(){return Object.keys(b)}function cacheLockKey(e,t,r=!1){e in g&&!r||(g[e]={...t,timestamp:Date.now()})}function clearLockKeyCache(e){null!=e?delete g[e]:g={}}function removeLocalAccount(e){delete g[e],delete b[e],storeLocalIdentities()}async function getLockKey({localIdentity:e=c(generateEntropy(15)),username:o="local-user",displayName:l="Local User",relyingPartyID:s=document.location.hostname,relyingPartyName:y="Local Data Lock",addNewPasskey:u=!1,resetLockKey:p=!1,useLockKey:d=null,verify:h=!0,signal:K}={}){var m=null!=e?b[e]:null;if(null!=m){let t=function getCachedLockKey(e){if(e in g&&g[e].timestamp>=Date.now()-k){let{timestamp:t,...r}=g[e];return r}}(e);if(null!=t&&!p){if(u){resetAbortToken(K);let{record:e}=await registerLocalIdentity(t)||{};cleanupExternalSignalHandler(L),L=null,null!=e&&(m.lastSeq=e.lastSeq,m.passkeys=[...m.passkeys,...e.passkeys],storeLocalIdentities())}return Object.freeze({...t,localIdentity:e})}if(delete g[e],p){if(resetAbortToken(K),({record:b[e],lockKey:t}=await registerLocalIdentity(d&&"object"==typeof d?checkLockKey(d):void 0)),cleanupExternalSignalHandler(L),L=null,null==b[e])delete b[e];else if(null!=t)return 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(K);let t=n({relyingPartyID:s,mediation:"optional",allowCredentials:m.passkeys.map((({credentialID:e})=>({type:"public-key",id:e}))),signal:L.signal}),r=await a(t);if(cleanupExternalSignalHandler(L),L=null,null!=r){if(h){let e=m.passkeys.find((e=>e.credentialID==r.response.credentialID)),t=null!=e?e.publicKey:null;if(!(null!=t&&await i(r.response,t)))throw new Error("Auth verification failed")}return{...extractLockKey(r),localIdentity:e}}}}}else if(u){resetAbortToken(K);let{record:t,lockKey:r}=await registerLocalIdentity(d&&"object"==typeof d?checkLockKey(d):void 0)||{};if(cleanupExternalSignalHandler(L),L=null,null!=t&&null!=r)return b[e]=t,cacheLockKey(e,r),storeLocalIdentities(),Object.freeze({...r,localIdentity:e})}else{resetAbortToken(K);let t=n({relyingPartyID:s,mediation:"optional",signal:L.signal}),r=await a(t);if(cleanupExternalSignalHandler(L),L=null,null!=r){let t=extractLockKey(r),[n]=Object.entries(b).find((([,e])=>null!=e.passkeys.find((e=>e.credentialID==r.response.credentialID))))||[];if(null!=n){if(delete g[e],m=b[e=n],h){let e=m.passkeys.find((e=>e.credentialID==r.response.credentialID)),t=null!=e?e.publicKey:null;if(!(null!=t&&await i(r.response,t)))throw new Error("Auth verification failed")}cacheLockKey(e,t)}else if(h)throw new Error("Auth verification requested but skipped, against unrecognized passkey (no matching local-identity)");return Object.freeze({...t,localIdentity:e})}}async function registerLocalIdentity(n=deriveLockKey()){try{let i=((b[e]||{}).lastSeq||0)+1,c=new Uint8Array(n.iv.byteLength+2),u=new DataView(new ArrayBuffer(2));u.setInt16(0,i,!1),c.set(n.iv,0),c.set(new Uint8Array(u.buffer),n.iv.byteLength);let p=t({relyingPartyID:s,relyingPartyName:y,user:{id:c,name:o,displayName:l},signal:L.signal}),d=await r(p);if(null!=d)return{record:{lastSeq:i,passkeys:[(a={seq:i,credentialID:d.response.credentialID,publicKey:d.response.publicKey},{...a,hash:computePasskeyEntryHash(a)})]},lockKey:n}}catch(e){throw new Error("Identity/Passkey registration failed",{cause:e})}var a}function extractLockKey(t){try{if(t&&t.response&&isByteArray(t.response.userID)&&t.response.userID.byteLength==f+2){let r=deriveLockKey(t.response.userID.subarray(0,f));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(L&&(cleanupExternalSignalHandler(L),L.aborted||L.abort("Passkey operation abandoned.")),L=new AbortController,null!=e)if(e.aborted)L.abort(e.reason);else{let handlerFn=()=>{cleanupExternalSignalHandler(L),L.abort(e.reason),L=e=handlerFn=null};e.addEventListener("abort",handlerFn),h.set(L,[e,handlerFn])}}function cleanupExternalSignalHandler(e){if(null!=e&&h.has(e)){let[t,r]=h.get(e);t.removeEventListener("abort",r),h.delete(e)}}function generateEntropy(e=16){return sodium.randombytes_buf(e)}function deriveLockKey(e=generateEntropy(f)){try{let t=sodium.crypto_sign_seed_keypair(e);return{keyFormatVersion:d,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===d)return e;if(isByteArray(e.iv)&&e.iv.byteLength==f)return deriveLockKey(e.iv)}throw new Error("Unrecongnized lock-key")}function lockData(e,t,{outputFormat:r="base64"}={}){try{let n=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==e)throw new Error("Non-empty data required.");let a=sodium.crypto_box_seal(n,t.encPK);return["base64","base-64"].includes(r.toLowerCase())?c(a):a}catch(e){throw new Error("Data encryption failed.",{cause:e})}}function unlockData(e,t,{outputFormat:r="utf8",parseJSON:n=!0}={}){try{let a=sodium.crypto_box_seal_open("string"==typeof e?s(e):e,t.encPK,t.encSK);if(["utf8","utf-8"].includes(r.toLowerCase())){let e=y(a);return n?JSON.parse(e):e}return a}catch(e){throw new Error("Data decryption failed.",{cause:e})}}function storeLocalIdentities(){var e=Object.fromEntries(Object.entries(b).map((([e,t])=>[e,{...t,passkeys:t.passkeys.map((e=>({...e,publicKey:o(e.publicKey)})))}])));Object.keys(e).length>0?window.localStorage.setItem("local-identities",JSON.stringify(e)):window.localStorage.removeItem("local-identities")}function setMaxLockKeyCacheLifetime(e=18e5){return k=Math.max(0,Number(e)||0)}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)})))} |
/*! Local-Vault: lv.js | ||
v0.10.0 (c) 2024 Kyle Simpson | ||
v0.11.0 (c) 2024 Kyle Simpson | ||
MIT License: http://getify.mit-license.org | ||
*/ | ||
import{supportsWebAuthn as t,toBase64String as e,fromBase64String as a,getLockKey as r,lockData as n,unlockData as l,generateEntropy as o,listLocalIdentities as i,removeLocalAccount as c,clearLockKeyCache as u}from"@lo-fi/local-data-lock";var y=null,s={},d={},g=new WeakMap;export{t as supportsWebAuthn,i as listLocalIdentities,c as removeLocalAccount,e as toBase64String,a as fromBase64String,defineAdapter,connect,removeAll,keepStorage};var p={supportsWebAuthn:t,listLocalIdentities:i,removeLocalAccount:c,toBase64String:e,fromBase64String:a,defineAdapter:defineAdapter,connect:connect,removeAll:removeAll,keepStorage:keepStorage};export default p;function defineAdapter({storageType:t="unknown",read:e,write:a,find:r,clear:n}){if(t in s)throw new Error(`Storage type ('${t}') already defined`);s[t]={read:e,write:a,find:r,clear:n}}async function connect({storageType:t,vaultID:a,keyOptions:{relyingPartyID:l=document.location.hostname,relyingPartyName:c="Local Vault",localIdentity:u,...y}={},addNewVault:p=!1,discoverVault:f=!1}){if(t in s){if(f){let e=i(),a=await r({...y,relyingPartyID:l,relyingPartyName:c});if(e.includes(a.localIdentity)){let[e]=await s[t].find({accountID:a.localIdentity});if(null!=e)return connect({storageType:t,vaultID:e,keyOptions:{relyingPartyID:l,relyingPartyName:c,...y}})}throw new Error(`No matching vault found in storage ('${t}') for presented passkey`)}if(null!=a||p){if(p&&null==a&&(a=e(o(12)).replace(/[^a-zA-Z0-9]+/g,"")),!(a in d)){d[a]={has:has,get:get,set:set,remove:remove,clear:clear,lock:lock,addPasskey:addPasskey,resetLockKey:resetLockKey,keys:keys,entries:entries,__exportLockKey:__exportLockKey};for(let[t,e]of Object.entries(d[a]))d[a][t]=e.bind(d[a]);Object.defineProperties(d[a],{storageType:{value:t,writable:!1,configurable:!1,enumerable:!0},id:{value:a,writable:!1,configurable:!1,enumerable:!0}})}let i=await getVaultEntry(t,a),f=p||null!=i.accountID?await r({...y,relyingPartyID:null!=i.rpID?i.rpID:l,relyingPartyName:c,...p?{addNewPasskey:!0,localIdentity:u}:{localIdentity:i.accountID}}):null;if(null!=f&&null!=f.localIdentity)return unlockVaultEntry(i,f),i.accountID==f.localIdentity&&i.rpID==l||(i.accountID=f.localIdentity,i.rpID=l,await s[t].write(a,i,n(i.data,f))),d[a];throw g.delete(d[a]),delete d[a],new Error("Vault lock-key access failed")}throw new Error("Required vault ID missing")}throw new Error(`Unknown storage type ('${t}')`)}async function removeAll(){for(let[t,e]of Object.entries(s))try{await e.clear()}catch(t){}return!0}async function has(t){var{vaultEntry:e}=await openVault(this);return t in e.data}async function get(t){var{vaultEntry:e}=await openVault(this);return e.data[t]}async function set(t,e){if(void 0===e)return remove(t);var{storageType:a,vaultID:r,vaultEntry:l,vaultLockKey:o}=await openVault(this);return l.data[t]=e,await s[a].write(r,l,n(l.data,o)),!0}async function remove(t){var{storageType:e,vaultID:a,vaultEntry:r,vaultLockKey:l}=await openVault(this);return delete r.data[t],await s[e].write(a,r,n(r.data,l)),!0}async function clear(){var{storageType:t,vaultID:e}=await openVault(this);return await s[t].clear(e),g.delete(d[e]),!0}function lock(){var t=this;if(null!=t&&"string"==typeof t.storageType&&"string"==typeof t.id&&g.has(t)){let e=g.get(t);return g.delete(t),u(e.accountID),!0}throw new Error("Not a currently unlocked vault")}async function addPasskey({localIdentity:t,relyingPartyID:e=document.location.hostname,relyingPartyName:a="Local Vault",...n}={}){var{vaultEntry:l}=await openVault(this);try{return await r({...n,localIdentity:l.accountID,relyingPartyID:null!=l.rpID?l.rpID:e,relyingPartyName:a,addNewPasskey:!0}),!0}catch(t){throw new Error("Adding passkey to vault's local-account failed",{cause:t})}}async function resetLockKey({localIdentity:t,relyingPartyID:e=document.location.hostname,relyingPartyName:a="Local Vault",...l}={}){var{storageType:o,vaultID:i,vaultEntry:c}=await openVault(this);try{let t=await r({...l,localIdentity:c.accountID,relyingPartyID:null!=c.rpID?c.rpID:e,relyingPartyName:a,resetLockKey:!0});return await s[o].write(i,c,n(c.data,t)),!0}catch(t){throw new Error("Resetting vault's lock-key failed",{cause:t})}}async function keys(){var{vaultEntry:t}=await openVault(this);return Object.keys(t.data)}async function entries(){var{vaultEntry:t}=await openVault(this);return Object.entries(t.data)}async function __exportLockKey({risky:t=!1}={}){if("this is unsafe"==t){let{vaultLockKey:t}=await openVault(this);return{...t}}throw new Error('Must pass {risky:"this is unsafe"} argument, to acknowledge the risks of using this method')}async function openVault(t){if(null!=t&&"string"==typeof t.storageType&&"string"==typeof t.id){let{storageType:e,id:a}=t;if(null!=s[e]){let t=await getVaultEntry(e,a),n=await r({localIdentity:t.accountID,relyingPartyID:t.rpID});return unlockVaultEntry(t,n),{storageType:e,vaultID:a,vaultEntry:t,vaultLockKey:n}}throw new Error(`Unknown storage type ('${e}')`)}throw new Error("Unrecognized vault instance")}async function getVaultEntry(t,e){var a=g.has(d[e])?g.get(d[e]):await s[t].read(e);return g.set(d[e],a),a}function unlockVaultEntry(t,e){"string"==typeof t.data&&(t.data=t.data.length>0?l(t.data,e):{})}async function keepStorage(){if(null==y)try{if(y=await navigator.storage.persisted())return y;y=await navigator.storage.persist()}catch(t){y=!1}return y} | ||
import{supportsWebAuthn as t,toBase64String as e,fromBase64String as a,getLockKey as n,lockData as l,unlockData as r,generateEntropy as i,listLocalIdentities as o,removeLocalAccount as u,clearLockKeyCache as s}from"@lo-fi/local-data-lock";var c=null,y={},d={},g=new WeakMap;export{t as supportsWebAuthn,o as listLocalIdentities,u as removeLocalAccount,e as toBase64String,a as fromBase64String,defineAdapter,connect,removeAll,keepStorage};var f={supportsWebAuthn:t,listLocalIdentities:o,removeLocalAccount:u,toBase64String:e,fromBase64String:a,defineAdapter:defineAdapter,connect:connect,removeAll:removeAll,keepStorage:keepStorage};export default f;function defineAdapter({storageType:t="unknown",read:e,write:a,find:n,clear:l}){if(t in y)throw new Error(`Storage type ('${t}') already defined`);y[t]={read:e,write:a,find:n,clear:l}}async function connect({storageType:t,vaultID:a,keyOptions:{relyingPartyID:r=document.location.hostname,relyingPartyName:u="Local Vault",localIdentity:s,...c}={},addNewVault:f=!1,discoverVault:p=!1,signal:w}){if(t in y){if(p){let e=o(),a=await n({...c,relyingPartyID:r,relyingPartyName:u,signal:w});if(null!=a&&e.includes(a.localIdentity)){let[e]=await y[t].find({accountID:a.localIdentity});if(null!=e)return connect({storageType:t,vaultID:e,keyOptions:{relyingPartyID:r,relyingPartyName:u,...c},signal:w})}throw new Error(`No matching vault found in storage ('${t}') for presented passkey`)}if(null!=a||f){if(f&&null==a&&(a=e(i(12)).replace(/[^a-zA-Z0-9]+/g,"")),!(a in d)){d[a]={id:a,storageType:t,has:has,get:get,set:set,remove:remove,clear:clear,lock:lock,addPasskey:addPasskey,resetLockKey:resetLockKey,keys:keys,entries:entries,__exportLockKey:__exportLockKey};for(let[t,e]of Object.entries(d[a]))"function"==typeof e&&(d[a][t]=e.bind(d[a]));Object.freeze(d[a])}let o=await getVaultEntry(t,a);null!=w&&(o.externalSignal=w);let p=f||null!=o.accountID?await n({...c,relyingPartyID:null!=o.rpID?o.rpID:r,relyingPartyName:u,...f?{addNewPasskey:!0,localIdentity:s}:{localIdentity:o.accountID},signal:w}):null;if(null!=p&&null!=p.localIdentity)return unlockVaultEntry(o,p),o.accountID==p.localIdentity&&o.rpID==r||(o.accountID=p.localIdentity,o.rpID=r,await y[t].write(a,o,l(o.data,p))),d[a];throw g.delete(d[a]),delete d[a],new Error("Vault lock-key access failed")}throw new Error("Required vault ID missing")}throw new Error(`Unknown storage type ('${t}')`)}async function removeAll(){for(let t of Object.values(y))try{await t.clear()}catch(t){}return!0}async function has(t){var{vaultEntry:e}=await openVault(this);if(null!=e)return t in e.data}async function get(t,{signal:e}={}){var{vaultEntry:a}=await openVault(this);if(null!=a)return a.data[t]}async function set(t,e,{signal:a}={}){if(void 0===e)return this.remove(t,{signal:a});var{storageType:n,vaultID:r,vaultEntry:i,vaultLockKey:o}=await openVault(this,a)||{};return null!=n&&null!=r&&null!=i&&null!=o&&(i.data[t]=e,await y[n].write(r,i,l(i.data,o)),!0)}async function remove(t,{signal:e}={}){var{storageType:a,vaultID:n,vaultEntry:r,vaultLockKey:i}=await openVault(this,e)||{};return null!=a&&null!=n&&null!=r&&null!=i&&(delete r.data[t],await y[a].write(n,r,l(r.data,i)),!0)}async function clear({signal:t}={}){var{storageType:e,vaultID:a}=await openVault(this,t)||{};return null!=e&&null!=a&&(await y[e].clear(a),g.delete(d[a]),!0)}function lock(){var t=this;if(null!=t&&"string"==typeof t.storageType&&"string"==typeof t.id&&g.has(t)){let e=g.get(t);return g.delete(t),s(e.accountID),!0}throw new Error("Not a currently unlocked vault")}async function addPasskey({localIdentity:t,relyingPartyID:e=document.location.hostname,relyingPartyName:a="Local Vault",signal:l,...r}={}){var{vaultEntry:i}=await openVault(this);if(null!=i)try{return null!=await n({...r,localIdentity:i.accountID,relyingPartyID:null!=i.rpID?i.rpID:e,relyingPartyName:a,addNewPasskey:!0,signal:null!=l?l:null!=i.externalSignal?i.externalSignal:null})}catch(t){throw new Error("Adding passkey to vault's local-account failed",{cause:t})}return!1}async function resetLockKey({localIdentity:t,relyingPartyID:e=document.location.hostname,relyingPartyName:a="Local Vault",signal:r,...i}={}){var{storageType:o,vaultID:u,vaultEntry:s}=await openVault(this)||{};if(null!=o&&null!=u&&null!=s)try{let t=await n({...i,localIdentity:s.accountID,relyingPartyID:null!=s.rpID?s.rpID:e,relyingPartyName:a,resetLockKey:!0,signal:null!=r?r:null!=s.externalSignal?s.externalSignal:null});if(null!=t)return await y[o].write(u,s,l(s.data,t)),!0}catch(t){throw new Error("Resetting vault's lock-key failed",{cause:t})}return!1}async function keys({signal:t}={}){var{vaultEntry:e}=await openVault(this,t)||{};return null!=e?Object.keys(e.data):[]}async function entries({signal:t}={}){var{vaultEntry:e}=await openVault(this,t)||{};if(null!=e)return Object.entries(e.data)}async function __exportLockKey({risky:t=!1,signal:e}={}){if("this is unsafe"==t)return(await openVault(this,e)||{}).vaultLockKey;throw new Error('Must pass {risky:"this is unsafe"} argument, to acknowledge the risks of using this method')}async function openVault(t,e){if(null==t||"string"!=typeof t.storageType||"string"!=typeof t.id)throw new Error("Unrecognized vault instance");{let{storageType:a,id:l}=t;if(null==y[a])throw new Error(`Unknown storage type ('${a}')`);{let t=await getVaultEntry(a,l),r=await n({localIdentity:t.accountID,relyingPartyID:t.rpID,signal:e});if(null!=r)return unlockVaultEntry(t,r),{storageType:a,vaultID:l,vaultEntry:t,vaultLockKey:r}}}}async function getVaultEntry(t,e){var a=g.has(d[e])?g.get(d[e]):await y[t].read(e);return g.set(d[e],a),a}function unlockVaultEntry(t,e){"string"==typeof t.data&&(t.data=t.data.length>0?r(t.data,e):{})}async function keepStorage(){if(null==c)try{if(c=await navigator.storage.persisted())return c;c=await navigator.storage.persist()}catch(t){c=!1}return c} |
{ | ||
"name": "@lo-fi/local-vault", | ||
"description": "Store key-value data encrypted (biometric passkey protected), locally in the client", | ||
"version": "0.10.0", | ||
"version": "0.11.0", | ||
"exports": { | ||
@@ -34,3 +34,3 @@ ".": "./dist/bundlers/lv.mjs", | ||
"dependencies": { | ||
"@lo-fi/local-data-lock": "~0.10.0", | ||
"@lo-fi/local-data-lock": "~0.11.0", | ||
"idb-keyval": "~6.2.1" | ||
@@ -37,0 +37,0 @@ }, |
@@ -351,2 +351,27 @@ # Local Vault | ||
## Cancelling Vault Operations | ||
If a call to `connect(..)` (or any of the asynchronous vault-instance methods) requires a passkey (re)authentication, there may be a substantial delay while the user is navigating the system prompts. Calling `connect()` or any vault method, while another `connect()` or vault method is currently pending, will abort that previous call -- and should cancel any open system dialogs the user is interacting with. | ||
However, you may want to cancel a currently pending passkey authentication *without* having to call one of these methods again, for example based on a timeout if authentication is taking too long. | ||
All asynchronous vault operations -- `connect()` as well as all the vault-instance methods (except `lock()`) -- accept an optional `signal` option, an [`AbortController.signal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal) instance. If the associated `AbortController` of this signal is aborted (with `abort()`), and the operation is currently pending a passkey authentication, that authentication will be aborted. | ||
For example: | ||
```js | ||
var cancelToken = new AbortController(); | ||
var vault = await connect({ | ||
/* .. */, | ||
signal: cancelToken.signal, | ||
}); | ||
await vault.set("hello","world",{ signal: cancelToken.signal }); | ||
await vault.entries({ signal: cancelToken.signal }); | ||
``` | ||
Any abort of a pending passkey authentication will throw an exception at the point of the method call (i.e., the `await`). So if you're using cancellation to control vault operations, make sure to use appropriate exception handling techniques (`try..catch`, etc). | ||
## Re-building `dist/*` | ||
@@ -353,0 +378,0 @@ |
268
src/lv.js
@@ -84,2 +84,3 @@ import { | ||
discoverVault = false, | ||
signal: cancelConnection, | ||
}) { | ||
@@ -95,6 +96,10 @@ if (storageType in adapters) { | ||
relyingPartyName, | ||
signal: cancelConnection, | ||
}); | ||
// lock-key discovered? | ||
if (localIdentities.includes(vaultLockKey.localIdentity)) { | ||
if ( | ||
vaultLockKey != null && | ||
localIdentities.includes(vaultLockKey.localIdentity) | ||
) { | ||
// vault found that matches the local-key's account ID? | ||
@@ -113,2 +118,3 @@ let [ discoveredVaultID, ] = await adapters[storageType].find({ | ||
}, | ||
signal: cancelConnection, | ||
}); | ||
@@ -131,2 +137,4 @@ } | ||
vaults[vaultID] = { | ||
id: vaultID, | ||
storageType, | ||
has, | ||
@@ -145,21 +153,7 @@ "get": get, | ||
for (let [ name, fn ] of Object.entries(vaults[vaultID])) { | ||
vaults[vaultID][name] = fn.bind(vaults[vaultID]); | ||
if (typeof fn == "function") { | ||
vaults[vaultID][name] = fn.bind(vaults[vaultID]); | ||
} | ||
} | ||
Object.defineProperties( | ||
vaults[vaultID], | ||
{ | ||
storageType: { | ||
value: storageType, | ||
writable: false, | ||
configurable: false, | ||
enumerable: true, | ||
}, | ||
id: { | ||
value: vaultID, | ||
writable: false, | ||
configurable: false, | ||
enumerable: true, | ||
}, | ||
} | ||
); | ||
Object.freeze(vaults[vaultID]); | ||
} | ||
@@ -169,2 +163,7 @@ | ||
// save abort-signal on vault entry? | ||
if (cancelConnection != null) { | ||
vaultEntry.externalSignal = cancelConnection; | ||
} | ||
// retrieve (or create) the cryptographic lock-key | ||
@@ -185,2 +184,3 @@ // for this vault | ||
), | ||
signal: cancelConnection, | ||
}) : | ||
@@ -202,2 +202,4 @@ | ||
vaultEntry.rpID = relyingPartyID; | ||
// commit vault-entry to storage (via adapter) | ||
await adapters[storageType].write( | ||
@@ -228,3 +230,3 @@ vaultID, | ||
async function removeAll() { | ||
for (let [ storageType, adapter ] of Object.entries(adapters)) { | ||
for (let adapter of Object.values(adapters)) { | ||
try { await adapter.clear(); } catch (err) {} | ||
@@ -237,41 +239,80 @@ } | ||
var { vaultEntry, } = await openVault(this); | ||
return (name in vaultEntry.data); | ||
if (vaultEntry != null) { | ||
return (name in vaultEntry.data); | ||
} | ||
} | ||
async function get(name) { | ||
async function get(name,{ signal, } = {}) { | ||
var { vaultEntry, } = await openVault(this); | ||
return vaultEntry.data[name]; | ||
if (vaultEntry != null) { | ||
return vaultEntry.data[name]; | ||
} | ||
} | ||
async function set(name,val) { | ||
async function set(name,val,{ signal, } = {}) { | ||
// JSON drops properties with `undefined` values, so | ||
// setting a store entry with `undefined` is just | ||
// treated like a remove() | ||
if (val === undefined) { | ||
return remove(name); | ||
return this.remove(name,{ signal, }); | ||
} | ||
var { storageType, vaultID, vaultEntry, vaultLockKey, } = await openVault(this); | ||
vaultEntry.data[name] = val; | ||
await adapters[storageType].write( | ||
vaultID, | ||
vaultEntry, | ||
lockData(vaultEntry.data,vaultLockKey) | ||
); | ||
return true; | ||
var { storageType, vaultID, vaultEntry, vaultLockKey, } = ( | ||
await openVault(this,signal) | ||
) || {}; | ||
if ( | ||
storageType != null && | ||
vaultID != null && | ||
vaultEntry != null && | ||
vaultLockKey != null | ||
) { | ||
vaultEntry.data[name] = val; | ||
// commit vault-entry to storage (via adapter) | ||
await adapters[storageType].write( | ||
vaultID, | ||
vaultEntry, | ||
lockData(vaultEntry.data,vaultLockKey) | ||
); | ||
return true; | ||
} | ||
return false; | ||
} | ||
async function remove(name) { | ||
var { storageType, vaultID, vaultEntry, vaultLockKey, } = await openVault(this); | ||
delete vaultEntry.data[name]; | ||
await adapters[storageType].write( | ||
vaultID, | ||
vaultEntry, | ||
lockData(vaultEntry.data,vaultLockKey) | ||
); | ||
return true; | ||
async function remove(name,{ signal, } = {}) { | ||
var { storageType, vaultID, vaultEntry, vaultLockKey, } = ( | ||
await openVault(this,signal) | ||
) || {}; | ||
if ( | ||
storageType != null && | ||
vaultID != null && | ||
vaultEntry != null && | ||
vaultLockKey != null | ||
) { | ||
delete vaultEntry.data[name]; | ||
// commit vault-entry to storage (via adapter) | ||
await adapters[storageType].write( | ||
vaultID, | ||
vaultEntry, | ||
lockData(vaultEntry.data,vaultLockKey) | ||
); | ||
return true; | ||
} | ||
return false; | ||
} | ||
async function clear() { | ||
var { storageType, vaultID, } = await openVault(this); | ||
await adapters[storageType].clear(vaultID); | ||
vaultEntryCache.delete(vaults[vaultID]); | ||
return true; | ||
async function clear({ signal, } = {}) { | ||
var { storageType, vaultID, } = (await openVault(this,signal)) || {}; | ||
if (storageType != null && vaultID != null) { | ||
await adapters[storageType].clear(vaultID); | ||
vaultEntryCache.delete(vaults[vaultID]); | ||
return true; | ||
} | ||
return false; | ||
} | ||
@@ -301,2 +342,3 @@ | ||
relyingPartyName = "Local Vault", | ||
signal: cancelAddPasskey, | ||
...keyOptions | ||
@@ -306,17 +348,25 @@ } = {}) { | ||
try { | ||
await getLockKey({ | ||
...keyOptions, | ||
localIdentity: vaultEntry.accountID, | ||
relyingPartyID: ( | ||
vaultEntry.rpID != null ? vaultEntry.rpID : relyingPartyID | ||
), | ||
relyingPartyName, | ||
addNewPasskey: true, | ||
}); | ||
return true; | ||
if (vaultEntry != null) { | ||
try { | ||
let vaultLockKey = await getLockKey({ | ||
...keyOptions, | ||
localIdentity: vaultEntry.accountID, | ||
relyingPartyID: ( | ||
vaultEntry.rpID != null ? vaultEntry.rpID : relyingPartyID | ||
), | ||
relyingPartyName, | ||
addNewPasskey: true, | ||
signal: ( | ||
cancelAddPasskey != null ? cancelAddPasskey : | ||
vaultEntry.externalSignal != null ? vaultEntry.externalSignal : | ||
null | ||
), | ||
}); | ||
return (vaultLockKey != null); | ||
} | ||
catch (err) { | ||
throw new Error("Adding passkey to vault's local-account failed",{ cause: err, }); | ||
} | ||
} | ||
catch (err) { | ||
throw new Error("Adding passkey to vault's local-account failed",{ cause: err, }); | ||
} | ||
return false; | ||
} | ||
@@ -328,44 +378,66 @@ | ||
relyingPartyName = "Local Vault", | ||
signal: cancelResetPasskey, | ||
...keyOptions | ||
} = {}) { | ||
var { storageType, vaultID, vaultEntry, } = await openVault(this); | ||
var { storageType, vaultID, vaultEntry, } = (await openVault(this)) || {}; | ||
try { | ||
let vaultLockKey = await getLockKey({ | ||
...keyOptions, | ||
localIdentity: vaultEntry.accountID, | ||
relyingPartyID: ( | ||
vaultEntry.rpID != null ? vaultEntry.rpID : relyingPartyID | ||
), | ||
relyingPartyName, | ||
resetLockKey: true, | ||
}); | ||
if ( | ||
storageType != null && | ||
vaultID != null && | ||
vaultEntry != null | ||
) { | ||
try { | ||
let vaultLockKey = await getLockKey({ | ||
...keyOptions, | ||
localIdentity: vaultEntry.accountID, | ||
relyingPartyID: ( | ||
vaultEntry.rpID != null ? vaultEntry.rpID : relyingPartyID | ||
), | ||
relyingPartyName, | ||
resetLockKey: true, | ||
signal: ( | ||
cancelResetPasskey != null ? cancelResetPasskey : | ||
vaultEntry.externalSignal != null ? vaultEntry.externalSignal : | ||
null | ||
), | ||
}); | ||
await adapters[storageType].write( | ||
vaultID, | ||
vaultEntry, | ||
lockData(vaultEntry.data,vaultLockKey) | ||
); | ||
if (vaultLockKey != null) { | ||
// commit vault-entry to storage (via adapter) | ||
await adapters[storageType].write( | ||
vaultID, | ||
vaultEntry, | ||
lockData(vaultEntry.data,vaultLockKey) | ||
); | ||
return true; | ||
return true; | ||
} | ||
} | ||
catch (err) { | ||
throw new Error("Resetting vault's lock-key failed",{ cause: err, }); | ||
} | ||
} | ||
catch (err) { | ||
throw new Error("Resetting vault's lock-key failed",{ cause: err, }); | ||
} | ||
return false; | ||
} | ||
async function keys() { | ||
var { vaultEntry, } = await openVault(this); | ||
return Object.keys(vaultEntry.data); | ||
async function keys({ signal, } = {}) { | ||
var { vaultEntry, } = (await openVault(this,signal)) || {}; | ||
if (vaultEntry != null) { | ||
return Object.keys(vaultEntry.data); | ||
} | ||
return []; | ||
} | ||
async function entries() { | ||
var { vaultEntry, } = await openVault(this); | ||
return Object.entries(vaultEntry.data); | ||
async function entries({ signal, } = {}) { | ||
var { vaultEntry, } = (await openVault(this,signal)) || {}; | ||
if (vaultEntry != null) { | ||
return Object.entries(vaultEntry.data); | ||
} | ||
} | ||
async function __exportLockKey({ risky = false, } = {}) { | ||
async function __exportLockKey({ risky = false, signal, } = {}) { | ||
if (risky == "this is unsafe") { | ||
let { vaultLockKey, } = await openVault(this); | ||
return { ...vaultLockKey, }; | ||
return ((await openVault(this,signal)) || {}).vaultLockKey; | ||
} | ||
@@ -377,3 +449,3 @@ else { | ||
async function openVault(vault) { | ||
async function openVault(vault,signal) { | ||
if ( | ||
@@ -396,6 +468,10 @@ vault != null && | ||
relyingPartyID: vaultEntry.rpID, | ||
signal, | ||
}); | ||
unlockVaultEntry(vaultEntry,vaultLockKey); | ||
return { storageType, vaultID, vaultEntry, vaultLockKey, }; | ||
// lock-key retrieval successful? | ||
if (vaultLockKey != null) { | ||
unlockVaultEntry(vaultEntry,vaultLockKey); | ||
return { storageType, vaultID, vaultEntry, vaultLockKey, }; | ||
} | ||
} | ||
@@ -416,5 +492,5 @@ else { | ||
// note: read() from adapter always works, | ||
// even if it just auto-initializes a new | ||
// empty vault entry | ||
// read() from adapter always works, even | ||
// if it just auto-initializes (and stores) | ||
// a new empty vault entry | ||
await adapters[storageType].read(vaultID) | ||
@@ -421,0 +497,0 @@ ); |
@@ -96,3 +96,7 @@ // note: these module specifiers come from the import-map | ||
var result = await Swal.fire({ | ||
title: "Vault Options", | ||
title: ( | ||
newVaultRegistration ? | ||
"New Vault Settings" : | ||
"Vault Info" | ||
), | ||
html: ` | ||
@@ -299,2 +303,19 @@ <p> | ||
async function promptSetupTimeout() { | ||
var confirmTimeout = await Swal.fire({ | ||
text: "Do you want to test cancellation, by limiting your next passkey authentication with a 5 sec timeout?", | ||
icon: "question", | ||
showConfirmButton: true, | ||
confirmButtonText: "Yes, timeout!", | ||
confirmButtonColor: "darkslateblue", | ||
showCancelButton: true, | ||
cancelButtonColor: "darkslategray", | ||
cancelButtonText: "Skip timeout", | ||
focusCancel: true, | ||
allowOutsideClick: true, | ||
allowEscapeKey: true, | ||
}); | ||
return confirmTimeout.isConfirmed; | ||
} | ||
async function setupVault() { | ||
@@ -315,2 +336,5 @@ var { storageType, vaultID, } = ( | ||
var setupTimeout = await promptSetupTimeout(); | ||
var { signal, intv } = setupTimeout ? createTimeoutToken() : {}; | ||
try { | ||
@@ -327,3 +351,5 @@ startSpinner(); | ||
}, | ||
signal, | ||
}); | ||
if (intv != null) { clearTimeout(intv); } | ||
updateElements(); | ||
@@ -344,2 +370,5 @@ stopSpinner(); | ||
var setupTimeout = await promptSetupTimeout(); | ||
var { signal, intv } = setupTimeout ? createTimeoutToken() : {}; | ||
try { | ||
@@ -353,3 +382,5 @@ startSpinner(); | ||
}, | ||
signal, | ||
}); | ||
if (intv != null) { clearTimeout(intv); } | ||
updateElements(); | ||
@@ -407,2 +438,5 @@ stopSpinner(); | ||
var setupTimeout = ( currentVault == null ? await promptSetupTimeout() : false); | ||
var { signal, intv } = setupTimeout ? createTimeoutToken() : {}; | ||
try { | ||
@@ -416,3 +450,5 @@ startSpinner(); | ||
}, | ||
signal, | ||
}); | ||
if (intv != null) { clearTimeout(intv); } | ||
updateElements(); | ||
@@ -451,6 +487,13 @@ stopSpinner(); | ||
let setupTimeout = await promptSetupTimeout(); | ||
let { signal, intv } = setupTimeout ? createTimeoutToken() : {}; | ||
try { | ||
startSpinner(); | ||
await currentVault.addPasskey({ username, displayName, }); | ||
let added = await currentVault.addPasskey({ username, displayName, signal, }); | ||
if (intv != null) { clearTimeout(intv); } | ||
stopSpinner(); | ||
if (added) { | ||
showToast("New passkey added to vault."); | ||
} | ||
} | ||
@@ -715,1 +758,7 @@ catch (err) { | ||
} | ||
function createTimeoutToken() { | ||
var ac = new AbortController(); | ||
var intv = setTimeout(() => ac.abort("Timeout!"),5000); | ||
return { signal: ac.signal, intv, }; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
906552
5482
410
+ Added@lo-fi/local-data-lock@0.11.2(transitive)
- Removed@lo-fi/local-data-lock@0.10.0(transitive)