Skip to content

Commit

Permalink
adding periodic (random) internal timer to purge expired key cache en…
Browse files Browse the repository at this point in the history
…tries, closes #9
  • Loading branch information
getify committed Sep 30, 2024
1 parent cddf871 commit ac2a28e
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 6 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"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": {
".": "./dist/bundlers/ldl.mjs",
"./bundlers/astro": "./bundler-plugins/astro.mjs",
Expand Down
65 changes: 60 additions & 5 deletions src/ldl.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ import {

const CURRENT_LOCK_KEY_FORMAT_VERSION = 1;
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;
var localIdentities = null;
var lockKeyCache = {};
var abortToken = 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";


// ***********************
Expand Down Expand Up @@ -90,6 +91,7 @@ async function listLocalIdentities() {

function getCachedLockKey(localID) {
var now = Date.now();

if (
// lock-key currently in cache?
(localID in lockKeyCache) &&
Expand All @@ -99,7 +101,7 @@ function getCachedLockKey(localID) {
now - Math.min(LOCK_KEY_CACHE_LIFETIME,now)
)
) {
// discard cache-internal timestamp field
// hide cache-internal timestamp field
let { timestamp, ...lockKey } = lockKeyCache[localID];
return lockKey;
}
Expand All @@ -114,16 +116,64 @@ function cacheLockKey(localID,lockKey,forceUpdate = false) {
// expiration check
timestamp: Date.now(),
};
resetCachePurgeTimer();
}
}

function clearLockKeyCache(localID) {
if (localID != null) {
delete lockKeyCache[localID]
delete lockKeyCache[localID];
}
else {
lockKeyCache = {};
}
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) {
Expand Down Expand Up @@ -687,7 +737,12 @@ async function storeLocalIdentities() {
}

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();
}
}

function configureStorage(storageOpt) {
Expand Down

0 comments on commit ac2a28e

Please sign in to comment.