@foir/sdk 0.21.0 → 0.22.0
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/CHANGELOG.md +84 -1
- package/README.md +9 -9
- package/dist/app/client.d.ts +90 -84
- package/dist/app/contract/proto/apps/v1/apps_pb.d.ts +14 -13
- package/dist/app/dispatch.d.ts +5 -5
- package/dist/app/errors.d.ts +11 -11
- package/dist/app/index.d.ts +4 -4
- package/dist/app/naming.d.ts +1 -1
- package/dist/app/ticket.d.ts +2 -2
- package/dist/app/vocabulary.generated.d.ts +2 -2
- package/dist/app-contract.js +3 -3
- package/dist/app.js +92 -90
- package/dist/auth/access.d.ts +19 -19
- package/dist/auth/auth.d.ts +216 -114
- package/dist/auth/hosted.d.ts +14 -14
- package/dist/auth/index.d.ts +7 -7
- package/dist/auth/operations.d.ts +128 -32
- package/dist/auth/ops.d.ts +39 -21
- package/dist/auth/session-client.d.ts +3 -3
- package/dist/auth-session.js +3 -3
- package/dist/auth.js +234 -135
- package/dist/client.js +4 -4
- package/dist/data/builtins.d.ts +26 -26
- package/dist/data/client.d.ts +2 -2
- package/dist/data/decimal.d.ts +1 -1
- package/dist/data/index.d.ts +1 -1
- package/dist/data/manifest.d.ts +1 -1
- package/dist/data/schema.d.ts +1 -1
- package/dist/data/transport.d.ts +10 -10
- package/dist/data/ws-transport.d.ts +1 -1
- package/dist/data.js +6 -6
- package/dist/graphql/content-client.d.ts +13 -13
- package/dist/live/drift.d.ts +1 -1
- package/dist/media/screen-capture.d.ts +1 -1
- package/dist/next/index.d.ts +2 -2
- package/dist/next/session.d.ts +10 -10
- package/dist/next.js +77 -56
- package/dist/placement/index.d.ts +13 -13
- package/dist/placement.js +9 -6
- package/dist/push.js +61 -40
- package/dist/react/FoirFeedback.d.ts +2 -2
- package/dist/react/FoirProvider.d.ts +23 -23
- package/dist/react/SignIn.d.ts +4 -4
- package/dist/react/UserButton.d.ts +1 -1
- package/dist/react/feedback-notes.d.ts +4 -4
- package/dist/react/index.d.ts +3 -3
- package/dist/react.js +119 -95
- package/dist/realtime/index.d.ts +1 -1
- package/dist/server/callback-client.d.ts +4 -4
- package/dist/server/events-client.d.ts +16 -16
- package/dist/server/operation-types.d.ts +8 -8
- package/dist/server/require-scoped-token.d.ts +3 -3
- package/dist/server/types.d.ts +1 -1
- package/dist/server/verify-scoped-token.d.ts +3 -3
- package/dist/server.js +16 -13
- package/dist/transport/foir-http.d.ts +13 -13
- package/dist/types/index.d.ts +2 -2
- package/package.json +3 -3
|
@@ -120,12 +120,12 @@ export interface ScopedTokenIngress {
|
|
|
120
120
|
*/
|
|
121
121
|
orgId: string | null;
|
|
122
122
|
/**
|
|
123
|
-
* The
|
|
124
|
-
*
|
|
123
|
+
* The user the run acts for, when it acts for one: the signed-in
|
|
124
|
+
* user who ran the operation, or the owner of the record whose event
|
|
125
125
|
* fired it. Verified with the rest of the token, so it is the one to trust
|
|
126
126
|
* over anything in the input.
|
|
127
127
|
*/
|
|
128
|
-
|
|
128
|
+
userId?: string;
|
|
129
129
|
/** When the token stops being accepted. There is no refresh: finish, or report, before it. */
|
|
130
130
|
expiresAt: Date;
|
|
131
131
|
/** The token's own id (`jti`). The run's id on a dispatch that carries no other. */
|
package/dist/server/types.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface ServerClientOptions {
|
|
|
16
16
|
configKey?: string;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* Where an editor iframe should appear in the
|
|
19
|
+
* Where an editor iframe should appear in the console's record editor.
|
|
20
20
|
*/
|
|
21
21
|
export interface EditorPlacement {
|
|
22
22
|
/** 'main-editor' replaces / augments the content tab; 'sidebar' renders in the sidebar. */
|
|
@@ -38,9 +38,9 @@ export interface ScopedTokenClaims {
|
|
|
38
38
|
iat: number;
|
|
39
39
|
ctx?: Record<string, string>;
|
|
40
40
|
/**
|
|
41
|
-
* The
|
|
42
|
-
*
|
|
43
|
-
* fired it. `requireScopedToken` answers it as `
|
|
41
|
+
* The user the token acts for, when it acts for one: the signed-in
|
|
42
|
+
* user who ran the operation, or the owner of the record whose event
|
|
43
|
+
* fired it. `requireScopedToken` answers it as `userId`.
|
|
44
44
|
*/
|
|
45
45
|
cid?: string;
|
|
46
46
|
}
|
package/dist/server.js
CHANGED
|
@@ -151,7 +151,8 @@ var GRANT_TABLE = [
|
|
|
151
151
|
["roles:write", false, [], []],
|
|
152
152
|
["projects:read", false, [], []],
|
|
153
153
|
["projects:write", false, [], []],
|
|
154
|
-
["project:
|
|
154
|
+
["project:stand", false, [], []],
|
|
155
|
+
["project:manage", false, [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "relyingparties:read", "relyingparties:write", "domains:read", "domains:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
|
|
155
156
|
["projects:create", false, [], []],
|
|
156
157
|
["projects:delete", false, [], []],
|
|
157
158
|
["tenants:read", false, [], []],
|
|
@@ -164,12 +165,14 @@ var GRANT_TABLE = [
|
|
|
164
165
|
["usage:read", false, [], []],
|
|
165
166
|
["controlkeys:read", false, [], []],
|
|
166
167
|
["controlkeys:write", false, [], []],
|
|
168
|
+
["tenantsigninproviders:read", false, [], []],
|
|
169
|
+
["tenantsigninproviders:write", false, [], []],
|
|
167
170
|
["tenanthooks:read", false, [], []],
|
|
168
171
|
["tenanthooks:write", false, [], []],
|
|
169
172
|
["apps:install", false, [], []],
|
|
170
|
-
["tenant:manage", false, [], ["project:manage", "projects:create", "projects:delete", "tenants:read", "tenants:write", "tenants:delete", "export:run", "billing:read", "billing:manage", "usage:read", "controlkeys:read", "controlkeys:write", "tenanthooks:read", "tenanthooks:write", "apps:install"]],
|
|
173
|
+
["tenant:manage", false, [], ["project:manage", "projects:create", "projects:delete", "tenants:read", "tenants:write", "tenants:delete", "export:run", "billing:read", "billing:manage", "usage:read", "controlkeys:read", "controlkeys:write", "tenanthooks:read", "tenanthooks:write", "tenantsigninproviders:read", "tenantsigninproviders:write", "apps:install"]],
|
|
171
174
|
["platform:read", false, [], []],
|
|
172
|
-
["platform:manage", false, [], ["tenant:manage", "platform:read", "platform:create_workspace"
|
|
175
|
+
["platform:manage", false, [], ["tenant:manage", "platform:read", "platform:create_workspace"]],
|
|
173
176
|
["platform:create_workspace", false, [], []],
|
|
174
177
|
["users:read", false, ["own", "named", "all"], []],
|
|
175
178
|
["users:write", false, ["own", "all"], []],
|
|
@@ -577,7 +580,7 @@ async function requireScopedToken(request, options) {
|
|
|
577
580
|
// header, so the client presents exactly what this guard accepted.
|
|
578
581
|
dispatch: { token: () => token, context: rawContext },
|
|
579
582
|
orgId: typeof level === "string" ? level : null,
|
|
580
|
-
...typeof claims.cid === "string" && claims.cid !== "" ? {
|
|
583
|
+
...typeof claims.cid === "string" && claims.cid !== "" ? { userId: claims.cid } : {},
|
|
581
584
|
expiresAt: new Date(claims.exp * 1e3),
|
|
582
585
|
tokenId: claims.jti
|
|
583
586
|
};
|
|
@@ -639,9 +642,9 @@ async function readDispatch(request) {
|
|
|
639
642
|
if (!isObject(context)) refuse(`body.context is ${describe(context)}, expected an object`);
|
|
640
643
|
requireString(context, "tenantId", "body.context");
|
|
641
644
|
requireString(context, "projectId", "body.context");
|
|
642
|
-
requireString(context, "
|
|
645
|
+
requireString(context, "actorId", "body.context", true);
|
|
643
646
|
requireString(context, "timestamp", "body.context");
|
|
644
|
-
if (context.
|
|
647
|
+
if (context.userId !== void 0) requireString(context, "userId", "body.context");
|
|
645
648
|
const input = body.input;
|
|
646
649
|
if (input !== null && !isObject(input)) {
|
|
647
650
|
refuse(`body.input is ${describe(input)}, expected an object`);
|
|
@@ -862,8 +865,8 @@ var FoirAuthDowngradedError = class extends FoirRequestError {
|
|
|
862
865
|
downgrade;
|
|
863
866
|
constructor(downgrade) {
|
|
864
867
|
super(
|
|
865
|
-
`[foir] your
|
|
866
|
-
{ graphQLErrors: [{ message: "
|
|
868
|
+
`[foir] your user token was ${downgrade.userToken} (${downgrade.reason}), so the request was served as ${downgrade.servedAs}`,
|
|
869
|
+
{ graphQLErrors: [{ message: "user token rejected", extensions: { code: "AUTH_DOWNGRADED" } }] }
|
|
867
870
|
);
|
|
868
871
|
this.name = "FoirAuthDowngradedError";
|
|
869
872
|
this.downgrade = downgrade;
|
|
@@ -871,7 +874,7 @@ var FoirAuthDowngradedError = class extends FoirRequestError {
|
|
|
871
874
|
};
|
|
872
875
|
var FoirSessionExpiredError = class extends FoirRequestError {
|
|
873
876
|
constructor(options) {
|
|
874
|
-
super("[foir] the
|
|
877
|
+
super("[foir] the user session has expired and could not be refreshed: sign in again", {
|
|
875
878
|
cause: options?.cause,
|
|
876
879
|
graphQLErrors: [{ message: "session expired", extensions: { code: "SESSION_EXPIRED" } }]
|
|
877
880
|
});
|
|
@@ -1131,7 +1134,7 @@ var FoirSocketTransport = class {
|
|
|
1131
1134
|
constructor(config) {
|
|
1132
1135
|
if (config.dispatch !== void 0) {
|
|
1133
1136
|
throw new FoirRequestError(
|
|
1134
|
-
"[foir] a client built from a dispatch token cannot subscribe. The subscription socket authenticates an api key,
|
|
1137
|
+
"[foir] a client built from a dispatch token cannot subscribe. The subscription socket authenticates an api key, a console token or a user bearer, and has no path for a dispatch token. Queries and mutations made as the operation go to /graphql and work as they are."
|
|
1135
1138
|
);
|
|
1136
1139
|
}
|
|
1137
1140
|
this.config = config;
|
|
@@ -1495,12 +1498,12 @@ function assertOperatorKey(apiKey) {
|
|
|
1495
1498
|
const kind = foirKeyKind(key);
|
|
1496
1499
|
if (kind === "publishable" || kind === "server") {
|
|
1497
1500
|
throw new FoirRequestError(
|
|
1498
|
-
`[foir] createEventsClient: the event log is
|
|
1501
|
+
`[foir] createEventsClient: the event log is a project surface and a ${kind} key can never hold ${FOIR_EVENTS_SCOPE}: it is not mintable on an app's key. Use an admin key from a machine of your own. The log names which user acted on records that whoever reads an app's key cannot themselves read.`
|
|
1499
1502
|
);
|
|
1500
1503
|
}
|
|
1501
1504
|
if (kind === void 0) {
|
|
1502
1505
|
throw new FoirRequestError(
|
|
1503
|
-
`[foir] createEventsClient: apiKey must be an admin key, got a key starting "${key.slice(0, 4)}". The event log is reachable by an admin key or
|
|
1506
|
+
`[foir] createEventsClient: apiKey must be an admin key, got a key starting "${key.slice(0, 4)}". The event log is reachable by an admin key or a console session only.`
|
|
1504
1507
|
);
|
|
1505
1508
|
}
|
|
1506
1509
|
return key;
|
|
@@ -1510,7 +1513,7 @@ function assertNoBearer(headers) {
|
|
|
1510
1513
|
for (const name of Object.keys(headers)) {
|
|
1511
1514
|
if (name.toLowerCase() === "authorization") {
|
|
1512
1515
|
throw new FoirRequestError(
|
|
1513
|
-
`[foir] createEventsClient: an Authorization header is not accepted. A
|
|
1516
|
+
`[foir] createEventsClient: an Authorization header is not accepted. A user bearer makes the user the resolved authority on api-public, and no user authority can contain ${FOIR_EVENTS_SCOPE}. The sk_ key alone is the credential for this surface.`
|
|
1514
1517
|
);
|
|
1515
1518
|
}
|
|
1516
1519
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* The split exists so the second one is possible. The Foir-specific transport
|
|
13
13
|
* concerns — which api key is in play, the preview schema channel header, the
|
|
14
|
-
* optional
|
|
14
|
+
* optional user bearer, and the `FoirRequestError` shape — are identical on
|
|
15
15
|
* both, and duplicating them is exactly how the two surfaces would drift. So
|
|
16
16
|
* they live here once and `foir-request.ts` re-exports them unchanged; the only
|
|
17
17
|
* thing that stayed behind is the part that needs `print`.
|
|
@@ -206,8 +206,8 @@ export declare function readFoirRateLimit(headers: Headers): FoirRateLimitSnapsh
|
|
|
206
206
|
export declare function readFoirRetryAfterMs(headers: Headers): number | undefined;
|
|
207
207
|
export interface FoirRequestOptions {
|
|
208
208
|
/**
|
|
209
|
-
*
|
|
210
|
-
* the project; the bearer adds the
|
|
209
|
+
* User bearer token. api-public still needs the `x-api-key` to resolve
|
|
210
|
+
* the project; the bearer adds the user identity for self-scoped reads
|
|
211
211
|
* (e.g. `myProfile`). Used by `@foir/sdk/auth`'s post-login profile fetch.
|
|
212
212
|
*/
|
|
213
213
|
bearerToken?: string;
|
|
@@ -256,7 +256,7 @@ export interface FoirRequestOptions {
|
|
|
256
256
|
onRateLimit?: (snapshot: FoirRateLimitSnapshot | undefined) => void;
|
|
257
257
|
}
|
|
258
258
|
/**
|
|
259
|
-
* Supplies the current
|
|
259
|
+
* Supplies the current user bearer. Sync or async so consumers can read a
|
|
260
260
|
* cookie session (Hydrogen/Remix), a KV store, an in-memory value, etc. Returns
|
|
261
261
|
* the token when signed in, or `null`/`undefined` when signed out.
|
|
262
262
|
*
|
|
@@ -265,7 +265,7 @@ export interface FoirRequestOptions {
|
|
|
265
265
|
* they would end up accepting subtly different things.
|
|
266
266
|
*/
|
|
267
267
|
export type FoirTokenProvider = () => string | null | undefined | Promise<string | null | undefined>;
|
|
268
|
-
/** A request made with an api key, optionally for a signed-in
|
|
268
|
+
/** A request made with an api key, optionally for a signed-in user. */
|
|
269
269
|
export interface FoirKeyHeaderInput {
|
|
270
270
|
apiKey: string;
|
|
271
271
|
/**
|
|
@@ -305,7 +305,7 @@ export interface FoirKeyHeaderInput {
|
|
|
305
305
|
*
|
|
306
306
|
* The token is the whole credential. api-public takes the scoped path when
|
|
307
307
|
* `X-Foir-Token` is present and never reads `x-api-key` on it, and the
|
|
308
|
-
* organization, the
|
|
308
|
+
* organization, the user and the schema channel all come from the token's
|
|
309
309
|
* own claims, so none of the key-side inputs has anything to say here.
|
|
310
310
|
*/
|
|
311
311
|
export interface FoirDispatchHeaderInput {
|
|
@@ -337,7 +337,7 @@ export declare function refuseCredentialHeaders(extra: Readonly<Record<string, s
|
|
|
337
337
|
* authorization difference nobody would notice until it mattered.
|
|
338
338
|
*
|
|
339
339
|
* Two credentials, and a request carries exactly one: an api key (with its
|
|
340
|
-
* optional
|
|
340
|
+
* optional user bearer, organization and channel), or an operation's
|
|
341
341
|
* dispatch token. A dispatch request never writes `x-api-key`.
|
|
342
342
|
*/
|
|
343
343
|
/** The header a server names the visitor it calls for in. */
|
|
@@ -412,7 +412,7 @@ export declare function foirApiEndpoint(apiUrl: string, path: `/${string}`): str
|
|
|
412
412
|
/**
|
|
413
413
|
* Foir DISCARDED a credential you sent, and served the request without it.
|
|
414
414
|
*
|
|
415
|
-
* A
|
|
415
|
+
* A user JWT is optional: if it fails to verify, the read is served as an
|
|
416
416
|
* ordinary anonymous one rather than refused, because plenty of reads
|
|
417
417
|
* legitimately have nobody signed in. That is the right behaviour and it has a
|
|
418
418
|
* sharp edge — a lapsed token and a genuinely anonymous request produce the
|
|
@@ -424,9 +424,9 @@ export declare function foirApiEndpoint(apiUrl: string, path: `/${string}`): str
|
|
|
424
424
|
*/
|
|
425
425
|
export interface FoirAuthDowngrade {
|
|
426
426
|
/** Always `"rejected"`. */
|
|
427
|
-
|
|
427
|
+
userToken: string;
|
|
428
428
|
/**
|
|
429
|
-
* `expired` — re-exchange and retry. `invalid` — not a
|
|
429
|
+
* `expired` — re-exchange and retry. `invalid` — not a user token for
|
|
430
430
|
* this environment. `wrong_project` — the token and the api-key name
|
|
431
431
|
* different projects.
|
|
432
432
|
*/
|
|
@@ -435,7 +435,7 @@ export interface FoirAuthDowngrade {
|
|
|
435
435
|
servedAs: string;
|
|
436
436
|
}
|
|
437
437
|
/**
|
|
438
|
-
* A request that sent a
|
|
438
|
+
* A request that sent a user token was served without it.
|
|
439
439
|
*
|
|
440
440
|
* Thrown only to a caller that asked (`rejectDowngrade`): one that sent the
|
|
441
441
|
* token because it wants the signed-in answer, and would otherwise be handed the
|
|
@@ -448,9 +448,9 @@ export declare class FoirAuthDowngradedError extends FoirRequestError {
|
|
|
448
448
|
constructor(downgrade: FoirAuthDowngrade);
|
|
449
449
|
}
|
|
450
450
|
/**
|
|
451
|
-
* The
|
|
451
|
+
* The user's session is over: its token was rejected and could not be
|
|
452
452
|
* refreshed. It is what a signed-in request gets in place of the anonymous
|
|
453
|
-
* answer. Send the
|
|
453
|
+
* answer. Send the user to sign in again.
|
|
454
454
|
*/
|
|
455
455
|
export declare class FoirSessionExpiredError extends FoirRequestError {
|
|
456
456
|
constructor(options?: {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface EditorInit {
|
|
|
21
21
|
apiUrl: string;
|
|
22
22
|
/** Config connection domain (e.g. my-store.myshopify.com) */
|
|
23
23
|
connectionDomain?: string;
|
|
24
|
-
/** How this editor is rendered in the
|
|
24
|
+
/** How this editor is rendered in the console. */
|
|
25
25
|
configMode?: 'editor' | 'sidebar';
|
|
26
26
|
/** Currently selected field keys (sidebar mode). */
|
|
27
27
|
selectedFieldKeys?: string[];
|
|
@@ -67,7 +67,7 @@ export interface EditorContextValue {
|
|
|
67
67
|
resize: (height: number) => void;
|
|
68
68
|
/** Report dirty state (unsaved changes). */
|
|
69
69
|
setDirty: (isDirty: boolean) => void;
|
|
70
|
-
/** Request the host to navigate to
|
|
70
|
+
/** Request the host to navigate to a console path. */
|
|
71
71
|
requestNavigate: (path: string) => void;
|
|
72
72
|
/** Request the host to execute a platform operation. Returns the result.
|
|
73
73
|
* `mode` controls whether the platform waits for the operation to complete
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foir/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Official Foir SDK — editor iframe client + server-side platform helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -177,8 +177,8 @@
|
|
|
177
177
|
"ws": "^8.18.0",
|
|
178
178
|
"y-websocket": "^3.0.0",
|
|
179
179
|
"yjs": "^13.6.30",
|
|
180
|
-
"@foir/
|
|
181
|
-
"@foir/
|
|
180
|
+
"@foir/proto-ts": "0.109.0",
|
|
181
|
+
"@foir/access-vocab": "0.0.0"
|
|
182
182
|
},
|
|
183
183
|
"engines": {
|
|
184
184
|
"node": ">=18.0.0"
|