Files
ihasmail/web/src/lib/sw/swFacts.ts
T
jcoffey-dev bd6a605d61 Group six more clusters out of web/src/lib
Takes the flat module count from 66 to 42, continuing what admin/ and
calendar/ started.

  lib/mailbox/  archiveDate, emptyFolder, folderMove, labelTree,
                mailboxName, mailboxRoute
  lib/sieve/    sieve, sieveApply, sieveFolders
  lib/input/    keyboard, swipe, touch, listSelection, dropUpload
  lib/notify/   notify, webpush, webpushEnable
  lib/sw/       swCache, swFacts, staleBuild
  lib/text/     html, markdown, text, emlName

FOUR THINGS THE FILENAMES GET WRONG, each checked by reading the file
rather than trusting what it is called:

  - appFolder is not a mailbox. It is the `ihasmail` folder in JMAP
    *Files*, where the client keeps signature images and synced settings.
    It stays flat.
  - format holds no formatting of text. It re-exports the date and clock
    formatters, so it belongs with dates/datetime, not with text/.
  - preview is the file viewer deciding what it can show without
    downloading, and source is where to point someone asking for this
    instance's AGPL source. Neither is about text.
  - notify is not Web Push. It is the tab title, the favicon badge and
    the new-mail sound -- in-app notification, which is why it sits with
    webpush rather than under sw/ with the service worker's own concerns.

threadScroll stays flat too: it decides where a conversation opens, which
is view state rather than a gesture, and input/ is honest only if
everything in it interprets something the reader did.

No behavior change. Almost every reference was on the @/ alias; eight
relative imports in files that did not move, or that moved away from a
sibling, needed rewriting by hand.
2026-09-15 23:17:50 -07:00

70 lines
2.6 KiB
TypeScript

/*
* What the service worker cannot work out for itself.
*
* The worker can act on mail — see the note on `jmap()` in sw.js — but it
* cannot read a catalog or a store. It is plain JavaScript copied into the
* build, outside the bundle, with no i18n and no idea which mailbox is the
* archive. Both of those are things a tab knows and can simply write down.
*
* So the app leaves a short briefing in the same cache it uses for every other
* handoff, and the worker reads it when a notification arrives. Where there is
* none, the worker offers no actions at all rather than guessing: an untitled
* button that files mail somewhere is worse than a notification you have to
* open.
*
* That means the actions appear once ihasmail has been opened since the worker
* was installed, which is the same condition background notifications already
* carry — a push subscription has to be renewed from a tab too.
*/
import { withBase } from "../basePath";
import { SW_CACHE_NAME } from "./swCache";
import { t } from "../i18n";
export const FACTS_KEY = "/ihasmail-worker-facts";
export interface WorkerFacts {
/** The account the notifications are about. */
accountId: string;
/** Where Archive files to; null where the account has no archive folder. */
archiveId: string | null;
/** The worker's own user-visible text, in the language this tab is in. */
strings: {
newMail: string;
newMessage: string;
noSubject: string;
archive: string;
markRead: string;
failed: string;
};
}
/**
* Write the briefing.
*
* Called again whenever what is in it could have changed — the language, the
* account, the archive folder — because it is what the worker will still be
* reading in a week's time. Rewriting it is one cache put; there is nothing to
* gain by working out whether it differs.
*/
export async function publishWorkerFacts(accountId: string | null, archiveId: string | null): Promise<void> {
if (typeof caches === "undefined" || !accountId) return;
const facts: WorkerFacts = {
accountId,
archiveId,
strings: {
newMail: t("New mail"),
newMessage: t("New message"),
noSubject: t("(no subject)"),
archive: t("Archive"),
markRead: t("Mark as read"),
failed: t("Could not do that — open ihasmail and try again"),
},
};
try {
const cache = await caches.open(SW_CACHE_NAME);
await cache.put(withBase(FACTS_KEY), new Response(JSON.stringify(facts), { headers: { "content-type": "application/json" } }));
} catch {
/* no cache storage: the worker falls back to a notification with no actions */
}
}