@sailfish-ai/recorder 1.12.4 → 1.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/canvas/adapters.js +382 -0
- package/dist/canvas/adapters.js.br +0 -0
- package/dist/canvas/adapters.js.gz +0 -0
- package/dist/canvas/canvasManager.js +1949 -0
- package/dist/canvas/canvasManager.js.br +0 -0
- package/dist/canvas/canvasManager.js.gz +0 -0
- package/dist/canvas/canvasPrivacy.js +233 -0
- package/dist/canvas/canvasPrivacy.js.br +0 -0
- package/dist/canvas/canvasPrivacy.js.gz +0 -0
- package/dist/canvas/codec.js +314 -0
- package/dist/canvas/codec.js.br +0 -0
- package/dist/canvas/codec.js.gz +0 -0
- package/dist/canvas/deviceClass.js +67 -0
- package/dist/canvas/deviceClass.js.br +0 -0
- package/dist/canvas/deviceClass.js.gz +0 -0
- package/dist/canvas/encodeWorker.js +284 -0
- package/dist/canvas/encodeWorker.js.br +0 -0
- package/dist/canvas/encodeWorker.js.gz +0 -0
- package/dist/canvas/governor.js +79 -0
- package/dist/canvas/governor.js.br +0 -0
- package/dist/canvas/governor.js.gz +0 -0
- package/dist/canvas/keyframes.js +183 -0
- package/dist/canvas/keyframes.js.br +0 -0
- package/dist/canvas/keyframes.js.gz +0 -0
- package/dist/canvas/mediaRecorderCapture.js +257 -0
- package/dist/canvas/mediaRecorderCapture.js.br +0 -0
- package/dist/canvas/mediaRecorderCapture.js.gz +0 -0
- package/dist/canvas/objectLog.js +150 -0
- package/dist/canvas/objectLog.js.br +0 -0
- package/dist/canvas/objectLog.js.gz +0 -0
- package/dist/canvas/patch2d.js +1302 -0
- package/dist/canvas/patch2d.js.br +0 -0
- package/dist/canvas/patch2d.js.gz +0 -0
- package/dist/canvas/patchGL.js +371 -0
- package/dist/canvas/patchGL.js.br +0 -0
- package/dist/canvas/patchGL.js.gz +0 -0
- package/dist/canvas/patchWebGPU.js +361 -0
- package/dist/canvas/patchWebGPU.js.br +0 -0
- package/dist/canvas/patchWebGPU.js.gz +0 -0
- package/dist/canvas/piiDetect.js +155 -0
- package/dist/canvas/piiDetect.js.br +0 -0
- package/dist/canvas/piiDetect.js.gz +0 -0
- package/dist/canvas/protocol.js +122 -0
- package/dist/canvas/protocol.js.br +0 -0
- package/dist/canvas/protocol.js.gz +0 -0
- package/dist/canvas/videoCapture.js +252 -0
- package/dist/canvas/videoCapture.js.br +0 -0
- package/dist/canvas/videoCapture.js.gz +0 -0
- package/dist/canvas/workerRelay.js +99 -0
- package/dist/canvas/workerRelay.js.br +0 -0
- package/dist/canvas/workerRelay.js.gz +0 -0
- package/dist/canvas/workerShim.js +227 -0
- package/dist/canvas/workerShim.js.br +0 -0
- package/dist/canvas/workerShim.js.gz +0 -0
- package/dist/canvas-worker.cjs +74 -0
- package/dist/canvas-worker.cjs.br +0 -0
- package/dist/canvas-worker.cjs.gz +0 -0
- package/dist/canvas-worker.js +78 -0
- package/dist/canvas-worker.js.br +0 -0
- package/dist/canvas-worker.js.gz +0 -0
- package/dist/canvasFrameStore.js +198 -0
- package/dist/canvasFrameStore.js.br +0 -0
- package/dist/canvasFrameStore.js.gz +0 -0
- package/dist/chunks/canvasManager-BYn3_oyo.js +1440 -0
- package/dist/chunks/canvasManager-BYn3_oyo.js.br +0 -0
- package/dist/chunks/canvasManager-BYn3_oyo.js.gz +0 -0
- package/dist/chunks/canvasManager-CoX5072G.js +1456 -0
- package/dist/chunks/canvasManager-CoX5072G.js.br +0 -0
- package/dist/chunks/canvasManager-CoX5072G.js.gz +0 -0
- package/dist/chunks/{chunkSerializer-FQtY90Av.js → chunkSerializer-C_K0Wyk0.js} +1 -1
- package/dist/chunks/chunkSerializer-C_K0Wyk0.js.br +0 -0
- package/dist/chunks/chunkSerializer-C_K0Wyk0.js.gz +0 -0
- package/dist/chunks/{chunkSerializer-DDukZpgl.js → chunkSerializer-CghPitF4.js} +1 -1
- package/dist/chunks/chunkSerializer-CghPitF4.js.br +0 -0
- package/dist/chunks/chunkSerializer-CghPitF4.js.gz +0 -0
- package/dist/chunks/{index-C-qbsfKe.js → index-BIj4l3CV.js} +1028 -650
- package/dist/chunks/index-BIj4l3CV.js.br +0 -0
- package/dist/chunks/index-BIj4l3CV.js.gz +0 -0
- package/dist/chunks/{index-D6axlCRu.js → index-DIYomvdD.js} +1119 -720
- package/dist/chunks/index-DIYomvdD.js.br +0 -0
- package/dist/chunks/index-DIYomvdD.js.gz +0 -0
- package/dist/chunks/patch2d-9voowhJ_.js +816 -0
- package/dist/chunks/patch2d-9voowhJ_.js.br +0 -0
- package/dist/chunks/patch2d-9voowhJ_.js.gz +0 -0
- package/dist/chunks/patch2d-PQxcaE6r.js +886 -0
- package/dist/chunks/patch2d-PQxcaE6r.js.br +0 -0
- package/dist/chunks/patch2d-PQxcaE6r.js.gz +0 -0
- package/dist/graphql.js +12 -1
- package/dist/graphql.js.br +0 -0
- package/dist/graphql.js.gz +0 -0
- package/dist/inAppReportIssueModal/index.js +48 -0
- package/dist/inAppReportIssueModal/index.js.br +0 -0
- package/dist/inAppReportIssueModal/index.js.gz +0 -0
- package/dist/index.js +144 -2
- package/dist/index.js.br +0 -0
- package/dist/index.js.gz +0 -0
- package/dist/recorder.cjs +2 -2
- package/dist/recorder.cjs.br +0 -0
- package/dist/recorder.cjs.gz +0 -0
- package/dist/recorder.js +52 -41
- package/dist/recorder.js.br +0 -0
- package/dist/recorder.js.gz +0 -0
- package/dist/recorder.umd.cjs +4847 -2212
- package/dist/recorder.umd.cjs.br +0 -0
- package/dist/recorder.umd.cjs.gz +0 -0
- package/dist/recording.js +119 -10
- package/dist/recording.js.br +0 -0
- package/dist/recording.js.gz +0 -0
- package/dist/types/canvas/adapters.d.ts +71 -0
- package/dist/types/canvas/canvasManager.d.ts +273 -0
- package/dist/types/canvas/canvasPrivacy.d.ts +184 -0
- package/dist/types/canvas/codec.d.ts +27 -0
- package/dist/types/canvas/deviceClass.d.ts +40 -0
- package/dist/types/canvas/encodeWorker.d.ts +54 -0
- package/dist/types/canvas/governor.d.ts +44 -0
- package/dist/types/canvas/keyframes.d.ts +81 -0
- package/dist/types/canvas/mediaRecorderCapture.d.ts +91 -0
- package/dist/types/canvas/objectLog.d.ts +75 -0
- package/dist/types/canvas/patch2d.d.ts +339 -0
- package/dist/types/canvas/patchGL.d.ts +74 -0
- package/dist/types/canvas/patchWebGPU.d.ts +37 -0
- package/dist/types/canvas/piiDetect.d.ts +60 -0
- package/dist/types/canvas/protocol.d.ts +176 -0
- package/dist/types/canvas/videoCapture.d.ts +67 -0
- package/dist/types/canvas/workerRelay.d.ts +29 -0
- package/dist/types/canvas/workerShim.d.ts +10 -0
- package/dist/types/canvasFrameStore.d.ts +23 -0
- package/dist/types/graphql.d.ts +1 -1
- package/dist/types/index.d.ts +99 -1
- package/dist/types/types.d.ts +40 -0
- package/dist/types/websocket.d.ts +19 -0
- package/dist/websocket.js +210 -0
- package/dist/websocket.js.br +0 -0
- package/dist/websocket.js.gz +0 -0
- package/package.json +7 -1
- package/dist/chunks/chunkSerializer-DDukZpgl.js.br +0 -0
- package/dist/chunks/chunkSerializer-DDukZpgl.js.gz +0 -0
- package/dist/chunks/chunkSerializer-FQtY90Av.js.br +0 -0
- package/dist/chunks/chunkSerializer-FQtY90Av.js.gz +0 -0
- package/dist/chunks/index-C-qbsfKe.js.br +0 -0
- package/dist/chunks/index-C-qbsfKe.js.gz +0 -0
- package/dist/chunks/index-D6axlCRu.js.br +0 -0
- package/dist/chunks/index-D6axlCRu.js.gz +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { p as e, a as t, r as s, d as n, C as o } from "./chunks/patch2d-PQxcaE6r.js";
|
|
2
|
+
let a = false, r = false, c = null, workerNow = () => Date.now(), f = null;
|
|
3
|
+
let i = null, p = null, d = [], l = [], u = 0, h = 0, y = [];
|
|
4
|
+
function registerWorkerCanvasContext(e2) {
|
|
5
|
+
c = e2;
|
|
6
|
+
}
|
|
7
|
+
function setWorkerNow(e2) {
|
|
8
|
+
workerNow = e2;
|
|
9
|
+
}
|
|
10
|
+
async function encodeBitmapLocal(e2) {
|
|
11
|
+
const t2 = new OffscreenCanvas(e2.width, e2.height);
|
|
12
|
+
t2.getContext("2d").drawImage(e2, 0, 0), e2.close();
|
|
13
|
+
return { size: (await t2.convertToBlob({ type: "image/webp", quality: 0.85 })).size };
|
|
14
|
+
}
|
|
15
|
+
function flushBatch(e2) {
|
|
16
|
+
if (!p || 0 === p.ops) return;
|
|
17
|
+
const t2 = p.takeBatch();
|
|
18
|
+
y.push(n(t2).then((t3) => {
|
|
19
|
+
d.push({ tMs: e2, bytes: t3.length });
|
|
20
|
+
const s2 = t3.buffer.slice(t3.byteOffset, t3.byteOffset + t3.byteLength);
|
|
21
|
+
self.postMessage({ type: "sf-record-batch", deflated: s2, tMs: e2 }, [s2]);
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
function ensurePatched() {
|
|
25
|
+
var _a;
|
|
26
|
+
if (a) return;
|
|
27
|
+
a = true;
|
|
28
|
+
const t2 = (_a = globalThis.OffscreenCanvasRenderingContext2D) == null ? void 0 : _a.prototype;
|
|
29
|
+
t2 && e(t2);
|
|
30
|
+
const s2 = globalThis.OffscreenCanvas;
|
|
31
|
+
if (s2) {
|
|
32
|
+
const e2 = s2.prototype.getContext;
|
|
33
|
+
s2.prototype.getContext = function(t3, s3) {
|
|
34
|
+
const n2 = e2.call(this, t3, s3);
|
|
35
|
+
return "2d" === t3 && n2 && !c && (c = n2), n2;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function attachNow() {
|
|
40
|
+
if (c && p && (i = t(c, { onOp(e2) {
|
|
41
|
+
const t2 = performance.now();
|
|
42
|
+
p.encode(e2), u += performance.now() - t2, e2.t - h >= 1e3 && (flushBatch(e2.t), h = e2.t);
|
|
43
|
+
}, onAssetBytes(e2, t2, s2) {
|
|
44
|
+
d.push({ tMs: s2, bytes: t2 });
|
|
45
|
+
}, note(e2) {
|
|
46
|
+
l.includes(e2) || l.push(e2);
|
|
47
|
+
} }, () => workerNow(), "cas", encodeBitmapLocal, { retainAssets: true }), i && (function flagsRequireRedaction(e2) {
|
|
48
|
+
return !!e2 && (false === e2.recordCreditCardInfo || false === e2.recordSsn || false === e2.recordDob);
|
|
49
|
+
})(f))) {
|
|
50
|
+
const e2 = f;
|
|
51
|
+
i.textRedactor = (t2) => s(t2, e2);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function installWorkerRecorder() {
|
|
55
|
+
r || (r = true, ensurePatched(), self.addEventListener("message", (e2) => {
|
|
56
|
+
const t2 = e2.data;
|
|
57
|
+
"sf-record-ping" === (t2 == null ? void 0 : t2.type) ? self.postMessage({ type: "sf-record-pong" }) : "sf-record-start" === (t2 == null ? void 0 : t2.type) ? (function start(e3) {
|
|
58
|
+
ensurePatched(), f = e3 ?? null, p = new o(), d = [], l = [], u = 0, h = 0, y = [], c ? attachNow() : (l.push("sf-record-start: no OffscreenCanvas 2D context registered yet"), self.postMessage({ type: "sf-record-noctx" }));
|
|
59
|
+
})(t2.contentFlags) : "sf-record-stop" === (t2 == null ? void 0 : t2.type) && (async function stop() {
|
|
60
|
+
!i && c && p && attachNow(), flushBatch(workerNow()), await (i == null ? void 0 : i.settle()), await Promise.all(y);
|
|
61
|
+
const e3 = [], t3 = [];
|
|
62
|
+
for (const [, s2] of (i == null ? void 0 : i.assets) ?? /* @__PURE__ */ new Map()) {
|
|
63
|
+
const n2 = { id: s2.id, kind: s2.kind, t: s2.t, bytes: s2.bytes, w: s2.w, h: s2.h };
|
|
64
|
+
s2.bitmap && (n2.bitmap = s2.bitmap, t3.push(s2.bitmap)), s2.imageData && (n2.imageData = s2.imageData), e3.push(n2);
|
|
65
|
+
}
|
|
66
|
+
self.postMessage({ type: "sf-record-done", batches: [], chunks: d, cpuMs: ((i == null ? void 0 : i.cpuMs) ?? 0) + u, notes: l, assets: e3 }, t3), i = null;
|
|
67
|
+
})();
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
if ("undefined" != typeof self && "undefined" == typeof window && "undefined" == typeof document && "function" == typeof self.postMessage) try {
|
|
71
|
+
installWorkerRecorder();
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
installWorkerRecorder,
|
|
76
|
+
registerWorkerCanvasContext,
|
|
77
|
+
setWorkerNow
|
|
78
|
+
};
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// canvasFrameStore.ts
|
|
2
|
+
// SSR/Edge-safe IndexedDB persistence for binary canvas SFCV frames.
|
|
3
|
+
//
|
|
4
|
+
// Why this exists: while the recording WebSocket is down, canvas binary frames
|
|
5
|
+
// buffer in a main-thread memory ring (see websocket.tsx) capped at 8 MiB,
|
|
6
|
+
// oldest-dropped — so a long/large offline window silently loses frames, and a
|
|
7
|
+
// page reload drops the whole ring. This module is the durable backstop: every
|
|
8
|
+
// offline frame is also written to IDB (larger at-rest budget, survives reload)
|
|
9
|
+
// and drained in order on reconnect. When IDB is unavailable (SSR, private
|
|
10
|
+
// mode, exotic runtimes) every function is a safe no-op and the caller falls
|
|
11
|
+
// back to the memory ring — i.e. today's behavior, never worse.
|
|
12
|
+
//
|
|
13
|
+
// Mirrors the defensive contract of eventStore.ts: zero top-level `indexedDB`
|
|
14
|
+
// access, fail-closed (treat any error as "unavailable"), 24h at-rest TTL.
|
|
15
|
+
// Uses its OWN database (not leapsEventDB) so the two stores' schema versions
|
|
16
|
+
// can evolve independently without VersionError cross-talk.
|
|
17
|
+
const DB_NAME = "leapsCanvasFrameDB";
|
|
18
|
+
const STORE_NAME = "canvasFrames";
|
|
19
|
+
const DB_VERSION = 1;
|
|
20
|
+
// HIPAA MVP: bound at-rest exposure of buffered canvas pixels. 24h matches
|
|
21
|
+
// eventStore.ts — long offline windows tolerated, not indefinite retention.
|
|
22
|
+
const FRAME_TTL_MS = 24 * 60 * 60 * 1000;
|
|
23
|
+
// At-rest budget. Larger than the 8 MiB in-memory ring (that cap exists to
|
|
24
|
+
// bound *heap*; IDB is disk-backed so it can hold a fuller offline session)
|
|
25
|
+
// but still bounded so a pathological offline window can't fill the origin's
|
|
26
|
+
// quota. Oldest frames are pruned first, matching the ring's semantics.
|
|
27
|
+
const FRAME_STORE_MAX_BYTES = 64 * 1024 * 1024;
|
|
28
|
+
// Cached open promise; never created on SSR.
|
|
29
|
+
let _dbPromise = null;
|
|
30
|
+
function hasIndexedDB() {
|
|
31
|
+
return typeof globalThis !== "undefined" && !!globalThis.indexedDB;
|
|
32
|
+
}
|
|
33
|
+
function openDb() {
|
|
34
|
+
if (!hasIndexedDB())
|
|
35
|
+
return Promise.resolve(null);
|
|
36
|
+
if (_dbPromise)
|
|
37
|
+
return _dbPromise;
|
|
38
|
+
_dbPromise = new Promise((resolve) => {
|
|
39
|
+
try {
|
|
40
|
+
const req = globalThis.indexedDB.open(DB_NAME, DB_VERSION);
|
|
41
|
+
req.onupgradeneeded = () => {
|
|
42
|
+
const db = req.result;
|
|
43
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
44
|
+
db.createObjectStore(STORE_NAME, { keyPath: "id", autoIncrement: true });
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
req.onsuccess = () => resolve(req.result);
|
|
48
|
+
req.onerror = () => resolve(null); // fail closed → unavailable
|
|
49
|
+
req.onblocked = () => resolve(null);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
resolve(null);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return _dbPromise;
|
|
56
|
+
}
|
|
57
|
+
function withStore(mode, fn) {
|
|
58
|
+
return openDb().then((db) => {
|
|
59
|
+
if (!db)
|
|
60
|
+
return null;
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
try {
|
|
63
|
+
const tx = db.transaction(STORE_NAME, mode);
|
|
64
|
+
const store = tx.objectStore(STORE_NAME);
|
|
65
|
+
Promise.resolve(fn(store))
|
|
66
|
+
.then((result) => {
|
|
67
|
+
tx.oncomplete = () => resolve(result);
|
|
68
|
+
tx.onerror = () => resolve(null);
|
|
69
|
+
})
|
|
70
|
+
.catch(() => resolve(null));
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
resolve(null);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// ── Public API (SSR-safe) ──────────────────────────────────────────────────
|
|
79
|
+
/**
|
|
80
|
+
* Persist one binary canvas frame. Returns true if the frame was durably
|
|
81
|
+
* written, false if IDB is unavailable (caller should keep the memory-ring
|
|
82
|
+
* fallback). After a successful add, prunes oldest frames so the store stays
|
|
83
|
+
* under FRAME_STORE_MAX_BYTES.
|
|
84
|
+
*/
|
|
85
|
+
export async function persistCanvasFrame(buf) {
|
|
86
|
+
const ok = await withStore("readwrite", (store) => {
|
|
87
|
+
store.add({ timestamp: Date.now(), bytes: buf.byteLength, buf });
|
|
88
|
+
return true;
|
|
89
|
+
});
|
|
90
|
+
if (ok)
|
|
91
|
+
void pruneToBudget().catch(() => { });
|
|
92
|
+
return ok === true;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Read all persisted frames in insertion order (ascending id), dropping any
|
|
96
|
+
* past the TTL and scheduling their background deletion. The returned `buf`s
|
|
97
|
+
* are ready to hand straight to the WS binary send path.
|
|
98
|
+
*/
|
|
99
|
+
export async function readAllCanvasFrames() {
|
|
100
|
+
const result = await withStore("readonly", (store) => {
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
const req = store.getAll();
|
|
103
|
+
req.onsuccess = () => resolve(req.result);
|
|
104
|
+
req.onerror = () => resolve([]);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
const all = result ?? [];
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
const fresh = [];
|
|
110
|
+
const staleIds = [];
|
|
111
|
+
for (const f of all) {
|
|
112
|
+
if (typeof f?.timestamp === "number" && now - f.timestamp >= FRAME_TTL_MS) {
|
|
113
|
+
if (typeof f.id === "number")
|
|
114
|
+
staleIds.push(f.id);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
fresh.push(f);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (staleIds.length > 0)
|
|
121
|
+
void deleteCanvasFramesByIds(staleIds).catch(() => { });
|
|
122
|
+
// getAll returns ascending key order, but be explicit — flush order is
|
|
123
|
+
// capture order, which matters for command-stream replay.
|
|
124
|
+
fresh.sort((a, b) => (a.id ?? 0) - (b.id ?? 0));
|
|
125
|
+
return fresh;
|
|
126
|
+
}
|
|
127
|
+
export async function deleteCanvasFramesByIds(ids) {
|
|
128
|
+
if (ids.length === 0)
|
|
129
|
+
return;
|
|
130
|
+
await withStore("readwrite", (store) => {
|
|
131
|
+
for (const id of ids)
|
|
132
|
+
store.delete(id);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
export async function clearCanvasFrames() {
|
|
136
|
+
await withStore("readwrite", (store) => {
|
|
137
|
+
store.clear();
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/** Diagnostics: total bytes currently persisted (0 when IDB unavailable). */
|
|
141
|
+
export async function countPersistedCanvasFrameBytes() {
|
|
142
|
+
const total = await withStore("readonly", (store) => {
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
let sum = 0;
|
|
145
|
+
const req = store.openCursor();
|
|
146
|
+
req.onsuccess = () => {
|
|
147
|
+
const cursor = req.result;
|
|
148
|
+
if (!cursor)
|
|
149
|
+
return resolve(sum);
|
|
150
|
+
const v = cursor.value;
|
|
151
|
+
sum += typeof v?.bytes === "number" ? v.bytes : 0;
|
|
152
|
+
cursor.continue();
|
|
153
|
+
};
|
|
154
|
+
req.onerror = () => resolve(sum);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
return total ?? 0;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Prune oldest frames (lowest id first) until the store is within
|
|
161
|
+
* FRAME_STORE_MAX_BYTES. Single readwrite cursor pass; best-effort.
|
|
162
|
+
*/
|
|
163
|
+
async function pruneToBudget() {
|
|
164
|
+
await withStore("readwrite", (store) => {
|
|
165
|
+
return new Promise((resolve) => {
|
|
166
|
+
// First total the bytes, then delete from the front until under budget.
|
|
167
|
+
// A single forward cursor does both: sum, and once we know the overage
|
|
168
|
+
// we can't "go back", so do it in two cursor passes within the same tx.
|
|
169
|
+
let total = 0;
|
|
170
|
+
const sizes = [];
|
|
171
|
+
const countReq = store.openCursor();
|
|
172
|
+
countReq.onsuccess = () => {
|
|
173
|
+
const c = countReq.result;
|
|
174
|
+
if (c) {
|
|
175
|
+
const v = c.value;
|
|
176
|
+
const bytes = typeof v?.bytes === "number" ? v.bytes : 0;
|
|
177
|
+
total += bytes;
|
|
178
|
+
if (typeof v?.id === "number")
|
|
179
|
+
sizes.push({ id: v.id, bytes });
|
|
180
|
+
c.continue();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Cursor exhausted — delete oldest until under budget.
|
|
184
|
+
let over = total - FRAME_STORE_MAX_BYTES;
|
|
185
|
+
if (over <= 0)
|
|
186
|
+
return resolve();
|
|
187
|
+
for (const s of sizes) {
|
|
188
|
+
if (over <= 0)
|
|
189
|
+
break;
|
|
190
|
+
store.delete(s.id);
|
|
191
|
+
over -= s.bytes;
|
|
192
|
+
}
|
|
193
|
+
resolve();
|
|
194
|
+
};
|
|
195
|
+
countReq.onerror = () => resolve();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
Binary file
|
|
Binary file
|