Commit Graph
10 Commits
Author SHA1 Message Date
Linus Rath d6ae3d172b feat: merge opening files and sharing into the installed app
# Conflicts:
#	components/mail/mail-app.tsx
#	lib/__tests__/sw-harness.ts
2026-11-03 07:11:39 +01:00
Linus Rath f716ea66ee fix: ask before a share starts a message and save nothing until the first edit
Any site could POST a form to the share target on load: with no clicks
the composer opened with its subject and file, and autosave stored a
real draft on the server 2 seconds later. The worker can't see where a
POST came from, and Chromium's link capturing accepts form submissions,
so an installed-app window proves nothing either.

- every share landing now asks first, in every window and browser; a
  text landing address gets the same question. Nothing goes up before
  the answer.
- handed-in text and files are the composer's starting state: no
  autosave, page-close save or sign-out flush before the first real
  edit.
- the worker ignores frames, keeps at most 5 entries and 100 MB for 15
  minutes, sweeps on activate, on each share and from a page timer,
  and seals what it keeps with a key passed in the landing fragment.
  Offline wipes clear it too.
- the server fallback uses the fragment and caps by encoded length:
  3000 Japanese characters made a 27 KB `Location` and a 431.
- a second compose handoff waits until the composer closes, also one
  set aside by reading a message, where it used to replace the first.
- a launch leaving the mail page saves what was just typed first.
- an expired handoff or a missing stored file now says so; shares get
  the control-character stripping and limits of `parseMailto`.

Existing tests changed because the landing moved from the query to the
fragment, stored entries gained a sealed part, and a share now asks
first.
2026-11-03 07:08:38 +01:00
Linus Rath 2bb0797996 feat: archive, mark read or reply right from a new mail notification
A notification for one message gets buttons where the browser has room
for them, and a click on its body works as before. The service worker
has no login, so an open tab takes the action first, through the store
actions. With no tab open a new same-origin route, /api/push/action,
does Mark read and Archive with the context cookie. When the cookie is
gone, the token has expired or the login can't be told apart, the
message opens in the app instead. Reply opens the composer through a
`?reply=1` link, with any text typed on the notification.

- `public/sw.js`: buttons, asking open tabs, the route, `sync` handling
- `app/api/push/action`: same-origin, checks the login and the account,
  honours the admin's Archive gate, files by the mirrored archive mode
- `lib/pwa-notify`: the page's side, the mirror for the worker, and
  `requestOutboxSync`, which the offline queue calls
- Background Sync, thin version: a `sync` event tells open tabs to flush

Two push-preview-route assertions change: they match the whole answer
for a named message, which now also carries `login`, what the action
route checks so a stale notification can't act on whoever holds the
cookie slot now.
2026-11-03 05:26:43 +01:00
Linus Rath c65d18eaae feat: open mail, calendar and contact files from the OS and share into a new draft
The installed app only handled mailto: and webcal: launches. The launch
consumer dropped files, and nothing could hand a File to the composer or
the import dialogs, because the pending mailto is JSON in sessionStorage.

- manifest: `shortcuts` named in the browser's language, `file_handlers`
  for .eml, .ics and .vcf, and `share_target`, all with the base path and
  per-domain branding. Lite's manifest is unchanged.
- `lib/launch-handoff.ts`: the one place files and links wait, in memory
  and in the Cache API, so they survive a sign-in that leaves the page.
  The desktop bridge can call the same functions, since launchQueue never
  fires in Electron. Sign-out clears it.
- launch consumer: routes files by type, and takes a shortcut to an open
  window, which focus-existing would otherwise only focus.
- .eml opens in a new host for the preview, also before sign-in, with the
  strictest device policy deciding about saving. .ics and .vcf open their
  import dialogs through a new `initialFile` prop. A .ics needs the server,
  so offline the dialog says so and carries on once back online.
- composer: `initialFiles` goes through `addFiles`, so plugin vetoes and
  size limits still apply, also from a Pro compose tab.
