@foir/sdk 0.26.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 +61 -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/index.d.ts +2 -2
- package/dist/auth/operations.d.ts +32 -0
- package/dist/auth/ops.d.ts +1 -0
- package/dist/auth.js +7 -0
- 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/push.js +3 -0
- package/dist/react/FoirFeedback.d.ts +12 -5
- package/dist/react/feedback-notes.d.ts +38 -4
- package/dist/react.js +110 -20
- package/dist/server/index.d.ts +1 -1
- package/dist/server.js +10 -0
- package/dist/transport/foir-http.d.ts +17 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -108,6 +108,67 @@ 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
|
+
|
|
111
172
|
## [0.26.0] — 2026-10-19
|
|
112
173
|
|
|
113
174
|
### 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/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';
|
|
@@ -21,4 +21,4 @@ export { hostedLoginUrl, hostedLogoutUrl, fetchHostedSession, hostedSignInNotice
|
|
|
21
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";
|
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
|
}
|
|
@@ -2088,6 +2094,7 @@ export {
|
|
|
2088
2094
|
myAddresses,
|
|
2089
2095
|
myAuthorizedApps,
|
|
2090
2096
|
myFactors,
|
|
2097
|
+
myOrganizationChoices,
|
|
2091
2098
|
myOrganizations,
|
|
2092
2099
|
myProviderToken,
|
|
2093
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. */
|
package/dist/data/index.d.ts
CHANGED
|
@@ -59,7 +59,7 @@ export { planFoirModelCall, planFoirNamespaceCall } from './document';
|
|
|
59
59
|
export type { FoirCall, FoirDocumentPlan, FoirNamespaceCall, FoirNamespaceSelections, FoirRecordPath, FoirSelectionModel, FoirSelectionNamespace, FoirSelections, FoirSelectionSource, FoirSelectionUnions, } from './document';
|
|
60
60
|
export { createFoirSubjectRegistry, recordFoirSubjects } from './subjects';
|
|
61
61
|
export type { FoirSubject, FoirSubjectEntry, FoirSubjectOptions, FoirSubjectOrigin, FoirSubjectRegistry, } from './subjects';
|
|
62
|
-
export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirWriteNoteArgs, FoirUserNote, FoirUserNoteAttachment, FoirDegraded, FoirDepthBelow, FoirDepthLevel, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirMyNotesArgs, FoirMyNotesResult, FoirNoteConfig, FoirNoteSubjectType, FoirPlatformType, FoirRecordOwner, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPoint, Image, ImageFormat, ImageVariant, ImageVariantSet, MediaValue, Passage, Prose, ProseSpan, RecordAncestor, Reference, Richtext, Video, } from './builtins';
|
|
62
|
+
export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirWriteNoteArgs, FoirUserNote, FoirUserNoteAttachment, FoirDegraded, FoirDepthBelow, FoirDepthLevel, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirMyNotesArgs, FoirMyNotesResult, FoirNoteConfig, FoirNoteReceipt, FoirNoteSubjectType, FoirPlatformType, FoirRecordOwner, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPoint, Image, ImageFormat, ImageVariant, ImageVariantSet, MediaValue, Passage, Prose, ProseSpan, RecordAncestor, Reference, Richtext, Video, } from './builtins';
|
|
63
63
|
/**
|
|
64
64
|
* Pre-rename spellings. The `<Name>Block` wrappers and the `<Name>Value`
|
|
65
65
|
* split are the same thing as the types above — an item IS its value — so each
|
|
@@ -81,5 +81,5 @@ export type { FoirConnection, FoirFieldsShape, FoirModelDefinition, FoirNamespac
|
|
|
81
81
|
export type { FoirInclude, FoirModelOperation, FoirNamespaceOperation, FoirOmittableKey, FoirOpResult, FoirQuery, FoirRecordResult, FoirWriteResult, } from './selection';
|
|
82
82
|
export { FoirDecimal, decimal } from './decimal';
|
|
83
83
|
export type { FoirDecimalDivOptions, FoirDecimalInput, FoirDecimalRounding, } from './decimal';
|
|
84
|
-
export { FoirRequestError, FoirAuthDowngradedError, FoirSessionExpiredError, FOIR_VISITOR_IP_HEADER, } from '../transport/foir-http';
|
|
85
|
-
export type { FoirEnv, FoirGraphQLErrorItem, FoirTokenProvider, FoirAuthDowngrade, FoirVisitor, FoirVisitorSource, } from '../transport/foir-http';
|
|
84
|
+
export { FoirRequestError, FoirAuthDowngradedError, FoirSessionExpiredError, FOIR_VISITOR_IP_HEADER, stepUpRequired, } from '../transport/foir-http';
|
|
85
|
+
export type { FoirEnv, FoirGraphQLErrorItem, FoirTokenProvider, FoirAuthDowngrade, FoirStepUp, FoirVisitor, FoirVisitorSource, } from '../transport/foir-http';
|
package/dist/data.js
CHANGED
|
@@ -10,6 +10,15 @@ var MALFORMED_PRUNE_REASONS = /* @__PURE__ */ new Set([
|
|
|
10
10
|
"subselections_on_scalar",
|
|
11
11
|
"missing_subselections"
|
|
12
12
|
]);
|
|
13
|
+
function stepUpRequired(error) {
|
|
14
|
+
if (!(error instanceof FoirRequestError)) return null;
|
|
15
|
+
for (const item of error.graphQLErrors ?? []) {
|
|
16
|
+
if (item.extensions?.code !== "STEP_UP_REQUIRED") continue;
|
|
17
|
+
const asked = item.extensions.stepUp;
|
|
18
|
+
if (asked === "second_factor" || asked === "sign_in_method") return asked;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
13
22
|
var FoirRequestError = class extends Error {
|
|
14
23
|
/** HTTP status code, when the request failed at the HTTP layer. */
|
|
15
24
|
status;
|
|
@@ -2686,5 +2695,6 @@ export {
|
|
|
2686
2695
|
planFoirModelCall,
|
|
2687
2696
|
planFoirNamespaceCall,
|
|
2688
2697
|
printSubscriptionDocument,
|
|
2689
|
-
recordFoirSubjects
|
|
2698
|
+
recordFoirSubjects,
|
|
2699
|
+
stepUpRequired
|
|
2690
2700
|
};
|
package/dist/next.js
CHANGED
|
@@ -1010,6 +1010,9 @@ var AUTH_OPS = {
|
|
|
1010
1010
|
// names the organization it acts in (`orgId` or `orgUrl` below), and the
|
|
1011
1011
|
// platform checks the caller stands there every time.
|
|
1012
1012
|
myOrganizations: "myOrganizations",
|
|
1013
|
+
// Where they may go: the organizations they are a member of, are invited
|
|
1014
|
+
// to, may join, or asked to join. An organization switcher's choices.
|
|
1015
|
+
myOrganizationChoices: "myOrganizationChoices",
|
|
1013
1016
|
// Founding one. Present in a manifest only when the project opted in.
|
|
1014
1017
|
registerOrganization: "registerOrganization",
|
|
1015
1018
|
// External identity providers.
|
package/dist/push.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.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FoirUserNote, FoirSubject, FoirSubjectRegistry } from '../data';
|
|
1
|
+
import type { FoirNoteReceipt, FoirUserNote, FoirSubject, FoirSubjectRegistry } from '../data';
|
|
2
2
|
import { type FoirFeedbackClient } from './feedback-notes';
|
|
3
3
|
export type { FoirFeedbackClient } from './feedback-notes';
|
|
4
4
|
export interface FoirFeedbackProps {
|
|
@@ -41,12 +41,19 @@ export interface FoirFeedbackProps {
|
|
|
41
41
|
/** Start with the panel open. Default false. */
|
|
42
42
|
defaultOpen?: boolean;
|
|
43
43
|
/**
|
|
44
|
-
* Called when the visitor has to sign in first
|
|
45
|
-
*
|
|
44
|
+
* Called when the visitor has to sign in first: the project takes no notes
|
|
45
|
+
* from visitors who are not signed in. Given a handler, the widget renders a
|
|
46
|
+
* button that calls it; given none, it says so and stops.
|
|
46
47
|
*/
|
|
47
48
|
onSignIn?: () => void;
|
|
48
|
-
/** Called with
|
|
49
|
+
/** Called with a signed-in user's note once it is written. */
|
|
49
50
|
onSubmitted?: (note: FoirUserNote) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Called with the receipt once a visitor who is not signed in has sent a
|
|
53
|
+
* note: its id and when it was written, which is all a visitor's note
|
|
54
|
+
* answers with.
|
|
55
|
+
*/
|
|
56
|
+
onVisitorSubmitted?: (receipt: FoirNoteReceipt) => void;
|
|
50
57
|
/**
|
|
51
58
|
* Called with anything that failed. The widget also logs it — this is for
|
|
52
59
|
* routing it into your own error reporting, not for making it visible.
|
|
@@ -59,4 +66,4 @@ export interface FoirFeedbackProps {
|
|
|
59
66
|
*/
|
|
60
67
|
className?: string;
|
|
61
68
|
}
|
|
62
|
-
export declare function FoirFeedback({ client, subjects, subject, label, triggerLabel, title, placeholder, defaultOpen, onSignIn, onSubmitted, onError, className, }: FoirFeedbackProps): import("react/jsx-runtime").JSX.Element;
|
|
69
|
+
export declare function FoirFeedback({ client, subjects, subject, label, triggerLabel, title, placeholder, defaultOpen, onSignIn, onSubmitted, onVisitorSubmitted, onError, className, }: FoirFeedbackProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* shape (see `data/selection.ts`) — so every response here has to be read rather
|
|
8
8
|
* than cast, and reading is the part worth testing on its own.
|
|
9
9
|
*/
|
|
10
|
-
import type { FoirUserNote, FoirNoteConfig, FoirSubject } from '../data';
|
|
10
|
+
import type { FoirUserNote, FoirNoteConfig, FoirNoteReceipt, FoirSubject } from '../data';
|
|
11
11
|
import type { FoirScreenCapture } from '../media/screen-capture';
|
|
12
12
|
/**
|
|
13
13
|
* The client the widget needs, and no more of it.
|
|
@@ -15,9 +15,9 @@ import type { FoirScreenCapture } from '../media/screen-capture';
|
|
|
15
15
|
* Structural rather than `FoirClient<S>`, for two reasons. `FoirClient` is
|
|
16
16
|
* generic in the caller's generated schema, so naming it here would force the
|
|
17
17
|
* component's props to be generic in a parameter nothing else reads. And what
|
|
18
|
-
* the widget actually depends on is
|
|
19
|
-
* a client whose key cannot reach `files` still satisfies this type, which
|
|
20
|
-
* the honest shape: a project with screenshots off never uploads anything.
|
|
18
|
+
* the widget actually depends on is a handful of root fields — narrowing to them
|
|
19
|
+
* means a client whose key cannot reach `files` still satisfies this type, which
|
|
20
|
+
* is the honest shape: a project with screenshots off never uploads anything.
|
|
21
21
|
*
|
|
22
22
|
* The signatures are the ones `foir types` emits for every namespace op —
|
|
23
23
|
* `Record<string, unknown>` in, `unknown` out — so a real generated client
|
|
@@ -27,6 +27,14 @@ export interface FoirFeedbackClient {
|
|
|
27
27
|
readonly ns: {
|
|
28
28
|
readonly notes: {
|
|
29
29
|
noteConfig(args?: Record<string, unknown>): Promise<unknown>;
|
|
30
|
+
/**
|
|
31
|
+
* The one note create, for a signed-in user and a visitor alike. Optional
|
|
32
|
+
* so a client typed before it still satisfies this; without it a
|
|
33
|
+
* signed-in user's note goes through `createMyNote`, and a visitor's
|
|
34
|
+
* cannot be sent.
|
|
35
|
+
*/
|
|
36
|
+
writeNote?(args?: Record<string, unknown>): Promise<unknown>;
|
|
37
|
+
/** The older name of `writeNote` for a signed-in user. */
|
|
30
38
|
createMyNote(args?: Record<string, unknown>): Promise<unknown>;
|
|
31
39
|
};
|
|
32
40
|
/**
|
|
@@ -77,6 +85,12 @@ export declare function classifyNotesFault(err: unknown): FoirFeedbackFault;
|
|
|
77
85
|
* `screenshotsEnabled` to false. Defaulting would be the quiet version of a
|
|
78
86
|
* project's setting being ignored: the offer would disappear from every surface
|
|
79
87
|
* at once and the only symptom would be a missing button.
|
|
88
|
+
*
|
|
89
|
+
* `signedIn` and `anonymousAccepted` are read as the older contract when they
|
|
90
|
+
* are absent, which is not a guess: a data API without them answers only a
|
|
91
|
+
* signed-in user, and refuses everyone else. A generated module that predates
|
|
92
|
+
* them selects neither from a data API that has both, and the client's drift
|
|
93
|
+
* check names that module as the thing to regenerate.
|
|
80
94
|
*/
|
|
81
95
|
export declare function readNoteConfig(client: FoirFeedbackClient): Promise<FoirNoteConfig>;
|
|
82
96
|
/** What one submission carries. */
|
|
@@ -101,3 +115,23 @@ export interface FoirFeedbackSubmission {
|
|
|
101
115
|
* they wrote ("as you can see in the screenshot") stops making sense.
|
|
102
116
|
*/
|
|
103
117
|
export declare function submitFeedbackNote(client: FoirFeedbackClient, submission: FoirFeedbackSubmission): Promise<FoirUserNote>;
|
|
118
|
+
/** What a visitor's submission carries. */
|
|
119
|
+
export interface FoirVisitorSubmission {
|
|
120
|
+
/** What the note is about, as for a signed-in user's. */
|
|
121
|
+
subject: FoirSubject;
|
|
122
|
+
body: string;
|
|
123
|
+
/**
|
|
124
|
+
* Where the project's team may reply, as the visitor typed it. Sent only
|
|
125
|
+
* when it is not blank, and never verified by anyone.
|
|
126
|
+
*/
|
|
127
|
+
contactEmail?: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Write a visitor's note, and hand back its receipt.
|
|
131
|
+
*
|
|
132
|
+
* It asks for the note's `id` and `createdAt` and nothing else, because that is
|
|
133
|
+
* all a visitor's note answers with: asked for more, the data API refuses the
|
|
134
|
+
* write before anything is written. And it takes no attachment, which a
|
|
135
|
+
* visitor's note cannot carry.
|
|
136
|
+
*/
|
|
137
|
+
export declare function submitVisitorNote(client: FoirFeedbackClient, submission: FoirVisitorSubmission): Promise<FoirNoteReceipt>;
|
package/dist/react.js
CHANGED
|
@@ -1004,6 +1004,9 @@ var AUTH_OPS = {
|
|
|
1004
1004
|
// names the organization it acts in (`orgId` or `orgUrl` below), and the
|
|
1005
1005
|
// platform checks the caller stands there every time.
|
|
1006
1006
|
myOrganizations: "myOrganizations",
|
|
1007
|
+
// Where they may go: the organizations they are a member of, are invited
|
|
1008
|
+
// to, may join, or asked to join. An organization switcher's choices.
|
|
1009
|
+
myOrganizationChoices: "myOrganizationChoices",
|
|
1007
1010
|
// Founding one. Present in a manifest only when the project opted in.
|
|
1008
1011
|
registerOrganization: "registerOrganization",
|
|
1009
1012
|
// External identity providers.
|
|
@@ -2246,7 +2249,14 @@ async function readNoteConfig(client) {
|
|
|
2246
2249
|
"[foir] noteConfig answered without a boolean `screenshotsEnabled`. The widget reads it to decide whether to offer screenshot capture, and will not guess."
|
|
2247
2250
|
);
|
|
2248
2251
|
}
|
|
2249
|
-
|
|
2252
|
+
const signedIn = result?.signedIn ?? true;
|
|
2253
|
+
const anonymousAccepted = result?.anonymousAccepted ?? false;
|
|
2254
|
+
if (typeof signedIn !== "boolean" || typeof anonymousAccepted !== "boolean") {
|
|
2255
|
+
throw new Error(
|
|
2256
|
+
"[foir] noteConfig answered `signedIn` or `anonymousAccepted` with something other than a boolean. The widget reads them to decide whether this visitor writes as themselves, and will not guess."
|
|
2257
|
+
);
|
|
2258
|
+
}
|
|
2259
|
+
return { screenshotsEnabled, anonymousAccepted, signedIn };
|
|
2250
2260
|
}
|
|
2251
2261
|
async function submitFeedbackNote(client, submission) {
|
|
2252
2262
|
const attachmentFileIds = [];
|
|
@@ -2255,14 +2265,42 @@ async function submitFeedbackNote(client, submission) {
|
|
|
2255
2265
|
await uploadFeedbackAttachment(client, submission.screenshot, submission.fetch)
|
|
2256
2266
|
);
|
|
2257
2267
|
}
|
|
2258
|
-
const args =
|
|
2268
|
+
const args = noteArgs(submission.subject, submission.body);
|
|
2269
|
+
if (attachmentFileIds.length > 0) args.attachmentFileIds = attachmentFileIds;
|
|
2270
|
+
const { notes } = client.ns;
|
|
2271
|
+
return readCreatedNote(
|
|
2272
|
+
await (notes.writeNote ? notes.writeNote(args) : notes.createMyNote(args))
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
async function submitVisitorNote(client, submission) {
|
|
2276
|
+
const { notes } = client.ns;
|
|
2277
|
+
if (!notes.writeNote) {
|
|
2278
|
+
throw new Error(
|
|
2279
|
+
"[foir] this client has no `notes.writeNote`, which is the only create that takes a note from a visitor who is not signed in. Re-run `foir types`."
|
|
2280
|
+
);
|
|
2281
|
+
}
|
|
2282
|
+
const args = noteArgs(submission.subject, submission.body);
|
|
2283
|
+
const contactEmail = submission.contactEmail?.trim();
|
|
2284
|
+
if (contactEmail) args.contactEmail = contactEmail;
|
|
2285
|
+
args.pick = ["id", "createdAt"];
|
|
2286
|
+
const result = await notes.writeNote(args);
|
|
2287
|
+
if (!result || typeof result.id !== "string" || result.id === "") {
|
|
2288
|
+
throw new Error(
|
|
2289
|
+
"[foir] writeNote answered a visitor with no receipt. Nothing was saved \u2014 if it had been, the surface would have returned its id."
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
return {
|
|
2293
|
+
id: result.id,
|
|
2294
|
+
createdAt: typeof result.createdAt === "string" ? result.createdAt : ""
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
function noteArgs(subject, body) {
|
|
2298
|
+
return {
|
|
2259
2299
|
subjectType: "record",
|
|
2260
|
-
subjectId:
|
|
2261
|
-
subjectModelKey:
|
|
2262
|
-
body
|
|
2300
|
+
subjectId: subject.recordId,
|
|
2301
|
+
subjectModelKey: subject.modelKey,
|
|
2302
|
+
body
|
|
2263
2303
|
};
|
|
2264
|
-
if (attachmentFileIds.length > 0) args.attachmentFileIds = attachmentFileIds;
|
|
2265
|
-
return readCreatedNote(await client.ns.notes.createMyNote(args));
|
|
2266
2304
|
}
|
|
2267
2305
|
async function uploadFeedbackAttachment(client, screenshot, fetchImpl = globalThis.fetch) {
|
|
2268
2306
|
const files = client.ns.files;
|
|
@@ -2314,7 +2352,7 @@ function readCreatedNote(result) {
|
|
|
2314
2352
|
const note = result;
|
|
2315
2353
|
if (!note || typeof note.id !== "string" || note.id === "") {
|
|
2316
2354
|
throw new Error(
|
|
2317
|
-
"[foir]
|
|
2355
|
+
"[foir] writeNote answered with no note. Nothing was saved \u2014 if it had been, the surface would have returned it."
|
|
2318
2356
|
);
|
|
2319
2357
|
}
|
|
2320
2358
|
return {
|
|
@@ -2364,6 +2402,8 @@ var COPY = {
|
|
|
2364
2402
|
loading: "One moment\u2026",
|
|
2365
2403
|
sendFailed: "Your feedback could not be sent. Please try again.",
|
|
2366
2404
|
sent: "Thanks. Your feedback has been sent.",
|
|
2405
|
+
reference: "Reference",
|
|
2406
|
+
contactEmail: "Your email, if you would like a reply (optional)",
|
|
2367
2407
|
send: "Send feedback",
|
|
2368
2408
|
sending: "Sending\u2026",
|
|
2369
2409
|
capture: "Add a screenshot",
|
|
@@ -2400,6 +2440,13 @@ var textareaStyle = {
|
|
|
2400
2440
|
minHeight: 96,
|
|
2401
2441
|
resize: "vertical"
|
|
2402
2442
|
};
|
|
2443
|
+
var inputStyle2 = {
|
|
2444
|
+
padding: "8px 10px",
|
|
2445
|
+
borderRadius: 6,
|
|
2446
|
+
border: "1px solid currentColor",
|
|
2447
|
+
background: "transparent",
|
|
2448
|
+
font: "inherit"
|
|
2449
|
+
};
|
|
2403
2450
|
var headingStyle = { margin: 0, fontSize: 15, fontWeight: 600 };
|
|
2404
2451
|
var subjectStyle = { margin: 0, fontSize: 13, opacity: 0.75 };
|
|
2405
2452
|
var noteStyle = { margin: 0, fontSize: 13 };
|
|
@@ -2424,12 +2471,15 @@ function FoirFeedback({
|
|
|
2424
2471
|
defaultOpen = false,
|
|
2425
2472
|
onSignIn,
|
|
2426
2473
|
onSubmitted,
|
|
2474
|
+
onVisitorSubmitted,
|
|
2427
2475
|
onError,
|
|
2428
2476
|
className
|
|
2429
2477
|
}) {
|
|
2430
2478
|
const [open, setOpen] = useState4(defaultOpen);
|
|
2431
2479
|
const [probe, setProbe] = useState4({ status: "loading" });
|
|
2432
2480
|
const [body, setBody] = useState4("");
|
|
2481
|
+
const [contactEmail, setContactEmail] = useState4("");
|
|
2482
|
+
const [receipt, setReceipt] = useState4(null);
|
|
2433
2483
|
const [screenshot, setScreenshot] = useState4(null);
|
|
2434
2484
|
const [capturing, setCapturing] = useState4(false);
|
|
2435
2485
|
const [busy, setBusy] = useState4(false);
|
|
@@ -2448,7 +2498,11 @@ function FoirFeedback({
|
|
|
2448
2498
|
try {
|
|
2449
2499
|
const config = await readNoteConfig(client);
|
|
2450
2500
|
if (!cancelled) {
|
|
2451
|
-
setProbe({
|
|
2501
|
+
setProbe({
|
|
2502
|
+
status: "ready",
|
|
2503
|
+
screenshotsEnabled: config.screenshotsEnabled,
|
|
2504
|
+
signedIn: config.signedIn
|
|
2505
|
+
});
|
|
2452
2506
|
}
|
|
2453
2507
|
} catch (err) {
|
|
2454
2508
|
if (cancelled) return;
|
|
@@ -2479,19 +2533,32 @@ function FoirFeedback({
|
|
|
2479
2533
|
const onSubmit = async (event) => {
|
|
2480
2534
|
event.preventDefault();
|
|
2481
2535
|
const prose = body.trim();
|
|
2482
|
-
if (!resolved || !prose || busy) return;
|
|
2536
|
+
if (!resolved || !prose || busy || probe.status !== "ready") return;
|
|
2483
2537
|
setBusy(true);
|
|
2484
2538
|
setFailure(null);
|
|
2485
2539
|
try {
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2540
|
+
if (probe.signedIn) {
|
|
2541
|
+
const note = await submitFeedbackNote(client, {
|
|
2542
|
+
subject: resolved,
|
|
2543
|
+
body: prose,
|
|
2544
|
+
screenshot
|
|
2545
|
+
});
|
|
2546
|
+
setBody("");
|
|
2547
|
+
setScreenshot(null);
|
|
2548
|
+
setSent(true);
|
|
2549
|
+
onSubmitted?.(note);
|
|
2550
|
+
} else {
|
|
2551
|
+
const written = await submitVisitorNote(client, {
|
|
2552
|
+
subject: resolved,
|
|
2553
|
+
body: prose,
|
|
2554
|
+
contactEmail
|
|
2555
|
+
});
|
|
2556
|
+
setBody("");
|
|
2557
|
+
setContactEmail("");
|
|
2558
|
+
setReceipt(written);
|
|
2559
|
+
setSent(true);
|
|
2560
|
+
onVisitorSubmitted?.(written);
|
|
2561
|
+
}
|
|
2495
2562
|
} catch (err) {
|
|
2496
2563
|
const fault = classifyNotesFault(err);
|
|
2497
2564
|
reportFault(fault, err, "sending feedback failed");
|
|
@@ -2507,6 +2574,7 @@ function FoirFeedback({
|
|
|
2507
2574
|
return;
|
|
2508
2575
|
}
|
|
2509
2576
|
setSent(false);
|
|
2577
|
+
setReceipt(null);
|
|
2510
2578
|
setFailure(null);
|
|
2511
2579
|
setOpen(true);
|
|
2512
2580
|
};
|
|
@@ -2567,11 +2635,28 @@ function FoirFeedback({
|
|
|
2567
2635
|
disabled: busy,
|
|
2568
2636
|
onChange: (event) => {
|
|
2569
2637
|
setBody(event.target.value);
|
|
2570
|
-
if (sent)
|
|
2638
|
+
if (sent) {
|
|
2639
|
+
setSent(false);
|
|
2640
|
+
setReceipt(null);
|
|
2641
|
+
}
|
|
2571
2642
|
},
|
|
2572
2643
|
style: styled ? textareaStyle : void 0
|
|
2573
2644
|
}
|
|
2574
2645
|
),
|
|
2646
|
+
!probe.signedIn && /* @__PURE__ */ jsx5(
|
|
2647
|
+
"input",
|
|
2648
|
+
{
|
|
2649
|
+
type: "email",
|
|
2650
|
+
"data-foir-feedback-contact": true,
|
|
2651
|
+
"aria-label": COPY.contactEmail,
|
|
2652
|
+
placeholder: COPY.contactEmail,
|
|
2653
|
+
autoComplete: "email",
|
|
2654
|
+
value: contactEmail,
|
|
2655
|
+
disabled: busy,
|
|
2656
|
+
onChange: (event) => setContactEmail(event.target.value),
|
|
2657
|
+
style: styled ? inputStyle2 : void 0
|
|
2658
|
+
}
|
|
2659
|
+
),
|
|
2575
2660
|
probe.screenshotsEnabled && isFoirScreenCaptureSupported() && /* @__PURE__ */ jsx5(Fragment2, { children: screenshot ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
2576
2661
|
/* @__PURE__ */ jsx5(
|
|
2577
2662
|
"img",
|
|
@@ -2614,6 +2699,11 @@ function FoirFeedback({
|
|
|
2614
2699
|
children: COPY.sent
|
|
2615
2700
|
}
|
|
2616
2701
|
),
|
|
2702
|
+
sent && !failure && receipt && /* @__PURE__ */ jsxs4("p", { "data-foir-feedback-receipt": true, style: styled ? subjectStyle : void 0, children: [
|
|
2703
|
+
COPY.reference,
|
|
2704
|
+
": ",
|
|
2705
|
+
receipt.id
|
|
2706
|
+
] }),
|
|
2617
2707
|
/* @__PURE__ */ jsx5(
|
|
2618
2708
|
"button",
|
|
2619
2709
|
{
|
package/dist/server/index.d.ts
CHANGED
|
@@ -12,5 +12,5 @@ export type { CallbackClient, CallbackResult } from './callback-client';
|
|
|
12
12
|
export { createEventsClient, FoirEventCursorExpiredError, isFoirEventCursorExpired, FOIR_EVENTS_PATH, FOIR_EVENTS_SCOPE, FOIR_EVENTS_RETENTION_DAYS, } from './events-client';
|
|
13
13
|
export type { FoirEvent, FoirEventActor, FoirEventPage, FoirEventRetention, FoirEventsClient, FoirEventsClientConfig, FoirEventsListParams, FoirEventsStreamParams, FoirEventsSubscribeParams, } from './events-client';
|
|
14
14
|
export type { FoirSubscription, FoirSubscriptionHandlers } from '../data/ws-transport';
|
|
15
|
-
export { FoirRequestError, FoirAuthDowngradedError, FoirSessionExpiredError, isFoirForbidden, isFoirNotFound, isFoirRateLimited, isFoirUnauthenticated, } from '../transport/foir-http';
|
|
15
|
+
export { FoirRequestError, FoirAuthDowngradedError, FoirSessionExpiredError, isFoirForbidden, isFoirNotFound, isFoirRateLimited, isFoirUnauthenticated, stepUpRequired, } from '../transport/foir-http';
|
|
16
16
|
export type { FoirErrorCode, FoirErrorExtensions, FoirGraphQLErrorItem, FoirAuthDowngrade, } from '../transport/foir-http';
|
package/dist/server.js
CHANGED
|
@@ -699,6 +699,15 @@ function continueRun(continuation) {
|
|
|
699
699
|
}
|
|
700
700
|
|
|
701
701
|
// src/transport/foir-http.ts
|
|
702
|
+
function stepUpRequired(error) {
|
|
703
|
+
if (!(error instanceof FoirRequestError)) return null;
|
|
704
|
+
for (const item of error.graphQLErrors ?? []) {
|
|
705
|
+
if (item.extensions?.code !== "STEP_UP_REQUIRED") continue;
|
|
706
|
+
const asked = item.extensions.stepUp;
|
|
707
|
+
if (asked === "second_factor" || asked === "sign_in_method") return asked;
|
|
708
|
+
}
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
702
711
|
var FoirRequestError = class extends Error {
|
|
703
712
|
/** HTTP status code, when the request failed at the HTTP layer. */
|
|
704
713
|
status;
|
|
@@ -1752,6 +1761,7 @@ export {
|
|
|
1752
1761
|
recordsReadModels,
|
|
1753
1762
|
recordsWriteModels,
|
|
1754
1763
|
requireScopedToken,
|
|
1764
|
+
stepUpRequired,
|
|
1755
1765
|
verifyScopedToken,
|
|
1756
1766
|
verifyWebhookSignature
|
|
1757
1767
|
};
|
|
@@ -55,7 +55,14 @@ export declare function resolveFoirApiKey(env: FoirEnv): string | undefined;
|
|
|
55
55
|
* because an absent record is an answer rather than a failure. `NOT_FOUND` only
|
|
56
56
|
* appears when a mutation names something that does not exist.
|
|
57
57
|
*/
|
|
58
|
-
export type FoirErrorCode = 'INVALID_ARGUMENT' | 'NOT_FOUND' | 'ALREADY_EXISTS' | 'FAILED_PRECONDITION' | 'PERMISSION_DENIED' | 'UNAUTHENTICATED' | 'RATE_LIMITED' | 'TIMEOUT' | 'UNAVAILABLE' | 'CANCELED' | 'INTERNAL';
|
|
58
|
+
export type FoirErrorCode = 'INVALID_ARGUMENT' | 'NOT_FOUND' | 'ALREADY_EXISTS' | 'FAILED_PRECONDITION' | 'PERMISSION_DENIED' | 'UNAUTHENTICATED' | 'RATE_LIMITED' | 'TIMEOUT' | 'UNAVAILABLE' | 'CANCELED' | 'INTERNAL' | 'STEP_UP_REQUIRED';
|
|
59
|
+
/**
|
|
60
|
+
* What a `STEP_UP_REQUIRED` refusal asks of the signed-in user, in
|
|
61
|
+
* `extensions.stepUp`: `second_factor`, prove a second factor (the step-up
|
|
62
|
+
* flow) and retry; `sign_in_method`, sign in again with a method the
|
|
63
|
+
* organization the request stands in accepts.
|
|
64
|
+
*/
|
|
65
|
+
export type FoirStepUp = 'second_factor' | 'sign_in_method';
|
|
59
66
|
export interface FoirErrorExtensions {
|
|
60
67
|
/** Stable machine-readable code. Branch on this, never on `message`. */
|
|
61
68
|
code?: FoirErrorCode | string;
|
|
@@ -63,6 +70,8 @@ export interface FoirErrorExtensions {
|
|
|
63
70
|
requestId?: string;
|
|
64
71
|
/** On `RATE_LIMITED`: whole seconds to wait. Never less than 1. */
|
|
65
72
|
retryAfter?: number;
|
|
73
|
+
/** On `STEP_UP_REQUIRED`: what the signed-in user must do before retrying. */
|
|
74
|
+
stepUp?: FoirStepUp;
|
|
66
75
|
[key: string]: unknown;
|
|
67
76
|
}
|
|
68
77
|
export interface FoirGraphQLErrorItem {
|
|
@@ -115,6 +124,13 @@ export interface FoirRateLimitSnapshot {
|
|
|
115
124
|
/** Unix seconds at which the window resets. */
|
|
116
125
|
resetAt: number;
|
|
117
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* What an error asks of the signed-in user before the request can succeed, or
|
|
129
|
+
* null: an organization's sign-in rules turned the session away, because it
|
|
130
|
+
* requires a second factor the session has not proven (`second_factor`) or does
|
|
131
|
+
* not accept how the user signed in (`sign_in_method`).
|
|
132
|
+
*/
|
|
133
|
+
export declare function stepUpRequired(error: unknown): FoirStepUp | null;
|
|
118
134
|
export declare class FoirRequestError extends Error {
|
|
119
135
|
/** HTTP status code, when the request failed at the HTTP layer. */
|
|
120
136
|
readonly status?: number;
|