@civitai/sdk 0.9.0 → 0.10.1
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/README.md +13 -2
- package/dist/core/errors.js +8 -1
- package/dist/core/handshake.d.ts +16 -1
- package/dist/core/transports/iframe-transport.js +24 -0
- package/dist/host/index.d.ts +10 -3
- package/dist/host/index.js +6 -1
- package/dist/host/protocol.d.ts +14 -0
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,6 +60,14 @@ Both give you:
|
|
|
60
60
|
A block's `app` also has `host`, `viewer`, `context`, `settings`, `theme` and
|
|
61
61
|
`onChange(listener)`. Outside a block they do not exist, and the types say so.
|
|
62
62
|
|
|
63
|
+
`context` and `theme` are LIVE, not mount-time snapshots: the host pushes
|
|
64
|
+
`THEME_CHANGE` when the viewer toggles dark mode and `ROUTE_CHANGED` when a page
|
|
65
|
+
block's sub-path moves, and both are folded into the same snapshot — so
|
|
66
|
+
`app.context.subPath` tracks `app.host.navigate(path)` (and the viewer's own back
|
|
67
|
+
button) and `onChange` tells you when. Read them on each render rather than
|
|
68
|
+
copying them once; there is deliberately no `onRouteChange`, because one value
|
|
69
|
+
observed two ways is two values that can disagree.
|
|
70
|
+
|
|
63
71
|
A block opts in by declaring `auth: "oauth"` in its `block.manifest.json`. The
|
|
64
72
|
host then hands it a real OAuth access token that `/api/v1`, the orchestrator
|
|
65
73
|
and the MCP accept, and consent — including `requestGrants` — goes through the
|
|
@@ -338,7 +346,7 @@ wider scope — and refuses without one.
|
|
|
338
346
|
| `resize(height)` | Resizes the frame, clamped to the manifest |
|
|
339
347
|
| `autoResize(element?)` | Keeps the frame as tall as the body (or `element`); returns a stop function |
|
|
340
348
|
| `reportError(message, { fatal })` | `fatal` swaps the block for the host's fallback |
|
|
341
|
-
| `navigate(path, { target })` | Deep-links within this app's own sub-paths |
|
|
349
|
+
| `navigate(path, { scope, target })` | Deep-links within this app's own sub-paths; `scope: 'site'` asks to leave the app for a civitai.com page instead. `scope` defaults to `'app'` |
|
|
342
350
|
| `onVisibilityChange(handler)` | Reports the page hiding and returning |
|
|
343
351
|
| `requestSignIn({ returnUrl })` | Starts sign-in; the block re-initialises signed in |
|
|
344
352
|
| `download({ url, filename })` | Saves to the viewer's device — a sandboxed frame cannot |
|
|
@@ -349,7 +357,10 @@ wider scope — and refuses without one.
|
|
|
349
357
|
| `publishGenerationOutputs({ workflowId, imageIndexes })` | Publishes your own workflow's outputs, behind the host's confirmation; resolves the new image ids |
|
|
350
358
|
|
|
351
359
|
Host failures reject with a `BridgeError` carrying a `code` (`forbidden`,
|
|
352
|
-
`unauthenticated`, `rate-limited`, …).
|
|
360
|
+
`unauthenticated`, `rate-limited`, …). `unauthenticated` includes a viewer whose
|
|
361
|
+
session ended (signed out in another tab) — offer `requestSignIn()`. A session
|
|
362
|
+
that now belongs to a different account is `forbidden`; its `message` asks the
|
|
363
|
+
viewer to reload the page. Timeouts are the host's to set; pass a
|
|
353
364
|
`signal` to cancel. `ApiError` and `BridgeError` both extend `CivitaiError`, so
|
|
354
365
|
one `catch` can tell a refusal from a bug.
|
|
355
366
|
|
package/dist/core/errors.js
CHANGED
|
@@ -20,11 +20,18 @@ export class BridgeError extends CivitaiError {
|
|
|
20
20
|
* The messages this bridge already had report failure as the server's own
|
|
21
21
|
* sentence — the host holds the tRPC code and drops it. Unmatched text is
|
|
22
22
|
* `unavailable`; `message` still carries what the host said.
|
|
23
|
+
*
|
|
24
|
+
* Procedures that need the viewer's signed-in session can refuse with a bare
|
|
25
|
+
* `UNAUTHORIZED` (the session ended, e.g. signed out in another tab), which is
|
|
26
|
+
* `unauthenticated`, or with "…belongs to a different account; reload the page
|
|
27
|
+
* to continue" (the page's session changed account), which is `forbidden`:
|
|
28
|
+
* signing in again does not help, reloading the page does.
|
|
23
29
|
*/
|
|
24
30
|
const HOST_FAILURES = [
|
|
25
31
|
[/authenticated viewer|no block token/i, 'unauthenticated'],
|
|
32
|
+
[/^UNAUTHORIZED$/, 'unauthenticated'],
|
|
26
33
|
[
|
|
27
|
-
/(requires|lacks) .+ scope|not approved|revoked|invalid block token|not enabled|review preview|^banned$/i,
|
|
34
|
+
/(requires|lacks) .+ scope|not approved|revoked|invalid block token|not enabled|review preview|belongs to a different account|^banned$/i,
|
|
28
35
|
'forbidden',
|
|
29
36
|
],
|
|
30
37
|
[/rate limit|^busy$/i, 'rate-limited'],
|
package/dist/core/handshake.d.ts
CHANGED
|
@@ -99,7 +99,17 @@ export interface BlockInitPayload {
|
|
|
99
99
|
/** The domain ceiling narrowed to this viewer. Always a subset of the above. */
|
|
100
100
|
effectiveBrowsingLevel?: number;
|
|
101
101
|
}
|
|
102
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* Host messages the transport itself acts on. Domains own everything else.
|
|
104
|
+
*
|
|
105
|
+
* `ROUTE_CHANGED` is here rather than in `HostPushes` for the same reason
|
|
106
|
+
* `THEME_CHANGE` is: it carries a value the snapshot already holds, so the
|
|
107
|
+
* transport folds it in and `client.context` / `client.onChange` are how a
|
|
108
|
+
* consumer observes it. A `HostPushes` row plus an `onRouteChange` method would
|
|
109
|
+
* publish a SECOND way to read one value, which is how `client.context.subPath`
|
|
110
|
+
* and a callback end up disagreeing. `SUSPEND`/`RESUME` are in `HostPushes`
|
|
111
|
+
* because they carry no state for the snapshot to hold.
|
|
112
|
+
*/
|
|
103
113
|
export type HostMessage = {
|
|
104
114
|
type: 'BLOCK_INIT';
|
|
105
115
|
payload: BlockInitPayload;
|
|
@@ -119,6 +129,11 @@ export type HostMessage = {
|
|
|
119
129
|
payload: {
|
|
120
130
|
theme: Theme;
|
|
121
131
|
};
|
|
132
|
+
} | {
|
|
133
|
+
type: 'ROUTE_CHANGED';
|
|
134
|
+
payload: {
|
|
135
|
+
subPath: string;
|
|
136
|
+
};
|
|
122
137
|
};
|
|
123
138
|
/** Discriminator only — the payload is still unvalidated wire data. */
|
|
124
139
|
export declare function isHostMessage<K extends HostMessage['type']>(data: unknown, type: K): data is Extract<HostMessage, {
|
|
@@ -287,6 +287,18 @@ export class IframeTransport {
|
|
|
287
287
|
this.#applyThemeChange(data.payload.theme);
|
|
288
288
|
return;
|
|
289
289
|
}
|
|
290
|
+
// The page's sub-path moved under the app root. Folded into the snapshot so
|
|
291
|
+
// `client.context.subPath` is live and `onChange` reports it — the same
|
|
292
|
+
// treatment as the theme, and the reason there is no `onRouteChange`.
|
|
293
|
+
//
|
|
294
|
+
// Unlike the theme this IS effectively init-gated, because the sub-path
|
|
295
|
+
// lives only in `context` and the pre-init context has no such key. That
|
|
296
|
+
// costs nothing: the host sends this only after init, and `BLOCK_INIT`
|
|
297
|
+
// replaces the snapshot with its own current value.
|
|
298
|
+
if (isHostMessage(data, 'ROUTE_CHANGED')) {
|
|
299
|
+
this.#applyRouteChange(data.payload.subPath);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
290
302
|
// Matching the TYPE too, not just the id: `IMAGE_SCAN_RESOLVED` is a push
|
|
291
303
|
// that deliberately reuses its `OPEN_IMAGE_UPLOAD`'s requestId, so an
|
|
292
304
|
// id-only match could resolve the upload with the scan verdict's payload.
|
|
@@ -337,6 +349,18 @@ export class IframeTransport {
|
|
|
337
349
|
this.#snapshot = next;
|
|
338
350
|
this.#emit();
|
|
339
351
|
}
|
|
352
|
+
// Only ever UPDATES a context that already carries the key; never INTRODUCES
|
|
353
|
+
// it. A model slot has no route, so synthesising `subPath` would assert a page
|
|
354
|
+
// context the host never sent. Emits only when the value moved.
|
|
355
|
+
#applyRouteChange(subPath) {
|
|
356
|
+
const context = this.#snapshot.context;
|
|
357
|
+
if (!('subPath' in context))
|
|
358
|
+
return;
|
|
359
|
+
if (context.subPath === subPath)
|
|
360
|
+
return;
|
|
361
|
+
this.#snapshot = { ...this.#snapshot, context: { ...context, subPath } };
|
|
362
|
+
this.#emit();
|
|
363
|
+
}
|
|
340
364
|
#emit() {
|
|
341
365
|
for (const listener of this.#listeners)
|
|
342
366
|
listener();
|
package/dist/host/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type BlockTransport } from '../core/transport.js';
|
|
2
2
|
import type { Session } from '../session/index.js';
|
|
3
|
-
import type { DownloadRequest, ImageScanResult, PickedResource, ResourcePickerType, SourceImage } from './protocol.js';
|
|
4
|
-
export type { ConsentRefusal, DownloadRequest, ImageScanResult, PickedResource, ResourcePickerType, SourceImage, UploadedImage, } from './protocol.js';
|
|
3
|
+
import type { DownloadRequest, ImageScanResult, NavigateScope, PickedResource, ResourcePickerType, SourceImage } from './protocol.js';
|
|
4
|
+
export type { ConsentRefusal, DownloadRequest, ImageScanResult, NavigateScope, PickedResource, ResourcePickerType, SourceImage, UploadedImage, } from './protocol.js';
|
|
5
5
|
export interface HostCallOptions {
|
|
6
6
|
signal?: AbortSignal;
|
|
7
7
|
}
|
|
@@ -33,8 +33,15 @@ export interface Host {
|
|
|
33
33
|
reportError(message: string, args?: {
|
|
34
34
|
fatal?: boolean;
|
|
35
35
|
}): void;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Asks the host to navigate. `scope` picks the space `path` is resolved in and
|
|
38
|
+
* DEFAULTS to `'app'` — this app's own sub-paths, which is all this method
|
|
39
|
+
* could reach before the field existed. Pass `scope: 'site'` to leave the app
|
|
40
|
+
* for a civitai.com page; the host grants that per-surface and refuses it
|
|
41
|
+
* elsewhere. Fire-and-forget either way: a refusal is silent.
|
|
42
|
+
*/
|
|
37
43
|
navigate(path: string, args?: {
|
|
44
|
+
scope?: NavigateScope;
|
|
38
45
|
target?: 'current' | 'new_tab';
|
|
39
46
|
}): void;
|
|
40
47
|
/** Fires `false` when the page hides and `true` when it returns. */
|
package/dist/host/index.js
CHANGED
|
@@ -25,7 +25,12 @@ export function createHost(transport) {
|
|
|
25
25
|
return () => observer.disconnect();
|
|
26
26
|
},
|
|
27
27
|
reportError: (message, args = {}) => notify('BLOCK_ERROR', { message, fatal: args.fatal ?? false }, { transport }),
|
|
28
|
-
|
|
28
|
+
// `scope` is OMITTED rather than sent as `undefined` when the caller did not
|
|
29
|
+
// choose one. Absent and `'app'` mean the same thing to the host, so this is
|
|
30
|
+
// not a behaviour difference — it keeps the wire payload byte-identical to
|
|
31
|
+
// what every pre-`scope` build sends, which is the property the back-compat
|
|
32
|
+
// claim rests on and the one a host-side diff can actually check.
|
|
33
|
+
navigate: (path, args = {}) => notify('NAVIGATE', { path, ...(args.scope ? { scope: args.scope } : {}), target: args.target ?? 'current' }, { transport }),
|
|
29
34
|
onVisibilityChange(handler) {
|
|
30
35
|
const offSuspend = on(transport, 'SUSPEND', () => handler(false));
|
|
31
36
|
const offResume = on(transport, 'RESUME', () => handler(true));
|
package/dist/host/protocol.d.ts
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import type { WrappedToken } from '../core/handshake.js';
|
|
2
2
|
export type ResourcePickerType = 'Checkpoint' | 'LORA';
|
|
3
|
+
/**
|
|
4
|
+
* Which SPACE a `navigate()` path is resolved in.
|
|
5
|
+
*
|
|
6
|
+
* - `'app'` — under this app's own route, as a sub-path of it. The DEFAULT.
|
|
7
|
+
* - `'site'` — at the site root, leaving the app. The host grants this
|
|
8
|
+
* per-surface and refuses it where the surface does not hold the capability.
|
|
9
|
+
*
|
|
10
|
+
* Omitting it means `'app'`, which is what keeps this field additive: a block
|
|
11
|
+
* that never sets it behaves as it did before the field existed. A leading slash
|
|
12
|
+
* carries no meaning — the host normalises it away in both scopes, so
|
|
13
|
+
* `'/settings'` and `'settings'` are one request within the scope you chose.
|
|
14
|
+
*/
|
|
15
|
+
export type NavigateScope = 'app' | 'site';
|
|
3
16
|
/** The one resource the viewer picked. Discovery only: nothing here is an entitlement. */
|
|
4
17
|
export interface PickedResource {
|
|
5
18
|
versionId: number;
|
|
@@ -153,6 +166,7 @@ export type HostNotifications = {
|
|
|
153
166
|
};
|
|
154
167
|
NAVIGATE: {
|
|
155
168
|
path: string;
|
|
169
|
+
scope?: NavigateScope;
|
|
156
170
|
target: 'current' | 'new_tab';
|
|
157
171
|
};
|
|
158
172
|
REQUEST_SIGN_IN: {
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type { StorageCallOptions, StorageClient, StorageKeyEntry, StorageListQue
|
|
|
14
14
|
export { isTerminal, isTerminalStatus } from './orchestration/index.js';
|
|
15
15
|
export type { OrchestrationClient, Step, StepTemplate, WaitOptions, Workflow, WorkflowPage, WorkflowQuery, WorkflowStatus, WorkflowTemplate, } from './orchestration/index.js';
|
|
16
16
|
export { createHost } from './host/index.js';
|
|
17
|
-
export type { ConsentRefusal, DownloadRequest, Host, HostCallOptions, ImageScanResult, PendingImage, PickedResource, ResourcePickerType, SourceImage, UploadedImage, } from './host/index.js';
|
|
17
|
+
export type { ConsentRefusal, DownloadRequest, Host, HostCallOptions, ImageScanResult, NavigateScope, PendingImage, PickedResource, ResourcePickerType, SourceImage, UploadedImage, } from './host/index.js';
|
|
18
18
|
export { ApiError } from './http/index.js';
|
|
19
19
|
export type { Query, QueryValue, RequestOptions } from './http/index.js';
|
|
20
20
|
export { BridgeError, CivitaiError } from './core/errors.js';
|
package/package.json
CHANGED