- `sw.js` takes the share POST before the offline check and keeps the files
  for the page. Without a worker, `/protocol/share` passes text and links
  on and the page says the files are lost. `protocol` joins the app
  sections and the handler pages join the shell, so a mailto: launch
  offline no longer gets a network error.

Browsers without launchQueue or a service worker behave as before.
2026-11-03 05:26:01 +01:00
Nick Sweeting 2262b2baf1 fix: paginate mailbox retrieval without sidebar changes 2026-11-02 09:06:27 -08:00
Linus Rath 4b60fcc1e6 fix: make the offline outbox never send twice, never lose mail silently and never skip a plugin #523
The review of offline mode found ways to send a message twice, to lose
unsent mail without a word, and to send mail without its signing,
encryption or check plugin. This fixes them and the smaller findings:

- each outbox message is taken for sending in one database transaction
  under a Web Lock per account; only a failure before the submission
  request (SendNotStartedError from the client) puts it back, any other
  one marks it "may have gone out"; read-backs after the submission no
  longer fail a send; a send of a closed tab turns uncertain after a minute
- every sign-out the user didn't confirm names the deleted messages on
  the sign-in page, or in a toast when another account stays signed in
- nothing is queued while an enabled plugin isn't running (after a start
  without the network none can); the composer says why
- the outbox waits for a fresh policy and holds messages the rules have
  since made stricter (a required label, a required send check)
- another tab switching offline mode off sticks: only switching it on
  may create the database
- only attachments of kept messages are kept, filed with their message,
  deleted with it and under the cap between syncs; file names and types
  are sealed; folders that went away leave the scope, and a folder that
  couldn't be asked keeps its mail without holding the others back
- a group scope that forbids offline mode waits until the account's
  groups are known; one server down no longer holds another's mail
- an offline start only when the server is really unreachable, and the
  real sign-in follows by itself once it answers
- the service worker files pages and chunks under the build that served
  them, and the shell list of another build isn't kept under this one
- a raw send counts as the composer's only from the plugin running its
  takeover, for that identity
- errors and refusals are in all languages; the banner tells "offline"
  from "server can't be reached"; outbox buttons name their message;
  changes and messages go in the order they were made
2026-11-01 14:19:54 +01:00
Linus Rath 2ff2e713ec fix: keep the sign-in page from creating the offline database again #523 2026-11-01 12:22:35 +01:00
Linus Rath 6428c2b880 fix: answer from the device only when the mail server is really gone, and finish the first offline sync #523 2026-11-01 12:07:30 +01:00
Linus Rath 5a169dd63f fix: keep offline mode from counting a refused request as no network and keep folder counts in step offline #523 2026-11-01 11:57:16 +01:00
Linus Rath db5ac82f53 feat: keep recent mail on this device and send mail written offline #523
Settings > Backup & reset gets "Keep mail on this device", off until the
user turns it on in this browser. While it is on:

- the JMAP client hands the server's answers to an encrypted cache in
  IndexedDB (AES-GCM, non-extractable key) and answers from it when the
  network is gone: the folder list, the last 30 days of the Inbox and of
  the folders the user opened, bodies and attachments, at most 250 MB,
  oldest out first; a background sync keeps it current
- read, flag, tags and moves made offline are queued and sent in order
  when the server answers again; deleting for good and emptying a folder
  are refused offline
- the composer, quick reply and the S/MIME and PGP plugins' sends that
  complete the composer's go to an outbox once every step up to the
  transport ran (checks, label, signature, plugin send checks); only the
  finished send waits, with files attached offline
- the service worker keeps this build's app shell, so the app also
  starts offline from the kept session and the last policy
- a full sign-out, the idle sign-out and signing one account out delete
  it, a sign-out the user starts asks first when mail is unsent, and the
  login page names what an automatic sign-out deleted; a policy fetch
  that forbids it (devices.noOffline) wipes that account

Not in Lite.
2026-11-01 11:46:11 +01:00