mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
keep small writes durable across page dismissal with fetch keepalive
This commit is contained in:
@@ -19,6 +19,18 @@ function vaultId() {
|
|||||||
return window.__currentVaultId || "";
|
return window.__currentVaultId || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const KEEPALIVE_MAX_BYTES = 64 * 1024;
|
||||||
|
|
||||||
|
// keepalive lets a request finish after the page starts unloading.
|
||||||
|
// Its body is capped at 64KB across a shared pool, so opt in only under that limit.
|
||||||
|
function withinKeepaliveCap(body) {
|
||||||
|
if (!body) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TextEncoder().encode(body).length <= KEEPALIVE_MAX_BYTES;
|
||||||
|
}
|
||||||
|
|
||||||
async function request(method, endpoint, params = {}) {
|
async function request(method, endpoint, params = {}) {
|
||||||
const url = new URL(API_BASE + endpoint, window.location.origin);
|
const url = new URL(API_BASE + endpoint, window.location.origin);
|
||||||
|
|
||||||
@@ -37,6 +49,11 @@ async function request(method, endpoint, params = {}) {
|
|||||||
options.body = JSON.stringify({ vault: vaultId(), ...params });
|
options.body = JSON.stringify({ vault: vaultId(), ...params });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A write (POST/DELETE) opts into keepalive so a page dismissal does not drop it.
|
||||||
|
if (method !== "GET" && withinKeepaliveCap(options.body)) {
|
||||||
|
options.keepalive = true;
|
||||||
|
}
|
||||||
|
|
||||||
const res = await fetch(url.toString(), options);
|
const res = await fetch(url.toString(), options);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const err = await res
|
const err = await res
|
||||||
|
|||||||
60
packages/shim/src/fs/transport.test.js
Normal file
60
packages/shim/src/fs/transport.test.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import { transport } from "./transport.js";
|
||||||
|
|
||||||
|
let fetchMock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fetchMock = vi.fn(async () => ({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({}),
|
||||||
|
text: async () => "",
|
||||||
|
arrayBuffer: async () => new ArrayBuffer(0),
|
||||||
|
}));
|
||||||
|
globalThis.fetch = fetchMock;
|
||||||
|
globalThis.window = {
|
||||||
|
location: { origin: "http://localhost" },
|
||||||
|
__currentVaultId: "v",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete globalThis.fetch;
|
||||||
|
delete globalThis.window;
|
||||||
|
});
|
||||||
|
|
||||||
|
function lastInit() {
|
||||||
|
return fetchMock.mock.calls.at(-1)[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("transport keepalive gating", () => {
|
||||||
|
it("sets keepalive on a small write", async () => {
|
||||||
|
await transport.writeFile("a.md", "hello", "utf-8");
|
||||||
|
|
||||||
|
expect(lastInit().keepalive).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("omits keepalive when the body exceeds the 64KB cap", async () => {
|
||||||
|
await transport.writeFile("a.md", "x".repeat(70 * 1024), "utf-8");
|
||||||
|
|
||||||
|
expect(lastInit().keepalive).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("counts base64 inflation against the cap for binary writes", async () => {
|
||||||
|
// 60KB of bytes inflates to ~80KB of base64, over the cap.
|
||||||
|
await transport.writeFile("a.bin", new Uint8Array(60 * 1024));
|
||||||
|
|
||||||
|
expect(lastInit().keepalive).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets keepalive on a bodyless delete", async () => {
|
||||||
|
await transport.unlink("a.md");
|
||||||
|
|
||||||
|
expect(lastInit().keepalive).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not set keepalive on a read", async () => {
|
||||||
|
await transport.readFile("a.md", "utf8");
|
||||||
|
|
||||||
|
expect(lastInit().keepalive).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user