Aller au contenu

Custom widgets: for administrators

Documentation 3 min de lecture Source sur codefloeEn anglais uniquement

Sur cette page

Custom widgets let an administrator put their own small web pages, or a third party's embed code, into the widget column beside the built-in widgets, and choose which widgets new accounts start with. They are in Control Panel > Community > Widgets (/admin/widgets), for administrators only: moderators cannot see the page or call its endpoints.

Everything on this page was checked in Chromium 151 and Firefox 157. Safari (WebKit) was not tested, and where the two browsers tested behaved differently, Safari may differ again.

What a widget can and cannot do

A widget can show text, images and styles, and, if you allow scripts, run scripts of its own. Its links open as links in the app.

Each widget runs in its own sealed frame. Whatever its code, a widget:

  • cannot see who is looking at it: it is never given a user name, user id, token or the address of the page around it;
  • cannot read or change the page around it, this server's cookies or storage, or another widget;
  • cannot call this server's API: its requests to this server are refused by the frame's policy, apart from images, which carry no cookie or credentials;
  • cannot draw outside its own box, and grows only up to its maximum height;
  • cannot open windows or dialogs, submit forms, start downloads or navigate the page around it.

What it is given: the 22 theme colours, the font and font size (as CSS variables, listed under CSS variables a widget can use on the page), the language, and light or dark. They follow the theme when it changes.

Links. A click on a link in a widget is passed to the app. A link to a page of this server opens in the app. Any other link opens in a new tab with no connection back to the app, after the external-link warning if the user has turned on Show warning when opening external URLs. A widget's script can only open a link within a few seconds of a real click or key press on the page, and at most three in five seconds.

The two kinds

  • Custom HTML: your HTML, with CSS and, if allowed, JavaScript. You can paste a third party's embed code here; the hosts it loads from must be listed (see below).
  • External frame: one page on another server, shown as it is, at a fixed height. Use this when an embed code is a single <iframe>: such embeds do not work as custom HTML in most browsers, and the editor offers to convert them. Only the address is stored: https, no port or user name, on a hostname other than this server's. The page runs in its own site's origin, so it keeps its own cookies and storage, and it cannot reach the app around it. Windows it opens stay sandboxed (no forms in them).

The scripts switch

Off by default. With it off, the widget's own scripts, inline handlers such as onclick, javascript: links and script files, including those from the sites you allow, do not run. Only the frame's own fixed script runs (height, theme and links).

With scripts off, the server also refuses HTML that would contact another site on its own, before any viewer has agreed. Browsers do this for two things the frame's policy does not stop:

  • any <meta http-equiv>, since <meta http-equiv="refresh"> sends the frame to another site as soon as it loads;
  • an <iframe> or <frame> whose address is not https on one of the widget's allowed sites, or that writes its content inline (srcdoc). Chromium contacts a frame's site even when it then blocks the frame.

To be sure of the second rule, the server counts every <iframe and <frame in the text, including ones in comments, scripts and SVG, and refuses the widget when they do not all turn out to be frames it could check. Reword such mentions, or remove them.

With scripts on, none of this can be checked: a script can do it at run time. Turn scripts on only for code you trust.

Hosts, and what users see

A widget of kind Custom HTML may load from at most 20 other sites ("hosts"). Each is a bare hostname over https, such as cdn.example.com: no path, port, wildcard, IP address or this server's own hostname. A subdomain is a separate host. The editor lists the hosts it finds in your code, and the preview names each one the browser blocked, with an Allow button. Nothing is allowed until you choose it.

The widget's frame may then load styles, images, fonts, media and frames from those hosts and connect to them, and with scripts on, load scripts from them. A widget with scripts on can use all of its hosts in every way they are allowed, as often as it likes, for as long as it is on screen. Every viewer's IP address and browser details reach those sites. A script there can also learn the viewer's languages, time zone, screen size and the widget's own address (which names this server and the widget), and send them on. It cannot learn who the viewer is from this server.

Users meet a widget with hosts behind a placeholder that names its sites, with Load (for this tab) and Always load. Nothing is requested from those sites, nor the widget itself, until they choose. While it is loaded, the title bar names the sites; such a widget always keeps its title bar, even with Show title bar off. That placeholder and the title bar are the only places a viewer sees which sites a widget contacts. Each user can set Server widgets that load content from other sites to Ask, Always load or Never load, in Settings > Privacy > Content from other sites. That setting is per device, and an administrator cannot change it.

A widget without hosts loads at once, with no placeholder. It contacts this server only, with one exception: with scripts on, a widget can send its own frame to another site. That request reaches the site, and the widget is then removed from the page with a notice, once per page load. A widget with scripts on cannot open a WebRTC connection: the frame removes it before your code runs.

