flare-plugin-sdk 0.0.0-stage → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +131 -2
- package/package.json +33 -4
- package/skill/flare-plugin/ABI.md +334 -0
- package/skill/flare-plugin/README.md +204 -0
- package/skill/flare-plugin/SKILL.md +80 -0
- package/skill/flare-plugin/assets/github-template/README.md +71 -0
- package/skill/flare-plugin/assets/github-template/assets/icon.png +0 -0
- package/skill/flare-plugin/assets/github-template/manifest.json +29 -0
- package/skill/flare-plugin/assets/github-template/plugin.js +108 -0
- package/skill/flare-plugin/examples/pixelfed/README.md +78 -0
- package/skill/flare-plugin/examples/pixelfed/assets/icon.png +0 -0
- package/skill/flare-plugin/examples/pixelfed/locales/en.json +24 -0
- package/skill/flare-plugin/examples/pixelfed/locales/zh-Hans.json +24 -0
- package/skill/flare-plugin/examples/pixelfed/manifest.json +211 -0
- package/skill/flare-plugin/examples/pixelfed/plugin.js +793 -0
- package/skill/flare-plugin/flare-plugin.d.ts +501 -0
- package/skill/flare-plugin/pack.mjs +155 -0
- package/skill/flare-plugin/references/authentication.md +239 -0
- package/skill/flare-plugin/references/capabilities.md +88 -0
- package/skill/flare-plugin/references/manifest.md +192 -0
- package/skill/flare-plugin/references/publishing.md +111 -0
- package/skill/flare-plugin/references/recipes.md +328 -0
- package/skill/flare-plugin/references/runtime-http.md +207 -0
- package/skill/flare-plugin/references/testing.md +176 -0
- package/skill/flare-plugin/references/troubleshooting.md +116 -0
- package/skill/flare-plugin/references/wire-data.md +163 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Detection and authentication recipes
|
|
2
|
+
|
|
3
|
+
These recipes describe the five supported interaction kinds. Website paths and
|
|
4
|
+
helpers in sketches are adaptation points, not universal endpoints. Use the
|
|
5
|
+
[GitHub template](../assets/github-template/plugin.js) for a complete token-login
|
|
6
|
+
implementation and [Pixelfed](../examples/pixelfed/plugin.js) for complete OAuth.
|
|
7
|
+
Never accept a submitted token/password as proof of identity without verifying
|
|
8
|
+
it against the website.
|
|
9
|
+
|
|
10
|
+
## Detection
|
|
11
|
+
|
|
12
|
+
Declare `platform.detector: { "priority": 0 }` only when implementing
|
|
13
|
+
`detector.detect(request, context)`. Inspect a documented endpoint on the
|
|
14
|
+
candidate origin. Return `match: "none"` when evidence does not match;
|
|
15
|
+
`"exact"` for the intended software and `"compatible"` only for known compatible
|
|
16
|
+
behavior. Return the canonical HTTPS origin and optional software/instance data:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
// Result after verifying the website's software identity.
|
|
20
|
+
const detected = {
|
|
21
|
+
match: "exact",
|
|
22
|
+
canonicalOrigin: context.origin,
|
|
23
|
+
software: "ExampleSoftware",
|
|
24
|
+
instance: { domain: context.origin.slice("https://".length), title: "Example" },
|
|
25
|
+
};
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Do not claim every reachable host. Detection has no credential access and may
|
|
29
|
+
request only the candidate origin. Omit the declaration and method if explicit
|
|
30
|
+
platform selection is sufficient, as in the template.
|
|
31
|
+
|
|
32
|
+
## State transitions
|
|
33
|
+
|
|
34
|
+
The method key is the manifest method **ID**, not necessarily its interaction
|
|
35
|
+
name. `id: "token"` registers `login.token.begin`, for example.
|
|
36
|
+
|
|
37
|
+
| Interaction | Calls | Allowed outcome |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| OAuth | begin → browser callback → resume | begin: externalBrowser or success; resume: success |
|
|
40
|
+
| Password | begin(values) | success |
|
|
41
|
+
| CredentialImport | begin(values) | success |
|
|
42
|
+
| Form | begin(values) | success |
|
|
43
|
+
| WebCookie | begin → repeated check(snapshot) | begin: webCookie; check: pending or success |
|
|
44
|
+
|
|
45
|
+
`pending` is not a general multi-step form mechanism. A form is submitted once;
|
|
46
|
+
include all fields required to complete that submission. Expected login errors
|
|
47
|
+
use `flare.error`; do not return an invented `error` transition.
|
|
48
|
+
|
|
49
|
+
All successful methods return:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
// profile and credential come from a verified remote login.
|
|
53
|
+
const success = {
|
|
54
|
+
type: "success",
|
|
55
|
+
value: {
|
|
56
|
+
accountId: profile.key.id,
|
|
57
|
+
origin: context.origin,
|
|
58
|
+
credential,
|
|
59
|
+
profile,
|
|
60
|
+
capabilities: { "flare.datasource.timeline/v1": ["page"] },
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`accountId` equals `profile.key.id`; the profile host equals the selected origin's
|
|
66
|
+
host, including a non-default port. The Host checks origin, account ownership,
|
|
67
|
+
and relogin identity. At least one displayable, declared capability must be
|
|
68
|
+
negotiated with its required operation set. Login can return `composeConfig`
|
|
69
|
+
using the [documented merge rules](manifest.md#compose-defaults). Preserve only credentials needed for later requests;
|
|
70
|
+
avoid retaining a password if the server issues a reusable token instead.
|
|
71
|
+
|
|
72
|
+
## Credential import
|
|
73
|
+
|
|
74
|
+
Use `interaction: "CredentialImport"` and a `Secret` field. The template has the
|
|
75
|
+
full manifest, `/api/me` verification, typed errors, and successful identity.
|
|
76
|
+
Adapt its token acquisition instructions and payload to the actual website.
|
|
77
|
+
Login contexts have no credential store access: return the verified payload;
|
|
78
|
+
Flare persists it. Do not call `flare.credential.replace` during login.
|
|
79
|
+
|
|
80
|
+
## Password
|
|
81
|
+
|
|
82
|
+
Manifest method fragment:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"id": "password",
|
|
87
|
+
"interaction": "Password",
|
|
88
|
+
"title": "Sign in",
|
|
89
|
+
"fields": [
|
|
90
|
+
{ "id": "username", "type": "Username", "label": "Username" },
|
|
91
|
+
{ "id": "password", "type": "Password", "label": "Password" }
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Service sketch (the three helpers are website-specific implementations):
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
const passwordService = {
|
|
100
|
+
async begin(request, context) {
|
|
101
|
+
const token = await exchangePassword(context.origin, request.values.username, request.values.password);
|
|
102
|
+
const profile = await verifiedProfile(context.origin, token);
|
|
103
|
+
return loginSuccess(context, profile, { accessToken: token });
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`exchangePassword` sends structured form/JSON/basic authentication as specified
|
|
109
|
+
by the website and checks the HTTP response. `verifiedProfile` maps the actual
|
|
110
|
+
authenticated identity. `loginSuccess` constructs the success shape above.
|
|
111
|
+
Register this as `login.password`. Report credential rejection with a typed
|
|
112
|
+
AuthenticationRequired or Validation error appropriate to that API.
|
|
113
|
+
|
|
114
|
+
## Form
|
|
115
|
+
|
|
116
|
+
Use a form for a different fixed collection of fields, such as username + OTP:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"id": "otp",
|
|
121
|
+
"interaction": "Form",
|
|
122
|
+
"title": "Sign in with a code",
|
|
123
|
+
"fields": [
|
|
124
|
+
{ "id": "username", "type": "Username", "label": "Username" },
|
|
125
|
+
{ "id": "code", "type": "Otp", "label": "One-time code" }
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
const otpService = {
|
|
132
|
+
async begin(request, context) {
|
|
133
|
+
const credential = await verifyOneTimeCode(context.origin, request.values);
|
|
134
|
+
const profile = await verifiedProfile(context.origin, credential.accessToken);
|
|
135
|
+
return loginSuccess(context, profile, credential);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Implement those website-specific helpers and register as `login.otp`. If the
|
|
141
|
+
website requires a preceding “send code” step, explain how the user obtains the
|
|
142
|
+
code outside this form or choose a supported browser flow. Do not invent dynamic
|
|
143
|
+
field updates, an extra resume call, or a pending-form transition.
|
|
144
|
+
|
|
145
|
+
## OAuth
|
|
146
|
+
|
|
147
|
+
Declare `{ "id": "oauth", "interaction": "OAuth", "title": "Authorize" }`
|
|
148
|
+
without fields or Cookie config. Implement `login.oauth.begin` and `.resume`.
|
|
149
|
+
Add exact `authOrigins` for a separate authorization/token origin when needed;
|
|
150
|
+
this does not expand later account HTTP access.
|
|
151
|
+
|
|
152
|
+
1. `begin` receives Host-owned `flowId`, `state`, `redirectUri`, and selected
|
|
153
|
+
origin. Use the supplied state and redirect URI. Register a client only if
|
|
154
|
+
the provider supports it, otherwise use the provider-approved configuration.
|
|
155
|
+
2. Return an approved HTTPS browser URL and JSON `pendingPayload` containing
|
|
156
|
+
only the bounded data needed to resume (for example client ID and PKCE
|
|
157
|
+
verifier). The begin runtime closes; globals do not carry the flow forward.
|
|
158
|
+
3. `resume` receives Host-validated callback parameters and persisted pending
|
|
159
|
+
payload. Handle the provider's denial/error response. Exchange the code,
|
|
160
|
+
verify the resulting account, and return success.
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
// begin result fragment; build authorizationUrl from the provider's contract.
|
|
164
|
+
const browserTransition = {
|
|
165
|
+
type: "externalBrowser",
|
|
166
|
+
url: authorizationUrl,
|
|
167
|
+
pendingPayload: { clientId, verifier },
|
|
168
|
+
};
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The Host owns callback validation, expiry, persistence, replay protection, and
|
|
172
|
+
relogin identity. It rejects a flow if the running package changed. The plugin
|
|
173
|
+
still implements the provider's token exchange and PKCE requirements; SHA-256
|
|
174
|
+
returns hex, so see [crypto encoding](runtime-http.md#credentials-crypto-and-locale).
|
|
175
|
+
Never hardcode a callback URI from a sample. See Pixelfed for dynamic client
|
|
176
|
+
registration, state propagation, code exchange, and profile verification.
|
|
177
|
+
|
|
178
|
+
## WebCookie
|
|
179
|
+
|
|
180
|
+
Manifest method fragment:
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"id": "browser",
|
|
185
|
+
"interaction": "WebCookie",
|
|
186
|
+
"title": "Sign in in the browser",
|
|
187
|
+
"cookie": {
|
|
188
|
+
"startUrl": "$accountOrigin/login",
|
|
189
|
+
"probes": [{
|
|
190
|
+
"url": "$accountOrigin/api/me",
|
|
191
|
+
"cookies": [{ "name": "session", "required": true }]
|
|
192
|
+
}]
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Service sketch; `fetchVerifiedCookieProfile`, `mapProfile`, and `loginSuccess`
|
|
198
|
+
must be implemented for the selected site's profile API and result shape:
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
const browserService = {
|
|
202
|
+
async begin(request, context) {
|
|
203
|
+
return { type: "webCookie", startUrl: context.origin + "/login" };
|
|
204
|
+
},
|
|
205
|
+
async check(request, context) {
|
|
206
|
+
const probeUrl = context.origin + "/api/me";
|
|
207
|
+
const cookies = request.cookies.cookies
|
|
208
|
+
.filter(cookie => cookie.sourceUrl === probeUrl)
|
|
209
|
+
.map(({ name, value }) => ({ name, value }));
|
|
210
|
+
const profile = await fetchVerifiedCookieProfile(probeUrl, cookies);
|
|
211
|
+
if (!profile) return { type: "pending" };
|
|
212
|
+
return loginSuccess(context, mapProfile(profile, context), { cookies });
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Register as `login.browser`. The returned start URL must exactly match the
|
|
218
|
+
resolved manifest start URL. The Host collects Cookies only for declared probes
|
|
219
|
+
and filters domain/path/HTTPS scope. `sourceUrl` identifies the probe: do not
|
|
220
|
+
combine Cookies from distinct origins merely because their names match. Use
|
|
221
|
+
structured `cookies` in the verification request, and retain only the values
|
|
222
|
+
appropriate for later account-origin requests. Required missing Cookies delay
|
|
223
|
+
checking; a present Cookie still needs server-side identity verification.
|
|
224
|
+
|
|
225
|
+
Return pending only for a verified “not authenticated yet” state. Map rate limits,
|
|
226
|
+
network failures, and malformed responses to their actual typed errors instead
|
|
227
|
+
of silently polling forever. The Host prevents overlapping checks and owns the
|
|
228
|
+
session's disposal; the plugin supplies no lifecycle hook.
|
|
229
|
+
|
|
230
|
+
## Refresh and relogin
|
|
231
|
+
|
|
232
|
+
Refresh access tokens within an account method using the website's documented
|
|
233
|
+
flow and the account's allowed origin. After successful verification, atomically
|
|
234
|
+
replace the full credential payload, retaining required fields. Retry the
|
|
235
|
+
original operation only when safe (especially for writes); avoid unbounded loops.
|
|
236
|
+
An invalid/expired credential that cannot be refreshed should raise
|
|
237
|
+
AuthenticationRequired, which the account adapter routes to relogin. The Host
|
|
238
|
+
requires the same account identity on relogin; schema changes are described in
|
|
239
|
+
[publishing](publishing.md#updates-and-compatibility).
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Capability and UI contract
|
|
2
|
+
|
|
3
|
+
Each ID below has prefix `flare.datasource.` and suffix `/v1`; use the full ID
|
|
4
|
+
in the manifest and negotiated login result. JavaScript methods live under
|
|
5
|
+
`definePlugin({ capabilities: { <service>: ... } })`.
|
|
6
|
+
|
|
7
|
+
| ID middle | Service | Required operations | Additional operations | Host surface / dependency |
|
|
8
|
+
| --- | --- | --- | --- | --- |
|
|
9
|
+
| `timeline` | `timeline` | `page` | None | Static timelines, including at least one default |
|
|
10
|
+
| `search` | `search` | `posts`, `profiles`, `discoverPosts`, `discoverProfiles`, `discoverHashtags` | None | Search/discovery sections |
|
|
11
|
+
| `profile` | `profile` | `byId`, `byHandle` | `timeline`, `following`, `followers` | Profile lookup; tabs additionally require `timeline` and profileTabs |
|
|
12
|
+
| `post` | `post` | `detail` | `context`, `delete`, `mutate` | Internal post detail, thread context, and supported actions |
|
|
13
|
+
| `relation` | `relation` | `state`, `mutate` | None | Follow/block/mute controls; nonempty static relationActions |
|
|
14
|
+
| `compose` | `compose` | `publish` | None | Composer; requires composeDefaults and an account |
|
|
15
|
+
| `notification` | `notification` | At least one of `page`, `badge` | Both may be declared | `page` supplies the list and requires notificationFilters; badge alone is not a displayable login capability |
|
|
16
|
+
| `list` | `list` | `page`, `detail`, `create`, `update`, `delete`, `timeline`, `members`, `memberships`, `addMember`, `removeMember` | None | List browsing and management |
|
|
17
|
+
| `direct-message` | `directMessage` | `rooms`, `room`, `messages`, `send`, `delete`, `leave`, `create`, `badge`, `canSend` | None | DM rooms, conversation, and related account controls |
|
|
18
|
+
| `article` | `article` | `detail`, `comments` | None | Article detail when a post selects article navigation |
|
|
19
|
+
| `gallery` | `gallery` | `detail`, `comments`, `recommendations` | None | Gallery detail when a post selects gallery navigation |
|
|
20
|
+
| `tab-catalog` | `tabCatalog` | `page` | None | Dynamic timeline catalog; also needs timeline.page and a static/default timeline |
|
|
21
|
+
|
|
22
|
+
These sets come from the Host's `PluginAbiV1` contract, not from TypeScript
|
|
23
|
+
`Partial<Service>` declarations. Known capability operations must be implemented
|
|
24
|
+
and declared together. A website without a required operation may need a smaller
|
|
25
|
+
feature set; do not advertise a successful mutation that does nothing. A genuine
|
|
26
|
+
empty comments collection may return an empty ended page; an unsupported remote
|
|
27
|
+
operation should be explained and omitted where the Host allows omission.
|
|
28
|
+
|
|
29
|
+
## Declaration, registration, negotiation
|
|
30
|
+
|
|
31
|
+
For the same timeline, these three shapes are intentionally different:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{ "flare.datasource.timeline/v1": { "operations": { "page": { "directions": ["refresh", "older"] } } } }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
// Registration fragment: include inside the single definePlugin call.
|
|
39
|
+
const timelineService = {
|
|
40
|
+
async page(request, context) {
|
|
41
|
+
return loadTimeline(request.timelineId, request.page, context);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{ "flare.datasource.timeline/v1": ["page"] }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The first is `platform.capabilities`, the second is `capabilities.timeline`
|
|
51
|
+
(with a website-specific `loadTimeline` implementation), and the third is the
|
|
52
|
+
login success's `capabilities`. On login, negotiate only declared, complete sets
|
|
53
|
+
that this account/instance supports. At least one displayable capability is
|
|
54
|
+
required: compose/relation alone and a notification badge alone do not qualify.
|
|
55
|
+
Guest access uses the enabled guest declaration and manifest capabilities instead
|
|
56
|
+
of account login negotiation. See [upgrade negotiation](publishing.md#updates-and-compatibility)
|
|
57
|
+
for how an existing account adopts newly declared operations after an update.
|
|
58
|
+
|
|
59
|
+
## Entry points and actions
|
|
60
|
+
|
|
61
|
+
The Host builds UI from capabilities and static configuration. Implementing an
|
|
62
|
+
undeclared function does not add a UI entry. A static timeline ID is passed as
|
|
63
|
+
`request.timelineId`; profile tabs pass `request.tabId`. Names and parameters are
|
|
64
|
+
not network URLs unless your implementation explicitly interprets them that way.
|
|
65
|
+
|
|
66
|
+
Post actions additionally depend on their `ActionDescriptorV1`, enabled state,
|
|
67
|
+
account, and mutation capability. Set accurate `active`/`count` values even when
|
|
68
|
+
an action is disabled. The Host supplies layout and controls; plugins cannot
|
|
69
|
+
install arbitrary UI or arbitrary action kinds.
|
|
70
|
+
|
|
71
|
+
| `PostV1.detailType` | Click behavior | Required target |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| Omitted or `post` | Internal post detail | post.detail |
|
|
74
|
+
| `article` | Internal article detail | article.detail + comments |
|
|
75
|
+
| `gallery` | Internal gallery detail | gallery.detail + comments + recommendations |
|
|
76
|
+
| `link` | Browser according to user settings | Absolute HTTPS `url`, no internal-detail capability needed |
|
|
77
|
+
|
|
78
|
+
Missing internal target capability makes that click unavailable; it does not
|
|
79
|
+
fall back to another detail type. `link` bypasses internal Deep Link matching.
|
|
80
|
+
Grid covers use the same article/gallery/link navigation; author/actions and
|
|
81
|
+
reposted targets retain their own behavior.
|
|
82
|
+
|
|
83
|
+
The login profile remains the current account's saved identity if profile lookup
|
|
84
|
+
is absent. It does not enable arbitrary profile pages, profile timelines, or
|
|
85
|
+
lookup methods. Use this for a minimal read-only feed such as the template.
|
|
86
|
+
|
|
87
|
+
See [recipes](recipes.md) for every service's inputs, result shape, and adaptation
|
|
88
|
+
points, and [the type declarations](../flare-plugin.d.ts) for full signatures.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Manifest reference
|
|
2
|
+
|
|
3
|
+
`manifest.json` is UTF-8, strict JSON. Unknown fields, comments, and trailing
|
|
4
|
+
commas are rejected. Property names and enum spellings are case-sensitive.
|
|
5
|
+
Defaults below apply when a field is omitted; use omission for optional fields
|
|
6
|
+
rather than sending null for non-nullable values. Start from the complete
|
|
7
|
+
[template manifest](../assets/github-template/manifest.json).
|
|
8
|
+
|
|
9
|
+
## Root object
|
|
10
|
+
|
|
11
|
+
| Field | Required / default | Contract |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `schemaVersion` | `1` | Manifest schema, currently exactly 1 |
|
|
14
|
+
| `apiVersion` | Required | Runtime ABI, currently exactly 1 |
|
|
15
|
+
| `id` | Required | Stable reverse-domain plugin ID; `[a-z][a-z0-9]*(?:\.[a-z][a-z0-9-]*)+` |
|
|
16
|
+
| `version` | Required | SemVer; use stable `major.minor.patch` for GitHub distribution |
|
|
17
|
+
| `defaultLocale` | `"en"` | BCP-47 locale tag |
|
|
18
|
+
| `name` | Required | Manifest text |
|
|
19
|
+
| `description` | Omitted | Manifest text |
|
|
20
|
+
| `permissions` | `{}` | Permission object below |
|
|
21
|
+
| `platform` | Required | Exactly one platform object |
|
|
22
|
+
|
|
23
|
+
Manifest text is either a nonblank string or `{ "key": "plugin.name", "fallback":
|
|
24
|
+
"Example" }`. Every localized key used by the manifest must exist in the default
|
|
25
|
+
locale catalog. Plain strings need no locale file. This encoding differs from
|
|
26
|
+
runtime `WireTextV1`; see [localization](recipes.md#localization).
|
|
27
|
+
|
|
28
|
+
## Permissions
|
|
29
|
+
|
|
30
|
+
| Field | Default | Meaning |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| `authOrigins` | `[]` | Up to 16 additional exact HTTPS origins for login, e.g. `https://login.example.org` |
|
|
33
|
+
| `mediaNetwork` | `false` | Allows automatic remote media loading, including avatars, HTML images, audio/video, and covers |
|
|
34
|
+
|
|
35
|
+
Auth origins have no paths, trailing slash, query, fragment, wildcard, or URL
|
|
36
|
+
credentials. Use normalized host casing and omit the default `:443` port.
|
|
37
|
+
They do not grant account/guest HTTP access to extra origins. Media permission
|
|
38
|
+
allows destinations across websites, including CDNs/federated servers; it does
|
|
39
|
+
not expand the HTTP API. See [origin rules](runtime-http.md#origins-and-permissions).
|
|
40
|
+
|
|
41
|
+
## Platform object
|
|
42
|
+
|
|
43
|
+
| Field | Required / default | Contract |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `id` | Required | Stable platform ID, `[A-Za-z][A-Za-z0-9_.-]{0,63}`; avoid built-in and other plugin platform IDs |
|
|
46
|
+
| `name`, `description` | Name required | Manifest text |
|
|
47
|
+
| `detector` | Omitted | `{ "priority": 0 }`; priority -10000..10000, requires `detector.detect` |
|
|
48
|
+
| `loginMethods` | `[]` | Login declarations below; method IDs unique and at most one method per interaction kind |
|
|
49
|
+
| `capabilities` | `{}` | Capability ID → declaration; up to 32 entries |
|
|
50
|
+
| `timelines` | `[]` | Static timeline declarations; timeline capability requires at least one |
|
|
51
|
+
| `profileTabs` | `[]` | Profile tab declarations; requires `profile.timeline` |
|
|
52
|
+
| `deepLinks` | `[]` | Up to 64 structured rules; see [Deep Links](recipes.md#deep-links) |
|
|
53
|
+
| `composeDefaults` | Omitted | Required conservative defaults when declaring `compose.publish` |
|
|
54
|
+
| `guest` | Omitted | `{ "enabled": true }` enables guest datasource access; object defaults enabled to true |
|
|
55
|
+
| `credentialSchemaVersion` | `1` | Integer 1..1000000; changing it requires existing accounts to log in again |
|
|
56
|
+
| `timelineSchemaVersion` | `1` | Integer 1..1000000; changes timeline identity; see [upgrades](publishing.md#updates-and-compatibility) |
|
|
57
|
+
|
|
58
|
+
Other identifiers (method, field, timeline, tab, parameter, capture) generally use
|
|
59
|
+
`[A-Za-z][A-Za-z0-9_.-]{0,127}`. Parameter objects have at most 32 string values,
|
|
60
|
+
each at most 4096 characters. Plugin/platform IDs have their separate rules above.
|
|
61
|
+
|
|
62
|
+
### Capabilities
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"flare.datasource.timeline/v1": {
|
|
67
|
+
"operations": { "page": { "directions": ["refresh", "older"] } }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
An operation configuration is an object, not a function name or boolean.
|
|
73
|
+
`directions` defaults to `[]`, which the current loader treats as unrestricted;
|
|
74
|
+
declare the actually supported directions explicitly. Non-paging operations use
|
|
75
|
+
`{}`. The [capability matrix](capabilities.md) defines exact names and required
|
|
76
|
+
sets. Known capability declarations with missing methods/operations fail
|
|
77
|
+
installation; unknown capability IDs produce warnings and are ignored.
|
|
78
|
+
|
|
79
|
+
`notificationFilters` belongs on the notification capability declaration and
|
|
80
|
+
must be nonempty when `page` is declared. Values: `all`, `mention`, `comment`,
|
|
81
|
+
`like`. `relationActions` belongs on the relation declaration and must be
|
|
82
|
+
nonempty. Values: `follow`, `block`, `mute`. Both default to empty and are static
|
|
83
|
+
arrays; placing them on another capability is invalid.
|
|
84
|
+
|
|
85
|
+
### Timelines and profile tabs
|
|
86
|
+
|
|
87
|
+
| Field | Timeline default | Profile-tab default |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `id`, `title` | Required ID and manifest text | Required ID and manifest text |
|
|
90
|
+
| `icon` | `"Home"` | `"Profile"` |
|
|
91
|
+
| `display` | `"List"` | `"List"` |
|
|
92
|
+
| `parameters` | `{}` | `{}` |
|
|
93
|
+
| `defaultForNewAccount` | `false` | Not a profile-tab field |
|
|
94
|
+
|
|
95
|
+
IDs must be unique within each list. At least one declared timeline must have
|
|
96
|
+
`defaultForNewAccount: true`. `display` is `List` or `Grid`, not lowercase.
|
|
97
|
+
`tabCatalog.page` still requires a static timeline and `timeline.page`.
|
|
98
|
+
|
|
99
|
+
Icons use these Host semantic names:
|
|
100
|
+
`Home`, `Notification`, `Search`, `Profile`, `Local`, `World`, `Featured`,
|
|
101
|
+
`Bookmark`, `Heart`, `List`, `Messages`, `Channel`, `Like`, `Repost`, `Reply`,
|
|
102
|
+
`Delete`, `Follow`, `Block`, `Mute`, `Info`. The platform icon itself is the
|
|
103
|
+
package's `assets/icon.<extension>` (PNG, JPEG, WebP, GIF or ICO), not one of these names.
|
|
104
|
+
|
|
105
|
+
#### Default and manually added tabs
|
|
106
|
+
|
|
107
|
+
When a new account with timeline access is added, Flare adds every timeline with
|
|
108
|
+
`defaultForNewAccount: true` in manifest order. The first default is also the
|
|
109
|
+
account's main timeline. Multiple defaults are supported; other static timelines
|
|
110
|
+
and dynamic `tabCatalog.page` entries remain available for manual addition.
|
|
111
|
+
|
|
112
|
+
Default tabs follow the same presentation convention as built-in platforms on
|
|
113
|
+
Android, iOS, native macOS and Compose Desktop:
|
|
114
|
+
|
|
115
|
+
| Where the tab appears | Title | Icon |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| First automatically added default | Localized `platform.name` | Packaged platform icon |
|
|
118
|
+
| Other automatically added defaults | Declared timeline `title` | Packaged platform icon |
|
|
119
|
+
| Default timeline in the manual-add list | Declared timeline `title` | Packaged platform icon |
|
|
120
|
+
| Other timelines in the manual-add list | Declared timeline `title` | Declared timeline `icon` |
|
|
121
|
+
|
|
122
|
+
Keep functional titles such as Home, Discover or Bookmarks in the timeline
|
|
123
|
+
declarations; Flare supplies the platform name for the first automatic tab.
|
|
124
|
+
The declared semantic `icon` is not used for default timelines in these lists.
|
|
125
|
+
Timeline IDs, parameters and `List`/`Grid` presentation are unchanged by these rules.
|
|
126
|
+
Profile tabs continue to use their own declared titles and icons.
|
|
127
|
+
|
|
128
|
+
Automatic addition happens only when creating an account. Signing in again or
|
|
129
|
+
updating/reloading a plugin does not recreate existing tabs or overwrite saved
|
|
130
|
+
names, icons, ordering or user customizations. Updated declarations apply to
|
|
131
|
+
newly added tabs.
|
|
132
|
+
|
|
133
|
+
### Login methods
|
|
134
|
+
|
|
135
|
+
Each method has required `id`, `interaction`, `title`; optional `description`;
|
|
136
|
+
`fields` defaults to `[]`; `cookie` is omitted except for WebCookie.
|
|
137
|
+
|
|
138
|
+
| Interaction | Required registration | Configuration |
|
|
139
|
+
| --- | --- | --- |
|
|
140
|
+
| `OAuth` | `login.<id>.begin`, `.resume` | No fields or Cookie configuration |
|
|
141
|
+
| `Password` | `login.<id>.begin` | At least one field |
|
|
142
|
+
| `CredentialImport` | `login.<id>.begin` | At least one field |
|
|
143
|
+
| `Form` | `login.<id>.begin` | At least one field |
|
|
144
|
+
| `WebCookie` | `login.<id>.begin`, `.check` | Cookie configuration; no fields |
|
|
145
|
+
|
|
146
|
+
Fields require `id`, `type`, `label`; `placeholder` is optional and `required`
|
|
147
|
+
defaults to true. Label/placeholder use manifest text. Field types are `Text`,
|
|
148
|
+
`Username`, `Password`, `Otp`, `Secret`. These select Host controls; the plugin
|
|
149
|
+
still validates credentials against the website. The form Host accepts at most
|
|
150
|
+
32 submitted fields, each up to 16384 characters, with only declared field IDs.
|
|
151
|
+
|
|
152
|
+
Cookie configuration contains `startUrl` and `probes`. Each probe has `url` and
|
|
153
|
+
`cookies: [{ "name": "session", "required": true }]`. There must be 1..16
|
|
154
|
+
probes and 1..32 requirements per probe, without duplicate Cookie names within a
|
|
155
|
+
probe. Cookie start/probe URLs use an approved HTTPS origin, or the special
|
|
156
|
+
`$accountOrigin` prefix. That prefix is resolved in Cookie and Deep Link manifest
|
|
157
|
+
configuration; it is not a general string interpolation facility for plugin code.
|
|
158
|
+
See the [complete login recipes](authentication.md).
|
|
159
|
+
|
|
160
|
+
### Compose defaults
|
|
161
|
+
|
|
162
|
+
| Field | Shape and defaults |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| `text` | Optional `{ "maxLength": 500 }`; limit 1..1000000 |
|
|
165
|
+
| `media` | Optional object; required `maxCount` (0..100), `maxBytes` (1..1073741824) |
|
|
166
|
+
| `media.minCountForNew` | Default 0, between 0 and maxCount |
|
|
167
|
+
| `media.supportedMimeTypes` | Default `[]`; up to 128 MIME patterns, including `image/*` |
|
|
168
|
+
| `media.altTextMaxLength` | Default 1500, range 0..100000 |
|
|
169
|
+
| `media.canSensitive` | Default true |
|
|
170
|
+
| `visibility` | Optional `{ "allowed": ["public"], "default": "public" }`; default must be allowed |
|
|
171
|
+
| `contentWarning` | Default false |
|
|
172
|
+
| `poll` | Optional `{ "maxOptions": 4 }`, range 2..100 |
|
|
173
|
+
| `language` | Optional `{ "maxCount": 1 }`, range 1..32 |
|
|
174
|
+
|
|
175
|
+
Visibility values are `public`, `unlisted`, `followers`, `direct`. Describe what
|
|
176
|
+
the website actually supports. Login may return an instance config. When both sides define a section, the Host
|
|
177
|
+
uses the stricter limits, intersects visibility/MIME constraints, and rejects
|
|
178
|
+
empty intersections. When only one side defines an optional section, that section
|
|
179
|
+
is retained; omission is not a prohibition. Content-warning support requires
|
|
180
|
+
both sides when both configs exist. Implement every resulting enabled field in
|
|
181
|
+
`compose.publish`; see [compose](recipes.md#compose).
|
|
182
|
+
|
|
183
|
+
## Frequent invalid combinations
|
|
184
|
+
|
|
185
|
+
- Timeline capability without a static/default timeline.
|
|
186
|
+
- A profile tab without the profile timeline operation.
|
|
187
|
+
- Compose capability without compose defaults, or defaults without the capability.
|
|
188
|
+
- Only `search.posts` declared when the search Host interface requires all five operations.
|
|
189
|
+
- A login method ID that differs from the corresponding `login` object key.
|
|
190
|
+
- Localized manifest keys absent from the default catalog.
|
|
191
|
+
- Extra root properties such as `author`, `homepage`, `entry`, or a trust badge:
|
|
192
|
+
API v1 defines no such manifest fields; put human-facing information in README.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# GitHub publication and upgrades
|
|
2
|
+
|
|
3
|
+
GitHub is the recommended distribution path. Flare installs from a public
|
|
4
|
+
`https://github.com/OWNER/REPOSITORY` URL (optional `.git` suffix), with one plugin
|
|
5
|
+
at the repository root:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
manifest.json
|
|
9
|
+
plugin.js
|
|
10
|
+
assets/icon.png
|
|
11
|
+
locales/en.json # optional; other BCP-47 catalogs as needed
|
|
12
|
+
README.md # author/user instructions, ignored by the importer
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The package must contain exactly one `assets/icon.<extension>`: `png`, `jpg`,
|
|
16
|
+
`jpeg`, `webp`, `gif`, or `ico`. Its extension must match its encoded contents.
|
|
17
|
+
The icon is kept unchanged, limited to 2 MiB and 1024×1024 pixels. SVG is not
|
|
18
|
+
supported. PNG remains the default in the templates.
|
|
19
|
+
|
|
20
|
+
Development files may coexist in the repository. Flare selects the plugin files;
|
|
21
|
+
it does not run a package manager, build script, or client-side bundler. Commit
|
|
22
|
+
the already-built classic script, including all runtime dependencies. A Flare
|
|
23
|
+
monorepo URL pointing to an example subdirectory is not a valid installation URL:
|
|
24
|
+
copy that example's contents into a repository you own first.
|
|
25
|
+
|
|
26
|
+
## Prepare and publish
|
|
27
|
+
|
|
28
|
+
1. Adapt the website endpoints and verify the advertised features. Replace the
|
|
29
|
+
template IDs, name, description, and placeholder icon before publication.
|
|
30
|
+
Keep real tokens, Cookies, passwords, and private fixtures out of Git history.
|
|
31
|
+
2. Put the required files at root and check that a clean checkout contains them.
|
|
32
|
+
Include README instructions for supported website/instances, token acquisition
|
|
33
|
+
if relevant, features, permissions, and tests actually performed.
|
|
34
|
+
3. Set a stable SemVer manifest version, for example `0.1.0`. Publish a tag
|
|
35
|
+
`0.1.0` or `v0.1.0` on the commit with those files. Removing the optional
|
|
36
|
+
**lowercase** `v` must exactly match manifest.version. Follow the same rule for
|
|
37
|
+
any permitted build metadata; plain major.minor.patch is the simplest choice.
|
|
38
|
+
4. Share the repository URL. A GitHub Release page and uploaded `.fpp` asset are
|
|
39
|
+
not required. Creating/pushing the repository or tag follows the user's
|
|
40
|
+
authorization; a generated project alone is not permission to publish it.
|
|
41
|
+
5. The user enters the repository URL in Flare plugin management and clicks
|
|
42
|
+
**Install**. Flare selects and downloads the latest stable version, then shows
|
|
43
|
+
source and permission review in the same dialog. Only confirmation installs
|
|
44
|
+
the plugin; cancellation discards the download. Errors stay in the dialog for
|
|
45
|
+
retry. Successful installs take effect immediately; verify the installed
|
|
46
|
+
tag/version and running behavior without restarting Flare.
|
|
47
|
+
|
|
48
|
+
Private repositories, arbitrary branch tracking, prerelease tags, automatic
|
|
49
|
+
installation, and build-on-device are unsupported. A public repo without a stable
|
|
50
|
+
version tag has no installable version, even if its default branch has valid files.
|
|
51
|
+
|
|
52
|
+
Flare resolves the latest stable tag to a commit SHA and downloads that source ZIP.
|
|
53
|
+
Archive checks also inspect ignored development files for unsafe paths/symlinks,
|
|
54
|
+
duplicates, multiple roots, and size limits. Do not commit node_modules, build
|
|
55
|
+
caches, or unrelated large assets. See [limits](troubleshooting.md#limits).
|
|
56
|
+
|
|
57
|
+
## Updates and compatibility
|
|
58
|
+
|
|
59
|
+
Increment manifest.version and publish a new stable tag. Preserve plugin ID,
|
|
60
|
+
platform ID, and the GitHub repository identity for an in-place update. Flare
|
|
61
|
+
records repository ID, owner/name, tag, and SHA. A moved tag/source change or new
|
|
62
|
+
permission is surfaced in review; prefer a new tag over rewriting a published
|
|
63
|
+
one. Failure/cancellation leaves the installed package intact.
|
|
64
|
+
|
|
65
|
+
Opening management checks for updates using a five-minute metadata cache;
|
|
66
|
+
manual checking bypasses it. Discovery only offers an update; download/review/
|
|
67
|
+
confirmation are separate steps. GitHub metadata uses global proxy routing and
|
|
68
|
+
rate-limit handling, not plugin account credentials. Excessive tag lists produce
|
|
69
|
+
an error rather than an incomplete “latest version” answer.
|
|
70
|
+
|
|
71
|
+
Install, update, and uninstall take effect immediately. Installed plugins are always enabled. There are no plugin lifecycle/migration callbacks. Media
|
|
72
|
+
grants follow the running package; cached media retains plugin ownership and
|
|
73
|
+
rechecks that package's permission. Missing plugins cannot restore their media.
|
|
74
|
+
Uninstall records removal before account cleanup. Once that record is saved,
|
|
75
|
+
account-cleanup or final-index-write failures keep the plugin stopped across
|
|
76
|
+
restart; retry deletion from management to finish. Updating or reinstalling
|
|
77
|
+
cannot bypass incomplete removal. Unused package-file cleanup can be retried
|
|
78
|
+
separately from management after deletion completes.
|
|
79
|
+
Publishing controls follow the current account capabilities after replacement or
|
|
80
|
+
relogin, without requiring the account list to change.
|
|
81
|
+
|
|
82
|
+
| Change | Author consequence |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| Credential payload format becomes incompatible | Increment credentialSchemaVersion; existing accounts require relogin, with no plugin migration hook |
|
|
85
|
+
| Timeline IDs/parameters change incompatibly | Consider timelineSchemaVersion; it changes timeline identities and retires old specs. Explain that existing selections may need reconfiguration |
|
|
86
|
+
| Capabilities removed | Running capabilities are filtered by the new manifest |
|
|
87
|
+
| Operations newly declared compared with the account's saved manifest | Current Host admits those new operations without re-negotiating login; do not assume a new login will gate them |
|
|
88
|
+
| Previously declared operation was not negotiated for the account | It remains unavailable unless account negotiation changes |
|
|
89
|
+
| Package hash changes | Account adapter invalidates cached content when refreshing its persisted package snapshot |
|
|
90
|
+
|
|
91
|
+
Treat website/credential compatibility deliberately. A version bump alone does
|
|
92
|
+
not convert stored credentials. Document new permissions and user-visible changes.
|
|
93
|
+
The Host's own cache-database migration is separate from a plugin's versioning;
|
|
94
|
+
plugin authors do not control it.
|
|
95
|
+
|
|
96
|
+
## Optional local installation
|
|
97
|
+
|
|
98
|
+
For offline development or local transfer, the skill includes Node's standard-
|
|
99
|
+
library-only packer. From this skill directory:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
node pack.mjs assets/github-template /tmp/example-feed-0.1.0.fpp
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
For an adapted plugin repository, create a staging directory containing only
|
|
106
|
+
manifest.json, plugin.js, one supported assets/icon.<extension>, and optional locales/*.json, then pass
|
|
107
|
+
that directory to pack.mjs. The packer ignores only a root README.md; unlike
|
|
108
|
+
GitHub import it rejects other development files, including .git, tests, and type
|
|
109
|
+
declarations. Keep the output outside the input directory. Packaging success is
|
|
110
|
+
not full manifest/registration/behavior validation. Local packages are unverified
|
|
111
|
+
and still require review; installation takes effect immediately and no GitHub source/update tracking is recorded.
|