Commit Graph
74 Commits
Author SHA1 Message Date
Linus Rath d9d6eb9be7 feat: let plugins search the settings and read the user's preferences 2026-10-20 20:27:08 +02:00
Linus Rath 647c24132c feat: let plugins read the app's name and version with api.app.getInfo 2026-10-20 20:19:38 +02:00
Linus Rath a191ef8b02 feat: let plugins search mail with api.email.search 2026-10-20 18:16:29 +02:00
Linus Rath 95003fd28b feat: let a plugin panel start folded until the user or the plugin opens it 2026-10-20 17:21:13 +02:00
Linus Rath 95bfb1681c feat: let a plugin's panel frame fill the panel and scroll inside 2026-10-20 17:21:13 +02:00
Linus Rath 2bb5a62ac4 feat: let a plugin open, close and toggle the panel its slot sits in 2026-10-20 17:21:13 +02:00
Linus Rath 5aba2a92c5 Merge pull request #1251 from tobiasge/feat/plugin-ui-kit-combobox
feat(plugins): add Combobox component to UI kit with host floating dropdown portal
2026-10-18 14:34:26 +02:00
Linus Rath 86d6c78ce7 fix: keep a plugin slot's callbacks working when its props change while it loads #1266 2026-10-18 13:46:47 +02:00
Tobias Genannt d6d3683b65 feat(plugins): add Combobox component to UI kit with host floating dropdown portal
- Add Combobox component supporting single and multiselect modes, search filtering, and clear button
- Implement floating dropdown bridge and host slot portal so dropdown renders cleanly outside plugin iframe
- Fix clear button and chip removals to not unexpectedly open the dropdown
- Add comprehensive unit tests covering Combobox options, clear behavior, multiselect, and bridge communication
2026-10-16 10:08:27 +02:00
Linus Rath 9d8f762b1d feat: let a plugin title its sidebar section 2026-10-14 11:47:25 +02:00
Linus Rath 5d83d76436 feat: let plugins open a reply to the open message with text to start from 2026-10-14 11:37:51 +02:00
Linus Rath cbeeca3ea5 feat: let plugins edit the message being written from composer buttons and menus 2026-10-14 11:35:00 +02:00
Linus Rath 1bd4f743a1 feat: give plugins the app's own controls and draw their dialogs like the app's dialogs 2026-10-14 11:10:14 +02:00
Linus Rath 78f72e0c03 fix: give plugins settings that change after they started, let their frames copy to the clipboard and tell them the user's time zone 2026-10-14 09:01:03 +02:00
Linus Rath c3cf0ddcff feat: tell plugins whether the admin has set up their accounts and keys 2026-10-14 08:24:28 +02:00
Linus Rath 289e975d38 fix: give large attachment offloads time to finish and let cloud pickers write links into the text 2026-10-14 07:40:32 +02:00
Linus Rath 85c6cbcac8 feat: let plugins move messages between folders and archive them 2026-10-14 06:31:07 +02:00
Linus Rath 1f89e89ce5 feat: let plugins refresh their message-list badges, which now need only the read permission 2026-10-14 06:26:10 +02:00
Linus Rath 49511dff38 feat: let plugins read their own non-secret admin settings without the admin permission 2026-10-14 06:23:10 +02:00
Linus Rath 1ba53655e5 fix: let identity hooks stop a plugin's change, send notification clicks to plugins that show notifications, and mark reading mail as high risk 2026-10-14 04:37:24 +02:00
Linus Rath 8ad8f44e78 Merge branch 'feat/plugin-host-apis' into feat/plugin-system-audit
# Conflicts:
#	lib/plugin-sandbox/permission-text.ts
#	lib/plugin-sandbox/permissions.ts
#	lib/protocol-handlers/mailto.ts
#	locales/en/common.json
2026-10-14 04:34:14 +02:00
Linus Rath 5d9268c923 feat: let plugins add buttons, calendar day labels and pages, and use connected accounts from the sandbox 2026-10-14 04:31:43 +02:00
Linus Rath 3a65964d26 feat: let plugins show notifications that name the plugin 2026-10-14 04:26:16 +02:00
Linus Rath ead925c1b8 feat: let plugins open a filled-in composer and save drafts 2026-10-14 04:23:17 +02:00
Linus Rath 08e2d65944 feat: let untrusted plugins save attachments and message sources of a given message 2026-10-14 04:16:31 +02:00
Linus Rath 8fd6bc42c3 feat: let plugins read any message, list a folder and see the folders 2026-10-14 04:13:33 +02:00
Linus Rath 121d8ba30c feat: let plugins manage templates, sender signatures and the out-of-office reply 2026-10-14 04:06:06 +02:00
Linus Rath 3df031edd1 feat: let plugins read and change calendar events and tasks 2026-10-14 04:02:47 +02:00
Linus Rath 8ef7ec022f fix: deliver a plugin OAuth callback only to the plugin that started the flow 2026-10-14 03:04:17 +02:00
Linus Rath 2adb1e2b12 fix: size toolbar plugin slots to their content and give composer plugin buttons their own row 2026-10-14 02:53:46 +02:00
Linus Rath cef2438c33 fix: let untrusted plugins upload attachments for the composer again 2026-10-14 02:34:31 +02:00
Linus Rath bb562362b8 fix: lay out plugin slots right-to-left in Arabic, Hebrew and Persian and use the app font size 2026-10-14 02:24:45 +02:00
Linus Rath a21dbc92ec fix: switch running plugins to a new language right away and fall back from de-CH to de 2026-10-14 02:23:40 +02:00
Linus Rath 8f70951555 fix: give plugins their default settings and apply setting changes without a reload 2026-10-14 02:21:45 +02:00
Linus Rath 22ffc36ca9 feat: let plugins run a message-list search with a JMAP filter #1024 2026-10-11 19:39:58 +02:00
Linus Rath d5c0996519 fix: bind the plugin sandbox to its framing window (GHSA-96cx-gx36-3g79) 2026-09-17 20:31:39 +02:00
bartfaizoli76 2adc281a9d feat: add ui.openDialog + 'plugin-dialog' slot for large, clickable custom UI
Every existing plugin slot renders wherever it's placed in the page (a
toolbar row, an attachment's hover-action bar, etc.) and is constrained
by that spot's own CSS - fine for small controls, but there's no way
for a plugin to show a large custom UI (a graphical file browser, a
multi-step wizard) since even a position:fixed overlay rendered from
inside such a slot is clipped to the surrounding row's own layout, not
the real page (confirmed live building the webdav-storage plugin: a
folder browser rendered but was squeezed into an unusable sliver).