An External frame always counts as contacting its site, and the placeholder names it. Only its first address is checked: the third-party page can send its frame elsewhere, this server included. Checked in Chromium and Firefox: the app's own pages refuse to be framed there; a widget frame loads but stays sealed; an uploaded file is either an image or a download the frame refuses, and its policy stops any script in it. An /embed/ page would load with this server's own origin, so it can tell when another site's page has put it inside the app, and then stops before any of its code runs. Embeds on other sites, and the preview in the app's embed code dialog, are not affected. Any route added later that serves pages on this hostname must refuse to be framed, stay sealed, or stop itself in the same way.

Default layouts

The Default layout tab sets the widgets new accounts start with: built-in widgets and custom ones, each with its own settings. Until a layout is saved, new accounts get the built-in three (calendar, notifications, trending), and nothing changes when you look at the tab. The sidebar shows one list: the left column's widgets first, then the right column's.

  • New accounts (created after the first layout was saved) start with exactly the layout, in its order.
  • Existing accounts keep the widgets they have. An entry reaches them only if Also add for existing users is switched on for it: then it is added once, after the widgets they already have, the next time they open the app on each device. An entry without the switch reaches only accounts created after the entry was added.
  • Once means once. A widget a user removes on a device does not come back there. Removing an entry from the layout changes nobody's sidebar.

Layouts are per device. A user's widgets are stored in their browser and are not synced between devices, so a user who signs in on a second device gets the layout there too, whatever they removed on the first. The deck is not affected: deck users add server widgets from a widgets column's picker.

A custom widget that is switched off keeps its entry in the layout, but nobody gets it while it is off. Deleting a custom widget removes its entries from the layout.

Turning a widget off or deleting it

An inactive or deleted widget is no longer offered, and leaves the widgets of every open tab within a few seconds, without a reload: after each change the server tells the tabs connected to it, and the widget's frame is taken off the page. A tab whose connection to the server was down at that moment asks for the list again within about ten seconds of reconnecting. A page loaded after the change never shows it. Saving new code for a widget loads its frame again in open tabs, with the new code. To stop a widget doing harm, switch it off or delete it.

A widget's frame is sent with Cache-Control: no-cache and an ETag, so the browser, and any cache in front of the server, asks the server each time a frame loads. An unchanged widget gets a short "not modified" answer; a widget that was switched off gets nothing, even in a tab that has not heard of the change yet.

Saving the default layout reaches open tabs too, but changes nobody's widgets there: the layout is applied when a page loads (see above).

Every create, update and delete is in the moderation log. An update records the host list before and after.

Safe mode

If a widget makes a tab unusable, for instance a script that never stops, open the app with ?safe_mode=no_widgets at the end of its address:

  • https://example.social/?safe_mode=no_widgets for a user, who can then remove the widget: Edit widgets shows each server widget as "Server widgets are off in this tab (safe mode)", with its remove button;
  • https://example.social/admin/widgets?safe_mode=no_widgets for you, to switch the widget off or fix it. The preview is off in such a tab too, since the code you came to fix may be what hangs it.

That tab starts without any server widget and requests no widget frame. It stays so while you move around the app; reloading the page without the parameter ends it. The home timeline rewrites its own address a few seconds after it loads, which drops the parameter, so a reload there ends safe mode as well. The default layout is not applied in a safe-mode tab; the next page load outside it applies it as usual.

Safe mode is not linked from anywhere in the app: pass the address on to a user who needs it.

Limits

What Limit
Custom widgets per server 30
HTML per widget 64 KB (UTF-8)
Hosts per widget 20
Height 1 to 800 px (the maximum for Custom HTML, the fixed height for External frame)
Name 128 characters
External frame address 2048 characters
Default layout 50 entries, each with at most 4 KB of settings
Preview 30 a minute per administrator, each kept for ten minutes

Remote accounts (signed in to another server through this one) and visitors never see custom widgets.

For API users

The endpoints are admin/custom-widgets/{create,update,delete,list,preview}, for administrators only. An access token needs the permission Manage custom widgets (write:admin:custom-widgets) or View custom widgets (read:admin:custom-widgets). The default layout is saved through admin/update-meta (defaultWidgets).

After each write, every streaming connection, signed in or not, receives the broadcast event customWidgetsUpdated with the fields of the detailed meta that changed: customWidgets (the active widgets, without their HTML), defaultWidgets (without entries for inactive widgets) and defaultWidgetsSince. A client can put these in place of its copy of meta instead of asking for meta again.

Database

One additive migration (its file name ends in custom-widget): the custom_widget table, and meta.defaultWidgets and meta.defaultWidgetsSince. Nothing is dropped or renamed (see Downgrading a Catodon instance to Sharkey).

What was not verified

  • Safari, as above.
  • DNS lookups from <link rel="dns-prefetch"> could not be observed on the test setup. Connections from it and from preconnect were looked for, and there were none.