tags are how the marketplace is browsed rather than scrolled: a list of them on the left, the plugins that carry one on the right. a closed vocabulary (PLUGIN_TAGS in the platform) because free text becomes homelab, home-lab, Home Lab and selfhosted inside a month, and a filter list of synonyms is worse than no filter at all. declared here rather than curated in the catalogue, so a plugin can say what it is and nobody edits a table when one ships. the marketplace caches it on sync, exactly as it already does for label and summary.
92 lines
4.2 KiB
TypeScript
92 lines
4.2 KiB
TypeScript
import type { PluginManifest } from '@@/plugins/manifest';
|
|
|
|
/**
|
|
* Calendar and contacts, over CalDAV and CardDAV.
|
|
*
|
|
* One sidecar (Radicale on loopback), one permission, one install — and TWO screens, because Calendar and
|
|
* Contacts are two places you go rather than two sections of one page. That is what `screens` below is
|
|
* for, and caldav is the first plugin to use it.
|
|
*/
|
|
export const manifest: PluginManifest = {
|
|
publisher: 'officerdev',
|
|
version: '1.0.0',
|
|
platform: '>=1.0.0',
|
|
|
|
/**
|
|
* What it is called in the store, in words that assume nothing.
|
|
*
|
|
* Not "Calendar", which is one of its two screens and undersells it; not "CalDAV" or "caldav", which
|
|
* are protocol names a person browsing has no reason to know. The convention across the store is that
|
|
* a plugin is named for what you get, not for how it works.
|
|
*/
|
|
label: 'Contacts and Calendar',
|
|
summary: 'Calendars and contacts on your own machine, synced to your phone over CalDAV and CardDAV.',
|
|
color: '#ef4444',
|
|
|
|
/**
|
|
* Personal because that is what it holds; Degoogling because replacing Google Calendar and Google
|
|
* Contacts is the reason most people would install it.
|
|
*/
|
|
tags: ['Personal', 'Degoogling'],
|
|
|
|
/**
|
|
* Two screens at URLs this plugin chose — `/calendar` and `/contacts`, not `/caldav/calendar`.
|
|
*
|
|
* They were core routes until 2026-08-27 and keeping them is not cosmetic: a bookmark, a dock tile a
|
|
* user has arranged, and the layout persisted under `screens/plugin/caldav/...` all address them.
|
|
*
|
|
* Only a first-party plugin may choose its own segment — a third-party one is confined to
|
|
* `/p/<publisher>/` so it cannot shadow a core route. Each ships its own artwork as
|
|
* `assets/<segment>.png`, because two identical tiles side by side say nothing about which is which.
|
|
*/
|
|
screens: [
|
|
{ segment: 'calendar', label: 'Calendar', icon: 'CalendarDays' },
|
|
{ segment: 'contacts', label: 'Contacts', icon: 'Contact' },
|
|
],
|
|
|
|
permissions: [
|
|
{
|
|
key: 'calendar',
|
|
label: 'Calendar and contacts',
|
|
description: 'Calendars, contacts, and the app passwords that sync them to a phone',
|
|
// App passwords are minted for and revoked by their own owner, and are how a member's own phone
|
|
// reaches their own calendar. Nothing under here is shared, so a read grant may still write them.
|
|
personal: ['/passwords', '/provision'],
|
|
},
|
|
],
|
|
|
|
/**
|
|
* The top-level doors, above the account gate — and the reason is that phones are not browsers.
|
|
*
|
|
* DAVx5, iOS and Thunderbird send HTTP Basic on every request and have nowhere to put a platform JWT,
|
|
* so the account gate would 401 every real client. `sync-router` demands its own credential instead: a
|
|
* scoped DAV app password, resolved to one user, whose id is then what Radicale is told the principal
|
|
* is. "Unauthenticated" here means "not authenticated by the PLATFORM's session", not "open".
|
|
*
|
|
* The two `.well-known` names are RFC 6764 autodiscovery: the client is given a bare domain and probes
|
|
* them before it has any credential at all. Without them iOS degrades to demanding a full collection
|
|
* URL, which is exactly the sort of thing that makes self-hosting feel worse than the product it
|
|
* replaces.
|
|
*
|
|
* Every one of these was the platform's until this plugin existed, and each is in `RESERVED_ROOT` for
|
|
* that reason — claimable by a first-party plugin, refused to everyone else.
|
|
*/
|
|
unauthenticatedRootPaths: ['/dav', '/.well-known/caldav', '/.well-known/carddav'],
|
|
|
|
/**
|
|
* Radicale — a ~2MB Python package, spawned on loopback by this plugin's sidecar.
|
|
*
|
|
* Not a container: it was one only because the old app store made everything one. No Homebrew formula
|
|
* exists (confirmed live: "No available formula with the name radicale"), so a macOS dev install has
|
|
* nowhere for the sidecar to find it — which is why `resolveRadicale()` failing is handled rather than
|
|
* fatal.
|
|
*/
|
|
osDependencies: [
|
|
{
|
|
binary: 'radicale',
|
|
reason: 'Serves the CalDAV and CardDAV collections. Without it there are no calendars and no contacts.',
|
|
packages: { apt: 'radicale', pacman: 'radicale', dnf: 'radicale' },
|
|
},
|
|
],
|
|
};
|