HoldSendWrite offline. Publish exactly once.GitHub

@holdsend/web

HoldSend for browsers.

Early: 0.1 is the first release line. The API may change before 1.0, and it is not ready for production yet.

ESM only. Load it with import on Node 20 or later, in browsers and in React Native. require() also works on Node 20.19 or later and 22.12 or later; on older Node, use import().

What is in it

Use it

import { Outbox, classifyResponse } from "@holdsend/core";
import { createWebStore, startAutoFlush } from "@holdsend/web";

const store = await createWebStore({ name: "my-app-outbox" });
if (!(await store.isDurable())) showWarning("Drafts are kept only while this tab stays open.");

const outbox = new Outbox({
  store,
  async send(item, { headers, signal }) {
    const res = await fetch("/api/posts", {
      method: "POST",
      headers: { "content-type": "application/json", ...headers },
      body: JSON.stringify(item.payload),
      signal, // an attempt abandoned after its timeout is really cancelled
    });
    const body = await res.json().catch(() => ({}));
    return classifyResponse(res.status, body.code);
  },
});

const flusher = startAutoFlush(outbox, { owner: currentUser.id });

async function publish(text: string) {
  await outbox.add("post.create", { text }, { owner: currentUser.id });
  flusher.kick();
}

Background sending, honestly

Only Chromium browsers implement Background Sync. Safari (including every browser on iOS) and Firefox do not. requestBackgroundSync() returns "background" only after a registration actually succeeded, and "open_app" otherwise. Show the person which one they got when they press Send. For urgent messages on "open_app" devices, offer another route, such as SMS.

In the service worker, onBackgroundSync(event, flush) rejects when the flush left items waiting, so the browser wakes the worker again later instead of treating the sync as done. The browser only does this a few times (Chrome: about three, with growing gaps; event.lastChance is true on the last one) and then drops the sync. So register again with requestBackgroundSync() whenever the app opens or comes back online with items still waiting, and do not tell the person a message will go "in the background" once the last chance has passed. The worker has no session of its own: when the person signs in, keep their owner id (and whatever your send needs to authenticate) in IndexedDB for the worker to read.

Licence

Apache-2.0

Edit this page on GitHub