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.
Files changed (27) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +131 -2
  3. package/package.json +33 -4
  4. package/skill/flare-plugin/ABI.md +334 -0
  5. package/skill/flare-plugin/README.md +204 -0
  6. package/skill/flare-plugin/SKILL.md +80 -0
  7. package/skill/flare-plugin/assets/github-template/README.md +71 -0
  8. package/skill/flare-plugin/assets/github-template/assets/icon.png +0 -0
  9. package/skill/flare-plugin/assets/github-template/manifest.json +29 -0
  10. package/skill/flare-plugin/assets/github-template/plugin.js +108 -0
  11. package/skill/flare-plugin/examples/pixelfed/README.md +78 -0
  12. package/skill/flare-plugin/examples/pixelfed/assets/icon.png +0 -0
  13. package/skill/flare-plugin/examples/pixelfed/locales/en.json +24 -0
  14. package/skill/flare-plugin/examples/pixelfed/locales/zh-Hans.json +24 -0
  15. package/skill/flare-plugin/examples/pixelfed/manifest.json +211 -0
  16. package/skill/flare-plugin/examples/pixelfed/plugin.js +793 -0
  17. package/skill/flare-plugin/flare-plugin.d.ts +501 -0
  18. package/skill/flare-plugin/pack.mjs +155 -0
  19. package/skill/flare-plugin/references/authentication.md +239 -0
  20. package/skill/flare-plugin/references/capabilities.md +88 -0
  21. package/skill/flare-plugin/references/manifest.md +192 -0
  22. package/skill/flare-plugin/references/publishing.md +111 -0
  23. package/skill/flare-plugin/references/recipes.md +328 -0
  24. package/skill/flare-plugin/references/runtime-http.md +207 -0
  25. package/skill/flare-plugin/references/testing.md +176 -0
  26. package/skill/flare-plugin/references/troubleshooting.md +116 -0
  27. 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.