Adds:
- host-dialog.ts: new 'custom' DialogKind + awaitCustomDialog(), reusing
  the existing confirm/alert/prompt queue plumbing.
- host-api.ts: 'ui.openDialog' API method.
- runtime.tsx: sandbox-side api.ui.openDialog(opts) proxy.
- plugin-types.ts: new 'plugin-dialog' SlotName - a plugin implements
  this slot with its actual custom UI.
- plugin-dialog-host.tsx: renders the requested slot via the existing
  PluginIframeSlot INSIDE the dialog's real app-root fixed overlay
  (position: fixed; inset: 0, above everything) instead of the
  confirm/prompt form, for kind: 'custom'. Resolves via a generic
  onResult callback in extraProps (marshalled the same way
  composer-attachment-source's onAttach already is - no new plumbing
  needed there).

A plugin calls api.ui.openDialog({ slot: 'plugin-dialog', extraProps,
title, width }) and awaits the Promise, which resolves to whatever its
own slot component passes to props.onResult(), or null if the user
closes the dialog without doing so.
2026-09-05 18:57:49 +02:00
Michael Richter 84617d6186 feat: OAuth handler for plugins; extended contacts and addressbook API 2026-08-31 09:55:06 +02:00
Paulhenry Saux d82a8e5ca5 feat(plugins): new plugin API method: getPublicKeyFromWKD 2026-08-22 20:55:45 +02:00
Paulhenry Saux e2f24e017f fix(plugins) : divide PRF workflow (getOrCreate) into two for better compatibility 2026-08-17 21:43:58 +02:00
Paulhenry Saux a5a12e11bc feat: allow privileged plugin to fetch only a range of blob 2026-08-15 18:28:05 +02:00
Ctaoist 6e792f8834 feat: expose label reordering to extensions 2026-08-13 10:59:45 +08:00
Ctaoist 01ef9240d4 refactor: generalize JMAP keyword capability 2026-08-12 22:47:00 +08:00
Ctaoist cf89eaad27 feat: expose gateway keywords to extensions 2026-08-12 21:40:02 +08:00
Ctaoist ddfd9e43e6 feat: expose JMAP keyword helpers to extensions 2026-08-12 20:43:55 +08:00
Ctaoist f2c379b929 feat: expose label settings to extensions 2026-08-12 15:38:05 +08:00
Paulhenry Saux 80c9d5b377 feat: add jmap.uploadBlob to plugins api 2026-08-09 16:54:26 +02:00
Linus Rath f056db5a05 fix(plugins): make attachment offload workable and keep the write gate closed
Review follow-up to #751.

