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,328 @@
|
|
|
1
|
+
# Capability recipes
|
|
2
|
+
|
|
3
|
+
Use the [capability matrix](capabilities.md) for required operation sets and
|
|
4
|
+
[type declarations](../flare-plugin.d.ts) for full signatures. Every method also
|
|
5
|
+
receives `context`. Tables below describe all twelve services. Code fragments
|
|
6
|
+
use site-specific helpers such as `api`, `mapPost`, and `mapProfile`; implement
|
|
7
|
+
them from the website contract and [HTTP rules](runtime-http.md), then place
|
|
8
|
+
the service in the single `definePlugin` registration. These are adaptation
|
|
9
|
+
recipes, not seventeen separate installable plugins.
|
|
10
|
+
|
|
11
|
+
## Timeline
|
|
12
|
+
|
|
13
|
+
`timeline.page(TimelinePageRequestV1)` returns `PageV1<PostV1>`. Declare a static
|
|
14
|
+
default timeline and explicit supported directions. Read `timelineId`,
|
|
15
|
+
`parameters`, and the nested `page`, route to the corresponding website endpoint,
|
|
16
|
+
and map its continuation to olderCursor/newerCursor. The
|
|
17
|
+
[template](../assets/github-template/plugin.js) implements refresh/older paging,
|
|
18
|
+
empty pages, credential reads, typed errors, and browser-link posts.
|
|
19
|
+
|
|
20
|
+
For multiple timelines, use stable IDs such as home/local/bookmarks and a bounded
|
|
21
|
+
switch over those IDs. Unknown IDs should produce Unsupported, not accidentally
|
|
22
|
+
load the default timeline. Dynamic IDs come from [tab catalog](#tab-catalog).
|
|
23
|
+
Test refresh, a second page, an empty continued page, and genuine exhaustion.
|
|
24
|
+
For automatic tab selection, platform names/icons and manual-add labels, follow
|
|
25
|
+
the [tab presentation rules](manifest.md#default-and-manually-added-tabs).
|
|
26
|
+
|
|
27
|
+
## Search
|
|
28
|
+
|
|
29
|
+
All five methods receive `SearchRequestV1 { query, page }`:
|
|
30
|
+
|
|
31
|
+
| Method | Return |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| posts | PageV1<PostV1> |
|
|
34
|
+
| profiles | PageV1<ProfileV1> |
|
|
35
|
+
| discoverPosts | PageV1<PostV1> |
|
|
36
|
+
| discoverProfiles | PageV1<ProfileV1> |
|
|
37
|
+
| discoverHashtags | PageV1<HashtagV1> |
|
|
38
|
+
|
|
39
|
+
Declare and implement all five to expose the current search interface. Map the
|
|
40
|
+
website's different search/discovery endpoints rather than forwarding a search
|
|
41
|
+
result envelope. Encode queries as URL parameters, and keep paging cursors
|
|
42
|
+
specific to the query and result type. Discovery may use an empty query; do not
|
|
43
|
+
invent a hidden required query that the Host cannot provide. See Pixelfed's
|
|
44
|
+
search service for complete examples. Test non-ASCII queries and empty results.
|
|
45
|
+
|
|
46
|
+
## Profile
|
|
47
|
+
|
|
48
|
+
| Method | Input | Return |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| byId | key + optional entityToken | ProfileV1 |
|
|
51
|
+
| byHandle | handle, host | ProfileV1 |
|
|
52
|
+
| timeline | profile key, optional tabId, parameters, page | PageV1<PostV1> |
|
|
53
|
+
| following / followers | key, optional entityToken, page | PageV1<ProfileV1> |
|
|
54
|
+
|
|
55
|
+
byId/byHandle are required together; the other operations are optional. Preserve
|
|
56
|
+
the website's canonical identity while translating handles to IDs. byHandle's
|
|
57
|
+
host is lookup data, not permission to request an arbitrary origin. A profile
|
|
58
|
+
tab requires both a manifest profileTabs entry and the timeline operation;
|
|
59
|
+
`display: "Grid"` changes presentation, not the return type. Map media-only
|
|
60
|
+
tabs through parameters as Pixelfed does. Test equivalent handles, a missing
|
|
61
|
+
profile, and stable keys between login and lookup.
|
|
62
|
+
|
|
63
|
+
## Post and semantic actions
|
|
64
|
+
|
|
65
|
+
| Method | Input | Return |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| detail | EntityRequestV1 | PostV1 |
|
|
68
|
+
| context | EntityPageRequestV1 | PageV1<PostV1> |
|
|
69
|
+
| delete | EntityRequestV1 | deleted or noChange |
|
|
70
|
+
| mutate | MutationRequestV1 | updatedPost, deleted, invalidate, or noChange |
|
|
71
|
+
|
|
72
|
+
Only detail is required. Map context into the site's meaningful thread order.
|
|
73
|
+
Attach supported semantic actions to returned posts:
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
const actions = [{
|
|
77
|
+
action: remote.favourited ? "Unfavourite" : "Favourite",
|
|
78
|
+
active: remote.favourited,
|
|
79
|
+
enabled: true,
|
|
80
|
+
count: remote.favourites_count,
|
|
81
|
+
actionToken: remote.action_token,
|
|
82
|
+
}];
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`mutate` receives `{ key, action, actionToken?, parameters }`. Map the semantic
|
|
86
|
+
action to a verified endpoint, including the inverse action; do not send the
|
|
87
|
+
enum name as a URL without a documented mapping. It is distinct from `delete`.
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
const postServiceFragment = {
|
|
91
|
+
async mutate(request, context) {
|
|
92
|
+
const updated = await mutateWebsitePost(request, context);
|
|
93
|
+
return { type: "updatedPost", post: mapPost(updated, context) };
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Return updatedPost when the server supplies authoritative new state. Return
|
|
99
|
+
`{ type: "invalidate", keys: [request.key] }` when affected content must be
|
|
100
|
+
reloaded; `deleted` only after deletion, and `noChange` for a successful outcome
|
|
101
|
+
that supplies no replacement state. A remote failure should throw, enabling the
|
|
102
|
+
Host's failure behavior. Do not return updatedProfile from post.mutate. Updated
|
|
103
|
+
post keys must match the requested entity; invalidation keys must be unique.
|
|
104
|
+
Test action/inverse, disabled active state, failure, and views of the same entity.
|
|
105
|
+
|
|
106
|
+
## Relation
|
|
107
|
+
|
|
108
|
+
Declare state and mutate, plus static `relationActions`, for example:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"operations": { "state": {}, "mutate": {} },
|
|
113
|
+
"relationActions": ["follow", "block", "mute"]
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`state(EntityRequestV1)` returns RelationV1; `mutate(MutationRequestV1)` returns
|
|
118
|
+
updatedRelation or noChange. Supported action pairs are Follow/Unfollow,
|
|
119
|
+
Block/Unblock, Mute/Unmute. Return the **complete authoritative relation** after
|
|
120
|
+
a mutation, including any server-side side effects (blocking may also unfollow)
|
|
121
|
+
and replacement actionTokens. Missing relation booleans default false, so a
|
|
122
|
+
partial updatedRelation can clear real state.
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
const result = {
|
|
126
|
+
type: "updatedRelation",
|
|
127
|
+
relation: {
|
|
128
|
+
profileKey: request.key,
|
|
129
|
+
following: server.following,
|
|
130
|
+
followedBy: server.followed_by,
|
|
131
|
+
blocking: server.blocking,
|
|
132
|
+
muting: server.muting,
|
|
133
|
+
actionTokens: server.next_action_tokens,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Validate/map `server.next_action_tokens` to known semantic action names. Test all
|
|
139
|
+
declared action pairs and account isolation; do not maintain durable relation
|
|
140
|
+
state in plugin globals.
|
|
141
|
+
|
|
142
|
+
## Compose
|
|
143
|
+
|
|
144
|
+
Declare publish and conservative composeDefaults. `compose.publish` receives
|
|
145
|
+
text, visibility, languages, assets, sensitive, optional spoilerText/replyTo/poll,
|
|
146
|
+
and returns `{ post: PostV1 }`.
|
|
147
|
+
|
|
148
|
+
1. Translate supported fields to the website's payload. Reply targets are keys;
|
|
149
|
+
do not confuse them with URLs. Poll duration is seconds, not milliseconds.
|
|
150
|
+
2. Upload assets using the Host multipart handle API. Check each result and use
|
|
151
|
+
the returned remote media IDs. Honor alt text, MIME, size, and count constraints.
|
|
152
|
+
3. Publish and map the authoritative returned post. Handle partial upload failure
|
|
153
|
+
without reporting that the post was published. Use the website's idempotency
|
|
154
|
+
facility when available; do not blindly retry an ambiguous write.
|
|
155
|
+
|
|
156
|
+
See [multipart](runtime-http.md#body-recipes) and Pixelfed's complete publish
|
|
157
|
+
implementation. Only enable controls the adapter handles. Login/manifest config
|
|
158
|
+
merging is described in [compose defaults](manifest.md#compose-defaults).
|
|
159
|
+
Test text-only where allowed, required-media constraints, an upload failure,
|
|
160
|
+
alt text, reply, visibility, and resulting post navigation.
|
|
161
|
+
|
|
162
|
+
## Notification
|
|
163
|
+
|
|
164
|
+
`notification.page({ filterId?, page })` returns `PageV1<NotificationV1>`;
|
|
165
|
+
`badge({})` returns `{ value: nonnegativeCount }`. Each may be declared separately,
|
|
166
|
+
but page requires a nonempty static notificationFilters list.
|
|
167
|
+
|
|
168
|
+
Filter IDs are `all`, `mention`, `comment`, `like`. Map them to the site's API.
|
|
169
|
+
Result kinds instead use `Mention`, `Reply`, `Favourite`, `Repost`, `Follow`,
|
|
170
|
+
`Other`. Preserve the notification's ID, actor, timestamp, custom message, and
|
|
171
|
+
post/repost presentation. Two events referencing one post still need distinct
|
|
172
|
+
notification IDs. Test those events together and filters with empty pages.
|
|
173
|
+
|
|
174
|
+
## List
|
|
175
|
+
|
|
176
|
+
The Host requires all list operations:
|
|
177
|
+
|
|
178
|
+
| Method | Input | Return |
|
|
179
|
+
| --- | --- | --- |
|
|
180
|
+
| page | PageRequestV1 directly | PageV1<SocialListV1> |
|
|
181
|
+
| detail | EntityRequestV1 | SocialListV1 |
|
|
182
|
+
| create / update | `{ id?, title?, entityToken? }` | SocialListV1 |
|
|
183
|
+
| delete | Same mutation request | deleted or noChange |
|
|
184
|
+
| timeline | EntityPageRequestV1 | PageV1<PostV1> |
|
|
185
|
+
| members / memberships | EntityPageRequestV1 | PageV1<ProfileV1> / PageV1<SocialListV1> |
|
|
186
|
+
| addMember | `{ listId, profileKey, entityToken? }` | ProfileV1 |
|
|
187
|
+
| removeMember | Same member request | deleted or noChange |
|
|
188
|
+
|
|
189
|
+
For memberships, the entity is the profile whose memberships are requested;
|
|
190
|
+
for list detail/timeline/members it identifies the list. Keep list IDs stable
|
|
191
|
+
across creation, editing, and paging. Preserve entityToken where the website
|
|
192
|
+
needs revision data. Validate title and operation-specific optional fields;
|
|
193
|
+
an optional TypeScript property does not mean every operation can omit it.
|
|
194
|
+
Test create → add member → members/memberships → remove member → delete.
|
|
195
|
+
|
|
196
|
+
## Direct message
|
|
197
|
+
|
|
198
|
+
All methods are required:
|
|
199
|
+
|
|
200
|
+
| Method | Input | Return |
|
|
201
|
+
| --- | --- | --- |
|
|
202
|
+
| rooms | PageRequestV1 directly | PageV1<DirectMessageRoomV1> |
|
|
203
|
+
| room | EntityRequestV1 | DirectMessageRoomV1 |
|
|
204
|
+
| messages | roomKey, page, optional roomEntityToken | PageV1<DirectMessageV1> |
|
|
205
|
+
| send | roomKey, text, optional roomEntityToken | DirectMessageV1 |
|
|
206
|
+
| delete | roomKey, messageKey, optional roomEntityToken/messageEntityToken | deleted or noChange |
|
|
207
|
+
| leave | EntityRequestV1 | deleted or noChange |
|
|
208
|
+
| create | EntityRequestV1 identifying the other profile | DirectMessageRoomV1 |
|
|
209
|
+
| badge | Empty object | `{ value: count }` |
|
|
210
|
+
| canSend | EntityRequestV1 identifying the other profile | `{ value: boolean }` |
|
|
211
|
+
|
|
212
|
+
Populate participants, sender, roomKey, and fromCurrentAccount accurately.
|
|
213
|
+
Messages and a room's lastMessage must belong to that room. `send` in v1 takes
|
|
214
|
+
text, not an invented media-attachment array.
|
|
215
|
+
|
|
216
|
+
For `messages`, support newer polling only when implemented. Return newerCursor
|
|
217
|
+
even for empty successful polls if the server advances it. Keep this separate
|
|
218
|
+
from roomEntityToken and history olderCursor. Sending a message is not a new
|
|
219
|
+
polling cursor. Test two rooms with different cursors, empty newer pages, failed
|
|
220
|
+
polls, history loading, send/delete, and a denied canSend result.
|
|
221
|
+
|
|
222
|
+
## Article
|
|
223
|
+
|
|
224
|
+
Declare detail and comments. Return a timeline PostV1 with
|
|
225
|
+
`detailType: "article"` and a key that article.detail understands.
|
|
226
|
+
`article.detail(EntityRequestV1)` returns ArticleV1 with key/title/content;
|
|
227
|
+
`comments(EntityPageRequestV1)` returns PageV1<PostV1>.
|
|
228
|
+
|
|
229
|
+
Map full text to sanitized HTML/plain rich text, keep images in meaningful
|
|
230
|
+
content order, and request mediaNetwork for remote covers/HTML images. A site
|
|
231
|
+
with no comments can return `{ items: [], endReached: true }` when that genuinely
|
|
232
|
+
represents its content. Test the preview click, long content, relative HTML
|
|
233
|
+
URLs, comments, and an unavailable article. A browser-only link feed can instead
|
|
234
|
+
use detailType link without declaring article capability.
|
|
235
|
+
|
|
236
|
+
## Gallery
|
|
237
|
+
|
|
238
|
+
Declare detail, comments, and recommendations; preview posts use
|
|
239
|
+
`detailType: "gallery"`.
|
|
240
|
+
|
|
241
|
+
| Method | Input | Return |
|
|
242
|
+
| --- | --- | --- |
|
|
243
|
+
| detail | EntityRequestV1 | GalleryV1 |
|
|
244
|
+
| comments / recommendations | EntityPageRequestV1 | PageV1<PostV1> |
|
|
245
|
+
|
|
246
|
+
Gallery detail needs key/title/createdAt/url and 1..100 image media entries.
|
|
247
|
+
Orientation is horizontal or vertical (default vertical). Preserve image order,
|
|
248
|
+
dimensions, descriptions, and the gallery's action state. Bookmark behavior
|
|
249
|
+
additionally requires post mutation capability, a matching semantic descriptor,
|
|
250
|
+
and an account; gallery capability alone does not implement mutations. Preserve
|
|
251
|
+
the same entity key across preview, gallery, and mutation responses. Test grid
|
|
252
|
+
navigation, empty/no-comment results, image permissions, and bookmark refresh.
|
|
253
|
+
|
|
254
|
+
## Tab catalog
|
|
255
|
+
|
|
256
|
+
`tabCatalog.page(PageRequestV1)` returns `PageV1<TimelineSectionV1>`. Each section
|
|
257
|
+
contains `id`, WireText `title`, and a nested PageV1 of TimelineDescriptorV1.
|
|
258
|
+
Each descriptor has id/title/icon and optional display/parameters:
|
|
259
|
+
|
|
260
|
+
```js
|
|
261
|
+
const catalog = {
|
|
262
|
+
items: [{
|
|
263
|
+
id: "topics", title: { value: "Topics" },
|
|
264
|
+
timelines: {
|
|
265
|
+
items: [{ id: "photography", title: { value: "Photography" }, icon: "Featured", parameters: { topic: "photo" } }],
|
|
266
|
+
endReached: true,
|
|
267
|
+
},
|
|
268
|
+
}],
|
|
269
|
+
endReached: true,
|
|
270
|
+
};
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
The descriptor's ID/parameters reach timeline.page when selected; implement that
|
|
274
|
+
routing. A catalog still requires timeline.page and at least one static default
|
|
275
|
+
timeline. Do not promise an independent nested-catalog paging callback: v1
|
|
276
|
+
exposes only tabCatalog.page. Test selecting a descriptor and its resulting page.
|
|
277
|
+
|
|
278
|
+
## Guest access
|
|
279
|
+
|
|
280
|
+
Declare `platform.guest: { "enabled": true }` only if the declared read paths
|
|
281
|
+
work without credentials. Branch on context.scope/credentialAvailable when an
|
|
282
|
+
operation supports both account and guest calls. Guest requests stay at the
|
|
283
|
+
selected origin and have no account credential API. The declaration makes the
|
|
284
|
+
guest datasource available; it does not make the plugin a global default guest
|
|
285
|
+
platform or create a new login method. Test supported guest navigation in Flare,
|
|
286
|
+
including capability requirements for internal article/gallery/post details.
|
|
287
|
+
|
|
288
|
+
## Localization
|
|
289
|
+
|
|
290
|
+
Manifest text accepts a string or `{ "key": "timeline.home", "fallback": "Home" }`.
|
|
291
|
+
Runtime WireText instead accepts `{ "value": "Home" }` or a localized object
|
|
292
|
+
with key/fallback and optional `args`. Exactly one of value/key is required;
|
|
293
|
+
localized WireText requires a fallback. Arguments use finite numbers, booleans,
|
|
294
|
+
or strings with valid names; omit null arguments.
|
|
295
|
+
|
|
296
|
+
`locales/en.json` contains a flat key-to-string map:
|
|
297
|
+
|
|
298
|
+
```json
|
|
299
|
+
{ "timeline.home": "Home", "error.limited": "Try again in {seconds} seconds" }
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Ensure manifest keys exist in the default catalog, and include runtime keys as
|
|
303
|
+
well for translated errors/titles. Resolution tries exact BCP-47 tag,
|
|
304
|
+
language-script, language, default locale, then fallback. Only named `{argument}`
|
|
305
|
+
substitution is supported; there is no plural/select message syntax. Test an
|
|
306
|
+
unavailable locale and a missing translation falling back to the default.
|
|
307
|
+
|
|
308
|
+
## Deep Links
|
|
309
|
+
|
|
310
|
+
Deep Links are manifest-only structured rules, not a JavaScript resolver:
|
|
311
|
+
|
|
312
|
+
```json
|
|
313
|
+
{
|
|
314
|
+
"origin": "$accountOrigin",
|
|
315
|
+
"path": [{ "type": "literal", "value": "posts" }, { "type": "capture", "name": "id" }],
|
|
316
|
+
"target": { "type": "Post", "value": "{id}" }
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Place the rule in `platform.deepLinks`. Origin must be `$accountOrigin` or a
|
|
321
|
+
declared auth origin. Paths contain 1..32 literal/capture segments with unique
|
|
322
|
+
capture names. Targets are `Profile`, `Post`, `Timeline`, `Browser`. Entity target
|
|
323
|
+
values reference captured values; the current validator allows only capture
|
|
324
|
+
substitutions, not arbitrary literal prefixes. Timeline target value is a
|
|
325
|
+
declared static timeline ID; Browser takes no value. No wildcard/regex/callback
|
|
326
|
+
field is available. Target capability and account/guest availability still
|
|
327
|
+
matter. Test matching/nonmatching paths and URI encoding with the actual Host;
|
|
328
|
+
detailType link explicitly bypasses this internal matching path.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Runtime, HTTP, and state
|
|
2
|
+
|
|
3
|
+
## Execution environment
|
|
4
|
+
|
|
5
|
+
Flare evaluates one self-contained classic `plugin.js` in an isolated runtime:
|
|
6
|
+
|
|
7
|
+
| Host target | JavaScript engine |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Native iOS / macOS | System JavaScriptCore |
|
|
10
|
+
| Android | AndroidX JavaScriptEngine, supplied by Android System WebView |
|
|
11
|
+
| Compose Desktop (including macOS) | Bundled QuickJS |
|
|
12
|
+
|
|
13
|
+
Android requires a provider supporting message ports, Promise results, and
|
|
14
|
+
evaluation beyond the Binder transaction limit. Update Android System WebView
|
|
15
|
+
when the Host reports that JavaScript is unavailable; there is no QuickJS fallback.
|
|
16
|
+
Engine versions on Apple and Android follow the OS/provider. Use portable
|
|
17
|
+
JavaScript and test on each target; newer syntax, `Intl` data, stack traces and
|
|
18
|
+
engine-specific globals are not a cross-platform compatibility guarantee.
|
|
19
|
+
|
|
20
|
+
Register exactly once with `definePlugin`. Use standard JavaScript data handling
|
|
21
|
+
and the frozen `flare` Host facade. Browser/Node facilities (`fetch`, DOM,
|
|
22
|
+
`localStorage`, `require`, `process`, `Buffer`, browser URL helpers, timers) are
|
|
23
|
+
not part of the plugin contract. Do not assume them from your build environment.
|
|
24
|
+
Static/dynamic imports, remote code, `eval`, `Function`, and WebAssembly compilation
|
|
25
|
+
are unavailable. A TypeScript declaration file supplies types, not runtime code.
|
|
26
|
+
If authoring with a bundler, commit its self-contained classic-script output at
|
|
27
|
+
repository root and check the output for unsupported dependencies.
|
|
28
|
+
|
|
29
|
+
Initialization is also evaluated during installation, without Host services.
|
|
30
|
+
Define constants/functions and register synchronously at top level. HTTP,
|
|
31
|
+
credential, crypto, and locale calls belong inside registered invocations.
|
|
32
|
+
Requests and contexts are deeply frozen. Copy data before modifying it.
|
|
33
|
+
|
|
34
|
+
Runtime scopes are detector attempt, login flow, account, and guest origin.
|
|
35
|
+
Calls in one runtime serialize. Globals may cache derived data but are not
|
|
36
|
+
persistent storage. Login begin/resume can use different runtime generations;
|
|
37
|
+
OAuth state that your code needs belongs in `pendingPayload`. There are no
|
|
38
|
+
lifecycle hooks or general-purpose storage API.
|
|
39
|
+
An unmarked exception, invalid Wire response, timeout, cancellation, or memory
|
|
40
|
+
failure retires that generation. See [recovery](troubleshooting.md).
|
|
41
|
+
|
|
42
|
+
## Async completion and errors
|
|
43
|
+
|
|
44
|
+
Flare waits for the registered method's returned/awaited Promise and for Host
|
|
45
|
+
tasks started by that invocation to finish before returning its result, including
|
|
46
|
+
when the method fails. This is completion tracking, not automatic error propagation:
|
|
47
|
+
a rejected Promise outside the method's `await`/`return` chain does not by itself
|
|
48
|
+
fail the invocation. This also applies to a Host call that the method did not await.
|
|
49
|
+
Always await required HTTP requests and credential updates so their failures affect
|
|
50
|
+
the result. Await dependent follow-up work; detached Promise chains are not a
|
|
51
|
+
background-task API and cannot rely on the invocation remaining active.
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
await flare.credential.replace({ token: newToken });
|
|
55
|
+
return result; // returned only after persistence; a failed replacement fails this chain
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Caught errors remain caught. A returned/awaited `flare.error(...)` remains a typed
|
|
59
|
+
plugin error; other returned/awaited failures (including `throw null` or rejected
|
|
60
|
+
`undefined`) retire the runtime. Detached failures may have no diagnostic.
|
|
61
|
+
|
|
62
|
+
Host deadlines and cancellation still stop waiting for a result, cancel invocation
|
|
63
|
+
Host work, and retire the runtime. They do not guarantee interruption of executing
|
|
64
|
+
JavaScript. In particular, public JavaScriptCore cannot stop a synchronous loop;
|
|
65
|
+
its worker may retain CPU/memory until the script returns or the process exits.
|
|
66
|
+
There is no uniform per-runtime heap/stack cap or heap-based pool eviction. The
|
|
67
|
+
system/provider still imposes its own limits. Android isolates share a sandbox
|
|
68
|
+
process: its death can invalidate multiple runtimes, which are recreated on later
|
|
69
|
+
calls. Failed operations are not automatically replayed. Keep computation bounded
|
|
70
|
+
and persistent state in the Host's existing credential/account APIs.
|
|
71
|
+
|
|
72
|
+
## Invocation context
|
|
73
|
+
|
|
74
|
+
`pluginId`, `platformId`, and `packageHash` identify the running package.
|
|
75
|
+
`scope` is `detector`, `login`, `account`, or `guest`; `origin` is the normalized
|
|
76
|
+
selected HTTPS origin. `accountId` is supplied for account invocations, not new
|
|
77
|
+
login or guest calls. `locale`, `approvedOrigins`, `credentialAvailable`, and
|
|
78
|
+
`assetHandles` describe this call. Treat them as Host-owned values; do not
|
|
79
|
+
construct a different account identity or widen permissions from remote data.
|
|
80
|
+
|
|
81
|
+
## Origins and permissions
|
|
82
|
+
|
|
83
|
+
| Scope | HTTP origins | Credential API | Proxy routing |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| Detector | Candidate origin | Unavailable | Global |
|
|
86
|
+
| New login | Selected origin + declared authOrigins | Unavailable | Global |
|
|
87
|
+
| Relogin | Selected origin + declared authOrigins | Unavailable | Existing account |
|
|
88
|
+
| Account | Verified account origin | Current account only | Account |
|
|
89
|
+
| Guest | Selected guest origin | Unavailable | Global |
|
|
90
|
+
|
|
91
|
+
Supply absolute HTTPS request URLs without URL credentials or fragments. Same
|
|
92
|
+
hostname with a different scheme/port is a different origin. A website whose
|
|
93
|
+
authenticated API is on another origin must be designed around the selected
|
|
94
|
+
API origin; adding `authOrigins` does not grant cross-origin account requests.
|
|
95
|
+
|
|
96
|
+
Only GET/HEAD redirects are supported, up to five, staying on the current origin
|
|
97
|
+
even if another origin is approved for login. Redirected POST uploads/token
|
|
98
|
+
exchanges must use their final endpoint directly.
|
|
99
|
+
|
|
100
|
+
`mediaNetwork: true` separately permits automatic remote images/avatars/covers/
|
|
101
|
+
audio/video, including HTML images and CDN/federated destinations. Installation
|
|
102
|
+
review discloses this broad media access. Media can expose data embedded in its
|
|
103
|
+
URL; keep secrets out of media and link URLs. Permission changes follow the
|
|
104
|
+
running package immediately, including cached/nested media. Local-file and
|
|
105
|
+
non-HTTPS resources remain unavailable.
|
|
106
|
+
|
|
107
|
+
Flare controls proxies, including resumed OAuth. A proxy failure does not fall
|
|
108
|
+
back to a direct request. A route change can cancel an in-flight upload or call.
|
|
109
|
+
|
|
110
|
+
## HTTP request and response
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
// Inside an account method; adapt the path and credential fields to the website.
|
|
114
|
+
const credential = await flare.credential.read();
|
|
115
|
+
const response = await flare.http.request({
|
|
116
|
+
method: "GET",
|
|
117
|
+
url: context.origin + "/api/me",
|
|
118
|
+
authorization: { type: "bearer", token: credential.accessToken },
|
|
119
|
+
});
|
|
120
|
+
if (response.status === 401) {
|
|
121
|
+
throw flare.error({ code: "AuthenticationRequired", message: { value: "Log in again" } });
|
|
122
|
+
}
|
|
123
|
+
if (response.status < 200 || response.status >= 300) {
|
|
124
|
+
throw flare.error({ code: "Remote", message: { value: "The website rejected the request" } });
|
|
125
|
+
}
|
|
126
|
+
let data;
|
|
127
|
+
try {
|
|
128
|
+
data = JSON.parse(response.body);
|
|
129
|
+
} catch (_) {
|
|
130
|
+
throw flare.error({ code: "InvalidResponse", message: { value: "Expected JSON" } });
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The result is `{ status, headers, body }`: body is a UTF-8 string and each header
|
|
135
|
+
value is an array of strings. Inspect status yourself; a 4xx/5xx response is not
|
|
136
|
+
automatically a thrown plugin error. Handle legitimate empty/204 responses
|
|
137
|
+
without JSON parsing. Header names should be looked up case-insensitively.
|
|
138
|
+
Do not use `response.ok`, `.json()`, `.text()`, or binary Response APIs.
|
|
139
|
+
|
|
140
|
+
`method` defaults to GET; allowed methods are GET, POST, PUT, PATCH, DELETE, HEAD.
|
|
141
|
+
GET/HEAD cannot have a body. `timeoutMillis` may shorten the invocation's timeout,
|
|
142
|
+
not extend it. The invocation deadline covers the whole method, including all
|
|
143
|
+
HTTP calls, so keep pagination and uploads bounded.
|
|
144
|
+
|
|
145
|
+
Authentication uses `authorization: { type: "bearer", token }` or
|
|
146
|
+
`{ type: "basic", username, password }`; Cookies use
|
|
147
|
+
`cookies: [{ name, value }]`. Never put Authorization/Cookie/Host/Content-Length
|
|
148
|
+
directly in headers. Connection/keep-alive/transfer/upgrade and proxy-/sec-
|
|
149
|
+
headers are also controlled by the Host. Response headers exclude sensitive and
|
|
150
|
+
hop-by-hop headers, including Set-Cookie: there is no implicit Cookie jar to
|
|
151
|
+
build from a login response. Use the Cookie login flow when browser Cookies are
|
|
152
|
+
required, or the website's documented token API.
|
|
153
|
+
|
|
154
|
+
### Body recipes
|
|
155
|
+
|
|
156
|
+
These are request `body` fragments; replace website fields with verified ones.
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
const jsonBody = { type: "json", value: { text: "Hello" } };
|
|
160
|
+
const formBody = { type: "form", values: { grant_type: "authorization_code", code: "CODE" } };
|
|
161
|
+
const textBody = { type: "text", value: "hello", contentType: "text/plain" };
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Multipart media uses Host-owned asset handles, not file bytes:
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
// Inside compose.publish; one actual request asset is shown.
|
|
168
|
+
const asset = request.assets[0];
|
|
169
|
+
const upload = await flare.http.request({
|
|
170
|
+
method: "POST",
|
|
171
|
+
url: context.origin + "/api/media",
|
|
172
|
+
authorization: { type: "bearer", token: credential.accessToken },
|
|
173
|
+
body: {
|
|
174
|
+
type: "multipart",
|
|
175
|
+
parts: [
|
|
176
|
+
{ type: "asset", name: "file", handle: asset.handle, fileName: asset.fileName },
|
|
177
|
+
{ type: "text", name: "description", value: asset.description || "" },
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Parse/check the upload response before publishing. Handles are valid only for
|
|
184
|
+
the current invocation, cannot be synthesized or persisted, and cannot be read
|
|
185
|
+
as bytes from JavaScript. The Host streams the asset.
|
|
186
|
+
|
|
187
|
+
## Credentials, crypto, and locale
|
|
188
|
+
|
|
189
|
+
`await flare.credential.read()` returns only the current account's JSON payload.
|
|
190
|
+
`await flare.credential.replace(next)` atomically replaces the **whole** payload
|
|
191
|
+
and resolves true. Merge retained refresh-token/client fields yourself when
|
|
192
|
+
necessary. Initial login returns `credential` in `LoginSuccessV1` instead; neither
|
|
193
|
+
detector nor login nor guest has credential access. An account cannot read or
|
|
194
|
+
replace another account's credential.
|
|
195
|
+
|
|
196
|
+
`await flare.crypto.randomHex(size)` uses **bytes** as its size and returns twice
|
|
197
|
+
as many hexadecimal characters. `uuid()` returns a UUID string. `sha256(value)`
|
|
198
|
+
hashes the UTF-8 string and returns hex, not a byte array or base64url PKCE value.
|
|
199
|
+
Implement any provider-required PKCE encoding explicitly using compatible JS;
|
|
200
|
+
do not assume Web Crypto or Buffer. `await flare.locale.current()` returns the
|
|
201
|
+
Host locale, also supplied as `context.locale`.
|
|
202
|
+
|
|
203
|
+
Host bridge failures carry codes such as `host.unavailable`,
|
|
204
|
+
`host.invalid-request`, and `credential.denied`. Catch only failures you can
|
|
205
|
+
classify meaningfully: the [template helper](../assets/github-template/plugin.js)
|
|
206
|
+
maps unavailable transport to a typed Network error while leaving programming
|
|
207
|
+
and permission mistakes visible. Do not mask every error as an empty page.
|