@foir/sdk 0.25.0 → 0.27.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 -0
- package/dist/app/client.d.ts +82 -14
- package/dist/app/index.d.ts +1 -1
- package/dist/app/vocabulary.generated.d.ts +1 -1
- package/dist/app.js +24 -6
- package/dist/auth/auth.d.ts +10 -1
- package/dist/auth/hosted.d.ts +14 -3
- package/dist/auth/index.d.ts +3 -3
- package/dist/auth/operations.d.ts +32 -0
- package/dist/auth/ops.d.ts +1 -0
- package/dist/auth/session-client.d.ts +7 -2
- package/dist/auth-session.js +2 -1
- package/dist/auth.js +16 -3
- package/dist/data/builtins.d.ts +38 -13
- package/dist/data/index.d.ts +3 -3
- package/dist/data.js +11 -1
- package/dist/next.js +3 -0
- package/dist/placement/index.d.ts +17 -0
- package/dist/placement.js +16 -3
- package/dist/push.js +3 -0
- package/dist/react/FoirFeedback.d.ts +12 -5
- package/dist/react/FoirProvider.d.ts +5 -4
- package/dist/react/feedback-notes.d.ts +38 -4
- package/dist/react.js +116 -23
- package/dist/server/index.d.ts +1 -1
- package/dist/server.js +13 -1
- package/dist/transport/foir-http.d.ts +17 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -108,6 +108,90 @@ nobody asking for `@foir/sdk` gets it early. The push that carries the commit to
|
|
|
108
108
|
|
|
109
109
|
Changes on `dev` that have not been published to npm.
|
|
110
110
|
|
|
111
|
+
## [0.27.0] — 2026-10-20
|
|
112
|
+
|
|
113
|
+
### Added
|
|
114
|
+
|
|
115
|
+
- **`organizations.startPool({ movePeople, dryRun })`** (`@foir/sdk/app`):
|
|
116
|
+
starting the run's organization's own pool takes its people along when
|
|
117
|
+
asked. Somebody who stands only there moves whole, and somebody who also
|
|
118
|
+
stands elsewhere is given a new user there with their memberships, roles and
|
|
119
|
+
what they own there. `dryRun` counts what would move and changes nothing.
|
|
120
|
+
The `AppOrganizationPoolMove` and `AppOrganizationPoolStart` types.
|
|
121
|
+
- **`stepUpRequired(error)`** and the `STEP_UP_REQUIRED` error code: what an
|
|
122
|
+
organization's sign-in rules ask of the signed-in user before a request
|
|
123
|
+
standing there can succeed, `second_factor` or `sign_in_method`.
|
|
124
|
+
- **`myOrganizationChoices`**: the organizations the signed-in user can enter,
|
|
125
|
+
as an organization switcher offers them: member, invited, may join or
|
|
126
|
+
requested, each marked available or not, and whether they stand at the
|
|
127
|
+
project. Requires the user's bearer.
|
|
128
|
+
- **`organizations.provision({ joining })`** (`@foir/sdk/app`): who may join
|
|
129
|
+
the new organization without an invitation. `'invitation'` takes only
|
|
130
|
+
invitations and staff adding people, `'open'` lets someone who can already
|
|
131
|
+
sign in at its page join by signing in there, and `'request'` makes signing
|
|
132
|
+
in there a request its staff approve or decline. Left out, the nearest
|
|
133
|
+
organization above that chose, then the project.
|
|
134
|
+
- **`organizations.provision({ users })`** (`@foir/sdk/app`): where somebody
|
|
135
|
+
who signs up at the new organization stands. `'joined'` makes them a user of
|
|
136
|
+
that organization, and `'everyone'` a user of the project, so every
|
|
137
|
+
organization that shares its sign-in can find, email and sign them in. Left
|
|
138
|
+
out, the nearest organization above that chose, then the project.
|
|
139
|
+
- **`<FoirFeedback>` takes a note from a visitor who is not signed in**
|
|
140
|
+
(`@foir/sdk/react`), on a project that takes visitors' notes ("Accept
|
|
141
|
+
anonymous notes") through a key that gives them `notes:create`. The visitor
|
|
142
|
+
gets the composer rather than a sign-in prompt, with an optional email to
|
|
143
|
+
reply to, and is shown the note's `id` as a reference. The new
|
|
144
|
+
`onVisitorSubmitted` prop is called with the receipt; `onSubmitted` stays the
|
|
145
|
+
signed-in user's note. A project that does not take visitors' notes still
|
|
146
|
+
asks them to sign in.
|
|
147
|
+
- **`FoirNoteReceipt`** (`@foir/sdk/data`): `{ id, createdAt }`, what
|
|
148
|
+
`writeNote` answers a visitor with.
|
|
149
|
+
- **`FoirNoteConfig.signedIn` and `FoirNoteConfig.anonymousAccepted`**:
|
|
150
|
+
whether the caller writes as a signed-in user, and whether the project takes
|
|
151
|
+
visitors' notes. `screenshotsEnabled` is false for a visitor, whose note
|
|
152
|
+
carries no attachment.
|
|
153
|
+
- **`FoirFeedbackClient` carries an optional `writeNote`**, so a client typed
|
|
154
|
+
before it still satisfies the prop.
|
|
155
|
+
|
|
156
|
+
### Changed
|
|
157
|
+
|
|
158
|
+
- **`<FoirFeedback>` writes a signed-in user's note through `writeNote`**, the
|
|
159
|
+
one note create, rather than `createMyNote`, its older name. The same note
|
|
160
|
+
on the same data API. A client whose generated module predates `writeNote`
|
|
161
|
+
is refused it, saying to re-run `foir types`; a client typed without it at
|
|
162
|
+
all still writes through `createMyNote`.
|
|
163
|
+
- **Breaking: `organizations.startPool()` answers `{ pool, moved }`**
|
|
164
|
+
(`AppOrganizationPoolStart`): the pool it signs its people in to now, and
|
|
165
|
+
what moved into it by kind. It answered the pool alone.
|
|
166
|
+
- **`organizations.provision({ ownPool: false })` is sent.** It used to be
|
|
167
|
+
dropped, so it read as leaving the choice to the project; it now says the
|
|
168
|
+
organization shares the pool above it, whatever the project chose. Left out,
|
|
169
|
+
it still takes the project's choice for a top-level organization and its
|
|
170
|
+
parent's pool for one beneath another.
|
|
171
|
+
|
|
172
|
+
## [0.26.0] — 2026-10-19
|
|
173
|
+
|
|
174
|
+
### Changed
|
|
175
|
+
|
|
176
|
+
- **`session.request` in `@foir/sdk/placement` throws `PlacementRequestFailed`.**
|
|
177
|
+
Its message says each distinct refusal once, where it repeated a read
|
|
178
|
+
refused on a field once per row, and its `errors` keeps every error with its
|
|
179
|
+
`path`. It is still an `Error`, so a caller that reads `message` is
|
|
180
|
+
unaffected.
|
|
181
|
+
|
|
182
|
+
### Added
|
|
183
|
+
|
|
184
|
+
- **A hosted sign-in can name the organization it is for** (`@foir/sdk/auth`,
|
|
185
|
+
`@foir/sdk/react`): `hostedLoginUrl(config, returnTo, { org })`, a session
|
|
186
|
+
client's `signInUrl(returnTo, { org })` and the provider's
|
|
187
|
+
`hostedSignInUrl(returnTo, { org })`. Use it when one auth client serves an
|
|
188
|
+
address per organization. The login domain's front door passes it on, and
|
|
189
|
+
the sign-in takes that organization's branding, providers and return
|
|
190
|
+
addresses. The platform refuses an organization other than the one the
|
|
191
|
+
domain or app is bound to, one of another project, and one that is not
|
|
192
|
+
active.
|
|
193
|
+
- **`HostedLoginOptions`** (`@foir/sdk/auth`, type).
|
|
194
|
+
|
|
111
195
|
## [0.25.0] — 2026-10-18
|
|
112
196
|
|
|
113
197
|
### Changed
|
package/dist/app/client.d.ts
CHANGED
|
@@ -241,9 +241,26 @@ export interface AppOrganizationProvision {
|
|
|
241
241
|
* The organisation signs its own people in, to a pool of its own, apart
|
|
242
242
|
* from its parent's and the project's. Its descendants share that pool
|
|
243
243
|
* unless one of them starts another. One that exists already starts one
|
|
244
|
-
* with `organizations.startPool`.
|
|
244
|
+
* with `organizations.startPool`. Left out, the project's choice for one at
|
|
245
|
+
* the top, and its parent's pool for one beneath another.
|
|
245
246
|
*/
|
|
246
247
|
ownPool?: boolean;
|
|
248
|
+
/**
|
|
249
|
+
* Where somebody who signs up at it stands: `'joined'` makes them a user of
|
|
250
|
+
* this organisation, and `'everyone'` a user of the project, so every
|
|
251
|
+
* organisation that shares its sign-in can find, email and sign them in.
|
|
252
|
+
* Left out, the nearest organisation above that chose, then the project.
|
|
253
|
+
* Its first member is a member of it either way.
|
|
254
|
+
*/
|
|
255
|
+
users?: 'joined' | 'everyone';
|
|
256
|
+
/**
|
|
257
|
+
* Who may join it without an invitation: `'invitation'` (only an invitation
|
|
258
|
+
* or staff adding them), `'open'` (someone who can already sign in at its
|
|
259
|
+
* page joins by signing in there) or `'request'` (signing in there asks, and
|
|
260
|
+
* its staff approve or decline). Left out, the nearest organisation above
|
|
261
|
+
* that chose, then the project.
|
|
262
|
+
*/
|
|
263
|
+
joining?: 'invitation' | 'open' | 'request';
|
|
247
264
|
}
|
|
248
265
|
/**
|
|
249
266
|
* The pool an organisation's people sign in to, and where it comes from: its
|
|
@@ -258,6 +275,44 @@ export interface AppOrganizationPool {
|
|
|
258
275
|
/** How many people the pool holds, when it is the organisation's own. Null for any other. */
|
|
259
276
|
people: number | null;
|
|
260
277
|
}
|
|
278
|
+
/** What starting an organisation's own pool moved, or for a dry run would move, by kind. */
|
|
279
|
+
export interface AppOrganizationPoolMove {
|
|
280
|
+
/** Whether nothing was changed. */
|
|
281
|
+
dryRun: boolean;
|
|
282
|
+
/** People who stood only there, moved whole into the new pool. */
|
|
283
|
+
peopleMoved: number;
|
|
284
|
+
/** People who also stand elsewhere, each given a new user in the new pool. */
|
|
285
|
+
peopleMade: number;
|
|
286
|
+
/** Of those, how many are emailed a link to set up their account. */
|
|
287
|
+
peopleTold: number;
|
|
288
|
+
/** Memberships of the people given a new user, moved to that new user. */
|
|
289
|
+
membershipsMoved: number;
|
|
290
|
+
/** Active roles held in those memberships. */
|
|
291
|
+
rolesMoved: number;
|
|
292
|
+
recordsMoved: number;
|
|
293
|
+
filesMoved: number;
|
|
294
|
+
/** Notes assigned to them there, now assigned to the new user. A note stays its author's. */
|
|
295
|
+
notesReassigned: number;
|
|
296
|
+
notificationsMoved: number;
|
|
297
|
+
/** Their choices of what they are told there. */
|
|
298
|
+
notificationChoicesMoved: number;
|
|
299
|
+
/** The access they were given to single resources there. */
|
|
300
|
+
accessGrantsMoved: number;
|
|
301
|
+
/** Open invitations there, now to the new pool. */
|
|
302
|
+
invitationsMoved: number;
|
|
303
|
+
/** Requests to join from people it no longer signs in, withdrawn. */
|
|
304
|
+
joinRequestsWithdrawn: number;
|
|
305
|
+
/** Apps that now sign in the new pool's people. */
|
|
306
|
+
appsMoved: number;
|
|
307
|
+
/** Sign-ins through those apps of anybody not in the new pool, ended. */
|
|
308
|
+
signInsEnded: number;
|
|
309
|
+
}
|
|
310
|
+
/** Starting an organisation's own pool: the pool it signs its people in to now, and what moved into it. */
|
|
311
|
+
export interface AppOrganizationPoolStart {
|
|
312
|
+
/** Its pool, now its own; for a dry run, the pool it signs in to still. */
|
|
313
|
+
pool: AppOrganizationPool;
|
|
314
|
+
moved: AppOrganizationPoolMove;
|
|
315
|
+
}
|
|
261
316
|
/**
|
|
262
317
|
* Who a consent change is for: a user whose id the app holds, or an
|
|
263
318
|
* address.
|
|
@@ -483,9 +538,12 @@ export interface AppClient {
|
|
|
483
538
|
};
|
|
484
539
|
users: {
|
|
485
540
|
/**
|
|
486
|
-
* The user who has this email
|
|
487
|
-
*
|
|
488
|
-
*
|
|
541
|
+
* The user who has this email in the sign-in the install's place uses,
|
|
542
|
+
* made when nobody has it. The same user every time for one email. One
|
|
543
|
+
* the place does not reach is answered only where its organization lets
|
|
544
|
+
* them in (open, or their verified address at its open domain), joining
|
|
545
|
+
* it first; anybody else is refused without saying who has the address.
|
|
546
|
+
* Needs `users:create`.
|
|
489
547
|
*
|
|
490
548
|
* `verified` says whether that address is one the user has proved is
|
|
491
549
|
* theirs. A user this call makes has not, and neither has one an
|
|
@@ -586,17 +644,27 @@ export interface AppClient {
|
|
|
586
644
|
pool(): Promise<AppOrganizationPool>;
|
|
587
645
|
/**
|
|
588
646
|
* Start the own pool of the organisation this run acts in, which it and
|
|
589
|
-
* everything beneath it sign their people in to.
|
|
590
|
-
* `organizations:write`.
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
647
|
+
* everything beneath it that shares its pool sign their people in to.
|
|
648
|
+
* Needs `organizations:write`.
|
|
649
|
+
*
|
|
650
|
+
* Without `movePeople`, refused while anybody of the pool it signs in to
|
|
651
|
+
* now holds a membership in it or beneath it, or an invitation there is
|
|
652
|
+
* open to that pool, and the refusal says how many. With it, somebody who
|
|
653
|
+
* stands only there moves whole, with their addresses, sign-ins and
|
|
654
|
+
* sessions, and somebody who also stands elsewhere is given a new user
|
|
655
|
+
* there, with their memberships, roles and what they own there, who
|
|
656
|
+
* proves their address before they sign in; refused while the new pool
|
|
657
|
+
* would hold an address twice. `dryRun` answers what would move and
|
|
658
|
+
* changes nothing.
|
|
659
|
+
*
|
|
660
|
+
* Its apps, and those beneath it, then sign their people in there, and
|
|
661
|
+
* anybody signed in through them who is not in the new pool is signed
|
|
662
|
+
* out. Safe to run again: it answers the pool it has and moves nobody.
|
|
598
663
|
*/
|
|
599
|
-
startPool(
|
|
664
|
+
startPool(options?: {
|
|
665
|
+
movePeople?: boolean;
|
|
666
|
+
dryRun?: boolean;
|
|
667
|
+
}): Promise<AppOrganizationPoolStart>;
|
|
600
668
|
/**
|
|
601
669
|
* Drop the own pool of the organisation this run acts in, so it signs its
|
|
602
670
|
* people in to the pool above it. Needs `organizations:write`. Refused
|
package/dist/app/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* The manifest's own messages are on `@foir/sdk/app/contract`.
|
|
22
22
|
*/
|
|
23
23
|
export { createAppClient } from './client';
|
|
24
|
-
export type { AppBinding, AppClient, AppClientConfig, AppConsentChange, AppConsentSubject, AppUser, AppUserEraseResult, AppUserList, AppUserSaveResult, AppFieldMapping, AppInstallConfig, AppNotification, AppOperationStart, AppOrganizationPool, AppOrganizationProvision, AppRecord, AppRecordList, AppStartedOperation, AppStatusReport, AppSyncDirection, AppUpsertResult, AppWhoami, } from './client';
|
|
24
|
+
export type { AppBinding, AppClient, AppClientConfig, AppConsentChange, AppConsentSubject, AppUser, AppUserEraseResult, AppUserList, AppUserSaveResult, AppFieldMapping, AppInstallConfig, AppNotification, AppOperationStart, AppOrganizationPool, AppOrganizationPoolMove, AppOrganizationPoolStart, AppOrganizationProvision, AppRecord, AppRecordList, AppStartedOperation, AppStatusReport, AppSyncDirection, AppUpsertResult, AppWhoami, } from './client';
|
|
25
25
|
export { appUserEmailUnavailableOf, appUserUnlinkedOf, appLookupConflictOf, isAppFieldWithheld } from './errors';
|
|
26
26
|
export type { AppUserEmailUnavailableReason, AppLookupConflict } from './errors';
|
|
27
27
|
export { appModelFieldName, appModelTypeName, appSelectorEnumValue } from './naming';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* manifest's hook may subscribe to (an event of the project, that something
|
|
4
4
|
* emits) is the platform's to say: `validateAppManifest` answers it.
|
|
5
5
|
*/
|
|
6
|
-
export declare const APP_HOOK_EVENTS: readonly ["RECORD_CREATED", "RECORD_UPDATED", "RECORD_DELETED", "RECORD_PUBLISHED", "RECORD_UNPUBLISHED", "USER_CREATED", "USER_UPDATED", "USER_DELETED", "USER_MERGED", "SESSION_REVOKED", "OPERATION_COMPLETED", "OPERATION_FAILED", "OPERATION_CANCELLED", "OPERATION_TIMED_OUT", "MEMBERSHIP_GRANTED", "MEMBERSHIP_REVOKED", "MEMBERSHIP_ROLE_CHANGED", "TENANT_PLAN_CHANGED", "TENANT_SUBSCRIPTION_STATUS_CHANGED", "PROJECT_CREATED", "PROJECT_ARCHIVED", "INVITATION_ACCEPTED", "IMPORT_COMPLETED", "MIGRATION_APPLIED", "BULK_UPDATE_COMPLETED", "BULK_PUBLISH_COMPLETED", "USER_PREFERENCE_CHANGED", "ORGANIZATION_CREATED", "ORGANIZATION_UPDATED", "ORGANIZATION_MOVED", "ORGANIZATION_SUSPENDED", "ORGANIZATION_RESUMED", "ORGANIZATION_DELETION_SCHEDULED", "ORGANIZATION_DELETION_CANCELLED", "ORGANIZATION_DELETED", "ORGANIZATION_MEMBER_ADDED", "ORGANIZATION_MEMBER_REMOVED", "ORGANIZATION_MEMBER_ROLE_CHANGED", "PROJECT_REACTIVATED"];
|
|
6
|
+
export declare const APP_HOOK_EVENTS: readonly ["RECORD_CREATED", "RECORD_UPDATED", "RECORD_DELETED", "RECORD_PUBLISHED", "RECORD_UNPUBLISHED", "USER_CREATED", "USER_UPDATED", "USER_DELETED", "USER_MERGED", "SESSION_REVOKED", "OPERATION_COMPLETED", "OPERATION_FAILED", "OPERATION_CANCELLED", "OPERATION_TIMED_OUT", "MEMBERSHIP_GRANTED", "MEMBERSHIP_REVOKED", "MEMBERSHIP_ROLE_CHANGED", "TENANT_PLAN_CHANGED", "TENANT_SUBSCRIPTION_STATUS_CHANGED", "PROJECT_CREATED", "PROJECT_ARCHIVED", "INVITATION_ACCEPTED", "IMPORT_COMPLETED", "MIGRATION_APPLIED", "BULK_UPDATE_COMPLETED", "BULK_PUBLISH_COMPLETED", "USER_PREFERENCE_CHANGED", "ORGANIZATION_CREATED", "ORGANIZATION_UPDATED", "ORGANIZATION_MOVED", "ORGANIZATION_SUSPENDED", "ORGANIZATION_RESUMED", "ORGANIZATION_DELETION_SCHEDULED", "ORGANIZATION_DELETION_CANCELLED", "ORGANIZATION_DELETED", "ORGANIZATION_MEMBER_ADDED", "ORGANIZATION_MEMBER_REMOVED", "ORGANIZATION_MEMBER_ROLE_CHANGED", "PROJECT_REACTIVATED", "JOIN_REQUESTED", "JOIN_REQUEST_APPROVED", "JOIN_REQUEST_DECLINED"];
|
|
7
7
|
/** An event a hook can name. */
|
|
8
8
|
export type AppHookEvent = (typeof APP_HOOK_EVENTS)[number];
|
|
9
9
|
/**
|
package/dist/app.js
CHANGED
|
@@ -353,8 +353,8 @@ function createAppClient(config) {
|
|
|
353
353
|
throw new FoirRequestError(`[foir] ${message}`);
|
|
354
354
|
};
|
|
355
355
|
const POOL_FIELDS = "organizationId poolId source sourceOrganizationId people";
|
|
356
|
-
|
|
357
|
-
|
|
356
|
+
const POOL_MOVE_FIELDS = "dryRun peopleMoved peopleMade peopleTold membershipsMoved rolesMoved recordsMoved filesMoved notesReassigned notificationsMoved notificationChoicesMoved accessGrantsMoved invitationsMoved joinRequestsWithdrawn appsMoved signInsEnded";
|
|
357
|
+
function poolFrom(name, answer) {
|
|
358
358
|
if (!answer?.poolId || !answer.organizationId || answer.source !== "OWN" && answer.source !== "ANCESTOR" && answer.source !== "PROJECT") {
|
|
359
359
|
return refuse(`${name} reported no error and answered no pool`);
|
|
360
360
|
}
|
|
@@ -366,6 +366,9 @@ function createAppClient(config) {
|
|
|
366
366
|
people: answer.people ?? null
|
|
367
367
|
};
|
|
368
368
|
}
|
|
369
|
+
async function poolOf(name, document) {
|
|
370
|
+
return poolFrom(name, await field(name, document, {}));
|
|
371
|
+
}
|
|
369
372
|
let surface;
|
|
370
373
|
function served() {
|
|
371
374
|
surface ??= withFoirRetry(async () => {
|
|
@@ -854,7 +857,9 @@ function createAppClient(config) {
|
|
|
854
857
|
input.firstMember = { userId: first.userId };
|
|
855
858
|
}
|
|
856
859
|
}
|
|
857
|
-
if (organization.ownPool ===
|
|
860
|
+
if (typeof organization.ownPool === "boolean") input.ownPool = organization.ownPool;
|
|
861
|
+
if (organization.users !== void 0) input.users = organization.users;
|
|
862
|
+
if (organization.joining !== void 0) input.joining = organization.joining;
|
|
858
863
|
const answered = settings === void 0 ? "organizationId status" : "organizationId settingsRecordId status";
|
|
859
864
|
const answer = await field(
|
|
860
865
|
"provisionOrganization",
|
|
@@ -882,8 +887,18 @@ function createAppClient(config) {
|
|
|
882
887
|
async pool() {
|
|
883
888
|
return poolOf("organizationPool", `query AppOrganizationPool { organizationPool { ${POOL_FIELDS} } }`);
|
|
884
889
|
},
|
|
885
|
-
async startPool() {
|
|
886
|
-
|
|
890
|
+
async startPool(options = {}) {
|
|
891
|
+
const asks = `movePeople: ${options.movePeople === true}, dryRun: ${options.dryRun === true}`;
|
|
892
|
+
const answer = await field(
|
|
893
|
+
"startOrganizationPool",
|
|
894
|
+
`mutation AppStartOrganizationPool { startOrganizationPool(${asks}) { pool { ${POOL_FIELDS} } moved { ${POOL_MOVE_FIELDS} } } }`,
|
|
895
|
+
{}
|
|
896
|
+
);
|
|
897
|
+
const pool = poolFrom("startOrganizationPool", answer?.pool);
|
|
898
|
+
if (!answer?.moved) {
|
|
899
|
+
return refuse("startOrganizationPool reported no error and answered nothing of what moved");
|
|
900
|
+
}
|
|
901
|
+
return { pool, moved: answer.moved };
|
|
887
902
|
},
|
|
888
903
|
async dropPool() {
|
|
889
904
|
return poolOf("dropOrganizationPool", `mutation AppDropOrganizationPool { dropOrganizationPool { ${POOL_FIELDS} } }`);
|
|
@@ -1218,7 +1233,10 @@ var APP_HOOK_EVENTS = [
|
|
|
1218
1233
|
"ORGANIZATION_MEMBER_ADDED",
|
|
1219
1234
|
"ORGANIZATION_MEMBER_REMOVED",
|
|
1220
1235
|
"ORGANIZATION_MEMBER_ROLE_CHANGED",
|
|
1221
|
-
"PROJECT_REACTIVATED"
|
|
1236
|
+
"PROJECT_REACTIVATED",
|
|
1237
|
+
"JOIN_REQUESTED",
|
|
1238
|
+
"JOIN_REQUEST_APPROVED",
|
|
1239
|
+
"JOIN_REQUEST_DECLINED"
|
|
1222
1240
|
];
|
|
1223
1241
|
var APP_GRANTS = [
|
|
1224
1242
|
"appconfig:read",
|
package/dist/auth/auth.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { type FoirEnv } from '../graphql/foir-request';
|
|
19
19
|
import { type AuthOpName, type AuthOpOptions } from './ops';
|
|
20
|
-
import { type AuthConfig, type AuthProviderPublic, type CurrentUser, type AuthResult, type DefaultAuthProvider, type AuthorizedApp, type DeleteAccountResult, type MyOrganization, type MyProfile, type MyProfileChange, type MySession, type MyAddress, type MyDataExport, type MyFactor, type StepUp, type FactorEnrollment, type MySignInMethod, type NewPersonalAccessToken, type ProviderCallbackInput, type ProviderLoginInput, type ProviderLoginResult, type ProviderOTPVerifyInput, type ProviderToken, type SignupContext } from './operations';
|
|
20
|
+
import { type AuthConfig, type AuthProviderPublic, type CurrentUser, type AuthResult, type DefaultAuthProvider, type AuthorizedApp, type DeleteAccountResult, type MyOrganization, type MyOrganizationChoices, type MyProfile, type MyProfileChange, type MySession, type MyAddress, type MyDataExport, type MyFactor, type StepUp, type FactorEnrollment, type MySignInMethod, type NewPersonalAccessToken, type ProviderCallbackInput, type ProviderLoginInput, type ProviderLoginResult, type ProviderOTPVerifyInput, type ProviderToken, type SignupContext } from './operations';
|
|
21
21
|
import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
22
22
|
/** An access token + its rotating refresh token. */
|
|
23
23
|
export interface FoirAuthTokens {
|
|
@@ -380,6 +380,15 @@ export declare function myOrganizations(env: FoirEnv, vars?: {
|
|
|
380
380
|
first?: number;
|
|
381
381
|
after?: string;
|
|
382
382
|
}, options?: AuthOpOptions): Promise<MyOrganization[]>;
|
|
383
|
+
/**
|
|
384
|
+
* The organizations the signed-in user can enter, as an organization switcher
|
|
385
|
+
* offers them: those they are a member of, are invited to, may join by signing
|
|
386
|
+
* in there, or asked to join, in their own sign-in. It is theirs alone. An
|
|
387
|
+
* organization that lets them in no way is never named, and one that may not
|
|
388
|
+
* act now is listed with `available: false` rather than left out. Requires the
|
|
389
|
+
* user's bearer.
|
|
390
|
+
*/
|
|
391
|
+
export declare function myOrganizationChoices(env: FoirEnv, options?: AuthOpOptions): Promise<MyOrganizationChoices>;
|
|
383
392
|
/**
|
|
384
393
|
* Found an organization, as its first member.
|
|
385
394
|
*
|
package/dist/auth/hosted.d.ts
CHANGED
|
@@ -27,11 +27,22 @@ export interface HostedLoginConfig {
|
|
|
27
27
|
*/
|
|
28
28
|
clientId?: string;
|
|
29
29
|
}
|
|
30
|
+
/** What a sign-in started at the hosted front door is for. */
|
|
31
|
+
export interface HostedLoginOptions {
|
|
32
|
+
/**
|
|
33
|
+
* The organization the person signs in to, when one auth client serves an
|
|
34
|
+
* address per organization. It decides the branding, the pool, and which
|
|
35
|
+
* addresses the person may come back to. The platform refuses an
|
|
36
|
+
* organization other than the one the login domain or app is bound to, one
|
|
37
|
+
* of another project, and one that is not active.
|
|
38
|
+
*/
|
|
39
|
+
org?: string;
|
|
40
|
+
}
|
|
30
41
|
/**
|
|
31
|
-
* Build the redirect URL to the hosted front door, forwarding
|
|
32
|
-
*
|
|
42
|
+
* Build the redirect URL to the hosted front door, forwarding an absolute
|
|
43
|
+
* `returnTo` and, where the app names one, the organization the sign-in is for.
|
|
33
44
|
*/
|
|
34
|
-
export declare function hostedLoginUrl(config: HostedLoginConfig, returnTo?: string): string;
|
|
45
|
+
export declare function hostedLoginUrl(config: HostedLoginConfig, returnTo?: string, options?: HostedLoginOptions): string;
|
|
35
46
|
/**
|
|
36
47
|
* Build the redirect URL that ENDS the hosted session — the mirror of
|
|
37
48
|
* `hostedLoginUrl`, and the only thing that actually signs a user out on
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* the caller's own manifest advertises, through the same op transport
|
|
11
11
|
* `@foir/sdk/data` uses, so `graphql` is not on this module's path at all.
|
|
12
12
|
*/
|
|
13
|
-
export { login, register, requestOTP, loginWithOTP, refreshTokens, loginWithProvider, providerCallback, providerVerifyOTP, providerSignInRefusalOf, signInRefusalOf, confirmProviderLink, resendProviderLinkCode, getAuthProviders, getAuthConfig, getDefaultAuthProvider, signupContext, joinWaitlist, requestPasswordReset, resetPassword, updatePassword, currentUser, myProviderToken, logout, deleteMyAccount, leaveMembership, updateMyProfile, myAuthorizedApps, revokeAuthorizedApp, mySessions, endMySession, myAddresses, addMyAddress, proveMyAddress, makeMyAddressPrimary, removeMyAddress, exportMyData, signOutEverywhere, createPersonalAccessToken, mySignInMethods, connectSignInMethod, disconnectSignInMethod, myFactors, beginStepUp, completeStepUp, beginFactorEnrollment, completeFactorEnrollment, removeFactor, renameFactor, makeRecoveryCodes, myOrganizations, registerOrganization, saveSession, clearSession, getSessionToken, getSessionUser, signInWithPassword, signUpWithPassword, signInWithOTP, resetPasswordAndSignIn, refreshSession, refreshSessionOnce, signOut, authedRequest, authedOp, requireAuth, } from './auth';
|
|
13
|
+
export { login, register, requestOTP, loginWithOTP, refreshTokens, loginWithProvider, providerCallback, providerVerifyOTP, providerSignInRefusalOf, signInRefusalOf, confirmProviderLink, resendProviderLinkCode, getAuthProviders, getAuthConfig, getDefaultAuthProvider, signupContext, joinWaitlist, requestPasswordReset, resetPassword, updatePassword, currentUser, myProviderToken, logout, deleteMyAccount, leaveMembership, updateMyProfile, myAuthorizedApps, revokeAuthorizedApp, mySessions, endMySession, myAddresses, addMyAddress, proveMyAddress, makeMyAddressPrimary, removeMyAddress, exportMyData, signOutEverywhere, createPersonalAccessToken, mySignInMethods, connectSignInMethod, disconnectSignInMethod, myFactors, beginStepUp, completeStepUp, beginFactorEnrollment, completeFactorEnrollment, removeFactor, renameFactor, makeRecoveryCodes, myOrganizations, myOrganizationChoices, registerOrganization, saveSession, clearSession, getSessionToken, getSessionUser, signInWithPassword, signUpWithPassword, signInWithOTP, resetPasswordAndSignIn, refreshSession, refreshSessionOnce, signOut, authedRequest, authedOp, requireAuth, } from './auth';
|
|
14
14
|
export type { FoirAuthTokens, FoirUser, SessionAdapter, RequireAuthOptions, } from './auth';
|
|
15
15
|
export { userAccess, getSessionAccess, hasRole, hasScope, requireRole, requireScope, FoirAccessDeniedError, } from './access';
|
|
16
16
|
export type { FoirAccess, FoirAccessDenial } from './access';
|
|
@@ -18,7 +18,7 @@ export { FoirAuthDowngradedError, FoirSessionExpiredError } from '../graphql/foi
|
|
|
18
18
|
export { AUTH_OPS } from './ops';
|
|
19
19
|
export type { AuthOpName, AuthOpOptions } from './ops';
|
|
20
20
|
export { hostedLoginUrl, hostedLogoutUrl, fetchHostedSession, hostedSignInNoticeOf } from './hosted';
|
|
21
|
-
export type { HostedLoginConfig, HostedSession, HostedSessionResult, HostedSignInNotice } from './hosted';
|
|
21
|
+
export type { HostedLoginConfig, HostedLoginOptions, HostedSession, HostedSessionResult, HostedSignInNotice } from './hosted';
|
|
22
22
|
export { createFoirSessionClient } from './session-client';
|
|
23
23
|
export type { FoirSessionClient, FoirSessionConfig, FoirSessionUser, } from './session-client';
|
|
24
|
-
export type { AuthResult, ProviderLoginResult, AuthProviderPublic, ProviderLoginInput, ProviderCallbackInput, ProviderOTPVerifyInput, SignupContext, SignupMode, AuthConfig, AuthConfigProvider, DefaultAuthProvider, CurrentUser, ProviderToken, ProviderTokenRefresh, DeleteAccountResult, MyOrganization, MyProfile, MyProfileChange, AuthorizedApp, MySession, MyAddress, MyDataExport, NewPersonalAccessToken, MySignInMethod, MyFactor, StepUp, FactorEnrollment, } from './operations';
|
|
24
|
+
export type { AuthResult, ProviderLoginResult, AuthProviderPublic, ProviderLoginInput, ProviderCallbackInput, ProviderOTPVerifyInput, SignupContext, SignupMode, AuthConfig, AuthConfigProvider, DefaultAuthProvider, CurrentUser, ProviderToken, ProviderTokenRefresh, DeleteAccountResult, MyOrganization, MyOrganizationChoice, MyOrganizationChoices, MyProfile, MyProfileChange, AuthorizedApp, MySession, MyAddress, MyDataExport, NewPersonalAccessToken, MySignInMethod, MyFactor, StepUp, FactorEnrollment, } from './operations';
|
|
@@ -322,6 +322,38 @@ export interface AuthorizedApp {
|
|
|
322
322
|
/** ISO-8601. */
|
|
323
323
|
grantedAt: string | null;
|
|
324
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* An organization the calling user can enter, and how, as
|
|
327
|
+
* `myOrganizationChoices` lists it. In their own sign-in only; an organization
|
|
328
|
+
* that lets them in no way is never named.
|
|
329
|
+
*/
|
|
330
|
+
export interface MyOrganizationChoice {
|
|
331
|
+
orgId: string;
|
|
332
|
+
name: string;
|
|
333
|
+
displayName: string | null;
|
|
334
|
+
parentId: string | null;
|
|
335
|
+
/** `member`, `invited`, `may_join` or `requested`. */
|
|
336
|
+
relation: 'member' | 'invited' | 'may_join' | 'requested';
|
|
337
|
+
/** False for an organization that may not act now: suspended, or due to be deleted. */
|
|
338
|
+
available: boolean;
|
|
339
|
+
/** For `may_join`: `open`, signing in there joins; `request`, it asks. */
|
|
340
|
+
joining: 'open' | 'request' | null;
|
|
341
|
+
/** For `may_join`: a verified address of theirs is what lets them. */
|
|
342
|
+
byAddress: boolean;
|
|
343
|
+
/** For `invited`: the invitation waiting for them. */
|
|
344
|
+
invitationId: string | null;
|
|
345
|
+
/** For `requested`: their waiting request. */
|
|
346
|
+
requestId: string | null;
|
|
347
|
+
}
|
|
348
|
+
/** What `myOrganizationChoices` answers. */
|
|
349
|
+
export interface MyOrganizationChoices {
|
|
350
|
+
organizations: Array<MyOrganizationChoice>;
|
|
351
|
+
/**
|
|
352
|
+
* They stand at the project, and so at every organization sharing its
|
|
353
|
+
* sign-in; those are not listed one by one.
|
|
354
|
+
*/
|
|
355
|
+
projectMember: boolean;
|
|
356
|
+
}
|
|
325
357
|
/**
|
|
326
358
|
* One organization the calling user may stand in, as `myOrganizations`
|
|
327
359
|
* returns it.
|
package/dist/auth/ops.d.ts
CHANGED
|
@@ -82,6 +82,7 @@ export declare const AUTH_OPS: {
|
|
|
82
82
|
readonly renameFactor: "renameFactor";
|
|
83
83
|
readonly makeRecoveryCodes: "makeRecoveryCodes";
|
|
84
84
|
readonly myOrganizations: "myOrganizations";
|
|
85
|
+
readonly myOrganizationChoices: "myOrganizationChoices";
|
|
85
86
|
readonly registerOrganization: "registerOrganization";
|
|
86
87
|
readonly loginWithProvider: "loginWithProvider";
|
|
87
88
|
readonly providerCallback: "providerCallback";
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* api.foir.dev but a browser cannot reach them there — the cookie would be
|
|
28
28
|
* cross-site, and that CORS surface deliberately refuses credentials.
|
|
29
29
|
*/
|
|
30
|
+
import type { HostedLoginOptions } from './hosted';
|
|
30
31
|
export interface FoirSessionConfig {
|
|
31
32
|
/**
|
|
32
33
|
* Your verified custom login domain, e.g. `https://login.acme.com`. Must share
|
|
@@ -66,8 +67,12 @@ export interface FoirSessionClient {
|
|
|
66
67
|
getUser(): Promise<FoirSessionUser | null>;
|
|
67
68
|
/** Revoke the session server-side and clear the cookie. Idempotent. */
|
|
68
69
|
signOut(): Promise<void>;
|
|
69
|
-
/**
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Where to send the browser to sign in. Name the organization the sign-in is
|
|
72
|
+
* for with `options.org` when one auth client serves an address per
|
|
73
|
+
* organization.
|
|
74
|
+
*/
|
|
75
|
+
signInUrl(returnTo?: string, options?: HostedLoginOptions): string;
|
|
71
76
|
/** Observe session changes. Returns an unsubscribe function. */
|
|
72
77
|
subscribe(listener: (user: FoirSessionUser | null) => void): () => void;
|
|
73
78
|
/** Cancel the refresh timer. Call on unmount. */
|
package/dist/auth-session.js
CHANGED
|
@@ -113,10 +113,11 @@ function createFoirSessionClient(config) {
|
|
|
113
113
|
forget();
|
|
114
114
|
config.onSignOut?.();
|
|
115
115
|
},
|
|
116
|
-
signInUrl(returnTo) {
|
|
116
|
+
signInUrl(returnTo, options = {}) {
|
|
117
117
|
const dest = new URL(trimSlash(config.loginOrigin));
|
|
118
118
|
const target = returnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
|
|
119
119
|
if (target && target !== "/") dest.searchParams.set("returnTo", target);
|
|
120
|
+
if (options.org) dest.searchParams.set("org", options.org);
|
|
120
121
|
return dest.toString();
|
|
121
122
|
},
|
|
122
123
|
subscribe(listener) {
|
package/dist/auth.js
CHANGED
|
@@ -1130,6 +1130,9 @@ var AUTH_OPS = {
|
|
|
1130
1130
|
// names the organization it acts in (`orgId` or `orgUrl` below), and the
|
|
1131
1131
|
// platform checks the caller stands there every time.
|
|
1132
1132
|
myOrganizations: "myOrganizations",
|
|
1133
|
+
// Where they may go: the organizations they are a member of, are invited
|
|
1134
|
+
// to, may join, or asked to join. An organization switcher's choices.
|
|
1135
|
+
myOrganizationChoices: "myOrganizationChoices",
|
|
1133
1136
|
// Founding one. Present in a manifest only when the project opted in.
|
|
1134
1137
|
registerOrganization: "registerOrganization",
|
|
1135
1138
|
// External identity providers.
|
|
@@ -1371,6 +1374,9 @@ async function makeRecoveryCodes(env, elevatedHandle, options) {
|
|
|
1371
1374
|
async function myOrganizations(env, vars, options) {
|
|
1372
1375
|
return authOp(env, AUTH_OPS.myOrganizations, vars ?? {}, options);
|
|
1373
1376
|
}
|
|
1377
|
+
async function myOrganizationChoices(env, options) {
|
|
1378
|
+
return authOp(env, AUTH_OPS.myOrganizationChoices, {}, options);
|
|
1379
|
+
}
|
|
1374
1380
|
async function registerOrganization(env, vars, options) {
|
|
1375
1381
|
return authOp(env, AUTH_OPS.registerOrganization, vars, options);
|
|
1376
1382
|
}
|
|
@@ -1567,6 +1573,8 @@ var GRANT_TABLE = [
|
|
|
1567
1573
|
["authclients:write", "none", [], []],
|
|
1568
1574
|
["domains:read", "none", [], []],
|
|
1569
1575
|
["domains:write", "none", [], []],
|
|
1576
|
+
["loginpages:read", "none", [], []],
|
|
1577
|
+
["loginpages:write", "none", [], []],
|
|
1570
1578
|
["secrets:read", "none", [], []],
|
|
1571
1579
|
["secrets:list", "none", [], []],
|
|
1572
1580
|
["secrets:put", "none", [], []],
|
|
@@ -1604,7 +1612,7 @@ var GRANT_TABLE = [
|
|
|
1604
1612
|
["projects:read", "none", [], []],
|
|
1605
1613
|
["projects:write", "none", [], []],
|
|
1606
1614
|
["project:stand", "none", [], []],
|
|
1607
|
-
["project:manage", "none", [], ["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", "authclients:read", "authclients: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"]],
|
|
1615
|
+
["project:manage", "none", [], ["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", "authclients:read", "authclients:write", "domains:read", "domains:write", "loginpages:read", "loginpages: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"]],
|
|
1608
1616
|
["projects:create", "none", [], []],
|
|
1609
1617
|
["projects:delete", "none", [], []],
|
|
1610
1618
|
["tenants:read", "none", [], []],
|
|
@@ -1797,11 +1805,14 @@ async function requireScope(adapter, env, scope) {
|
|
|
1797
1805
|
var DEFAULT_COOKIE_NAME = "foir_session";
|
|
1798
1806
|
var DEFAULT_SESSION_PATH = "/session/token";
|
|
1799
1807
|
var DEFAULT_END_SESSION_PATH = "/session/end-session";
|
|
1800
|
-
function hostedLoginUrl(config, returnTo) {
|
|
1808
|
+
function hostedLoginUrl(config, returnTo, options = {}) {
|
|
1801
1809
|
const dest = new URL(config.loginOrigin);
|
|
1802
1810
|
if (returnTo && returnTo !== "/") {
|
|
1803
1811
|
dest.searchParams.set("returnTo", returnTo);
|
|
1804
1812
|
}
|
|
1813
|
+
if (options.org) {
|
|
1814
|
+
dest.searchParams.set("org", options.org);
|
|
1815
|
+
}
|
|
1805
1816
|
return dest.toString();
|
|
1806
1817
|
}
|
|
1807
1818
|
function hostedLogoutUrl(config, clientId, returnTo) {
|
|
@@ -2017,10 +2028,11 @@ function createFoirSessionClient(config) {
|
|
|
2017
2028
|
forget();
|
|
2018
2029
|
config.onSignOut?.();
|
|
2019
2030
|
},
|
|
2020
|
-
signInUrl(returnTo) {
|
|
2031
|
+
signInUrl(returnTo, options = {}) {
|
|
2021
2032
|
const dest = new URL(trimSlash(config.loginOrigin));
|
|
2022
2033
|
const target = returnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
|
|
2023
2034
|
if (target && target !== "/") dest.searchParams.set("returnTo", target);
|
|
2035
|
+
if (options.org) dest.searchParams.set("org", options.org);
|
|
2024
2036
|
return dest.toString();
|
|
2025
2037
|
},
|
|
2026
2038
|
subscribe(listener) {
|
|
@@ -2082,6 +2094,7 @@ export {
|
|
|
2082
2094
|
myAddresses,
|
|
2083
2095
|
myAuthorizedApps,
|
|
2084
2096
|
myFactors,
|
|
2097
|
+
myOrganizationChoices,
|
|
2085
2098
|
myOrganizations,
|
|
2086
2099
|
myProviderToken,
|
|
2087
2100
|
mySessions,
|
package/dist/data/builtins.d.ts
CHANGED
|
@@ -517,22 +517,50 @@ export interface FoirUserNote {
|
|
|
517
517
|
createdAt: string;
|
|
518
518
|
}
|
|
519
519
|
/**
|
|
520
|
-
* What a note surface has to know
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
* Charged `notes:create` rather than `notes:read`, and
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
520
|
+
* What a note surface has to know before it renders anything —
|
|
521
|
+
* `foir.ns.notes.noteConfig`.
|
|
522
|
+
*
|
|
523
|
+
* Charged `notes:create` rather than `notes:read`, and answered to exactly the
|
|
524
|
+
* callers `writeNote` takes a note from: a signed-in user, and a visitor with
|
|
525
|
+
* no session on a project that takes visitors' notes, through a key that gives
|
|
526
|
+
* them `notes:create`. Everyone else is refused. So a composer can read it as
|
|
527
|
+
* its single readiness probe: an answer means this caller may write, and says
|
|
528
|
+
* how; a refusal names which half is missing.
|
|
527
529
|
*/
|
|
528
530
|
export interface FoirNoteConfig {
|
|
529
531
|
/**
|
|
530
|
-
* Whether this
|
|
532
|
+
* Whether this caller's composer may OFFER to capture a screenshot: the
|
|
533
|
+
* project allows it, and the caller is signed in. Always false for a
|
|
534
|
+
* visitor, whose note carries no attachment.
|
|
531
535
|
*
|
|
532
536
|
* It gates the offer, not the storing. Capture stays opt-in per submission —
|
|
533
537
|
* the browser's own picker is the consent gate and it cannot be bypassed.
|
|
534
538
|
*/
|
|
535
539
|
screenshotsEnabled: boolean;
|
|
540
|
+
/**
|
|
541
|
+
* Whether the project takes notes from visitors who are not signed in
|
|
542
|
+
* (Notes settings → "Accept anonymous notes").
|
|
543
|
+
*/
|
|
544
|
+
anonymousAccepted: boolean;
|
|
545
|
+
/**
|
|
546
|
+
* Whether the note this caller writes is a signed-in user's. False is a
|
|
547
|
+
* visitor's: `writeNote` answers it with a {@link FoirNoteReceipt} and nothing
|
|
548
|
+
* more, and it may carry a `contactEmail`.
|
|
549
|
+
*/
|
|
550
|
+
signedIn: boolean;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* What `writeNote` answers a visitor with: the note's `id` and `createdAt`,
|
|
554
|
+
* and nothing else, because nobody is signed in to read the note back as. Ask
|
|
555
|
+
* for more and the write is refused before anything is written.
|
|
556
|
+
*
|
|
557
|
+
* Show the `id` to the visitor so they can quote it. Nothing on the public
|
|
558
|
+
* plane takes it to read the note.
|
|
559
|
+
*/
|
|
560
|
+
export interface FoirNoteReceipt {
|
|
561
|
+
id: string;
|
|
562
|
+
/** ISO-8601. */
|
|
563
|
+
createdAt: string;
|
|
536
564
|
}
|
|
537
565
|
/** `myNotes` arguments. Forward-only cursor pagination, like every list read. */
|
|
538
566
|
export interface FoirMyNotesArgs {
|
|
@@ -558,11 +586,8 @@ export type FoirMyNotesResult = FoirConnection<FoirUserNote>;
|
|
|
558
586
|
* A signed-in user's note is theirs and comes back in `myNotes`. A
|
|
559
587
|
* visitor's, through a key that gives `notes:create` to whoever comes through
|
|
560
588
|
* it, on a project that takes visitors' notes, is nobody's: ask for its `id`
|
|
561
|
-
* and `createdAt` and nothing else
|
|
562
|
-
*
|
|
563
|
-
* Served by a data API that has the field. `<FoirFeedback>` still writes
|
|
564
|
-
* through `createMyNote`, the same create under its older name, which every
|
|
565
|
-
* data API serves.
|
|
589
|
+
* and `createdAt` and nothing else (`pick: ['id', 'createdAt']`), because
|
|
590
|
+
* nothing reads it back. What comes back is a {@link FoirNoteReceipt}.
|
|
566
591
|
*/
|
|
567
592
|
export interface FoirWriteNoteArgs {
|
|
568
593
|
/** A visitor may write about a record and nothing else. */
|