The offload path could not actually run: onBeforeBlobUpload goes through
HookBus.transform, which capped every handler at the 5s default. A handler
that pushes the file to an external store blows past that, and on timeout
the error is swallowed, the original file id is kept, and the composer
attaches the big file anyway - silently - while three such timeouts in a
minute auto-disable the plugin. Buses can now carry their own per-handler
budget and this one gets 120s. The same ceiling existed inside the sandbox,
where http.post/http.fetch were bounded by the 30s default API timeout;
those two are transfers rather than round-trips and now get 120s as well.

PRIVILEGED_ONLY_METHODS listed 'upfiles.set', which matches no dispatched
method - the dispatcher calls it 'upfiles.save'. So the write was never
tier-gated despite the comment saying it was, and an untrusted plugin with
email:blob-write could replace the bytes of a file the user was about to
send. Fixed the name. Relaxing the read while that stayed open was the
wrong order.

Moving upfiles.get to email:blob-read is kept, but it is a wider grant than
the PR described: onBeforeBlobUpload hands the file id to every registered
handler, so the id is not a secret and any untrusted plugin holding
email:blob-read can read every file the user attaches. The consent copy now
says that; email:blob-write's copy was describing the read and now describes
the replace it actually gates.

Also in the composer offload path:
- sanitize the handler's html before it reaches the document and the sent
  message, and insert it through the editor at the caret rather than
  concatenating it after the signature and quoted block
- honour an abort that lands while the handler is offloading
- delete the right staged file when the handler re-saved it under a new id
  via upfiles.save, which otherwise stranded a copy in IndexedDB forever
2026-08-06 14:08:07 +02:00
Roman Varlakov 600ab02034 feat(plugins): binary Blob/File bodies for api.http.post
api.http.post(path, body, options) now accepts a Blob/File body and sends
it as-is to the same-origin route instead of JSON.stringify-ing it into
'{}'. The plugin may only supply Content-Type and X-Plugin-* metadata
headers; anything else is refused, and the host attaches its credential
headers after the plugin's, so they can never be overridden. Non-Blob
bodies keep the stock JSON behaviour. The sandbox facade previously
dropped the third argument, so the options never reached the host.
2026-08-06 17:25:42 +06:00
Paulhenry Saux 1979c27d9e fix: use correct method 'crypto.getPublicKeys' in crypto plugin API 2026-08-06 07:36:18 +02:00