@foir/sdk 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +135 -1
- package/README.md +9 -9
- package/dist/app/client.d.ts +90 -84
- package/dist/app/contract/proto/apps/v1/apps_pb.d.ts +14 -13
- package/dist/app/dispatch.d.ts +5 -5
- package/dist/app/errors.d.ts +11 -11
- package/dist/app/index.d.ts +4 -4
- package/dist/app/naming.d.ts +1 -1
- package/dist/app/ticket.d.ts +2 -2
- package/dist/app/vocabulary.generated.d.ts +2 -2
- package/dist/app-contract.js +3 -3
- package/dist/app.js +92 -90
- package/dist/auth/access.d.ts +19 -19
- package/dist/auth/auth.d.ts +216 -114
- package/dist/auth/hosted.d.ts +14 -14
- package/dist/auth/index.d.ts +7 -7
- package/dist/auth/operations.d.ts +128 -32
- package/dist/auth/ops.d.ts +39 -21
- package/dist/auth/session-client.d.ts +3 -3
- package/dist/auth-session.js +3 -3
- package/dist/auth.js +291 -146
- package/dist/client/platform-client.d.ts +1 -1
- package/dist/client.js +32 -5
- package/dist/codegen/index.js +1 -1
- package/dist/data/builtins.d.ts +41 -82
- package/dist/data/client.d.ts +2 -2
- package/dist/data/decimal.d.ts +1 -1
- package/dist/data/document.d.ts +14 -0
- package/dist/data/index.d.ts +2 -2
- package/dist/data/manifest.d.ts +1 -1
- package/dist/data/schema.d.ts +1 -1
- package/dist/data/transport.d.ts +33 -26
- package/dist/data/ws-transport.d.ts +1 -1
- package/dist/data.js +97 -19
- package/dist/graphql/content-client.d.ts +13 -13
- package/dist/index.js +24 -0
- package/dist/live/drift.d.ts +1 -1
- package/dist/media/screen-capture.d.ts +1 -1
- package/dist/next/index.d.ts +2 -2
- package/dist/next/session.d.ts +10 -10
- package/dist/next.js +121 -61
- package/dist/placement/index.d.ts +15 -32
- package/dist/placement.js +18 -20
- package/dist/push.js +113 -46
- package/dist/react/FoirFeedback.d.ts +2 -2
- package/dist/react/FoirProvider.d.ts +23 -23
- package/dist/react/SignIn.d.ts +4 -4
- package/dist/react/UserButton.d.ts +1 -1
- package/dist/react/feedback-notes.d.ts +4 -4
- package/dist/react/index.d.ts +3 -3
- package/dist/react.js +168 -105
- package/dist/realtime/index.d.ts +1 -1
- package/dist/server/callback-client.d.ts +4 -4
- package/dist/server/events-client.d.ts +18 -18
- package/dist/server/operation-types.d.ts +8 -8
- package/dist/server/require-scoped-token.d.ts +3 -3
- package/dist/server/server-client.d.ts +1 -1
- package/dist/server/types.d.ts +1 -1
- package/dist/server/verify-scoped-token.d.ts +4 -28
- package/dist/server.js +67 -112
- package/dist/transport/foir-http.d.ts +57 -18
- package/dist/transport/key-kind.d.ts +40 -0
- package/dist/types/index.d.ts +2 -2
- package/package.json +3 -3
- package/dist/server/older-claims.generated.d.ts +0 -8
package/dist/push.js
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
// src/graphql/foir-request.ts
|
|
2
2
|
import { print as print2 } from "graphql";
|
|
3
3
|
|
|
4
|
+
// src/transport/key-kind.ts
|
|
5
|
+
var KEY_PREFIXES = [
|
|
6
|
+
["foir_pub_", "publishable"],
|
|
7
|
+
["foir_srv_", "server"],
|
|
8
|
+
["foir_adm_", "admin"],
|
|
9
|
+
["pk_", "publishable"],
|
|
10
|
+
["sk_", "older"],
|
|
11
|
+
["tc_", "older"]
|
|
12
|
+
];
|
|
13
|
+
var FOIR_CREDENTIALS_DOC = "https://docs.foir.dev/credentials";
|
|
14
|
+
function foirKeyKind(apiKey) {
|
|
15
|
+
if (typeof apiKey !== "string") return void 0;
|
|
16
|
+
for (const [prefix, kind] of KEY_PREFIXES) {
|
|
17
|
+
if (apiKey.startsWith(prefix)) return kind;
|
|
18
|
+
}
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
function adminKeyRefusal(client) {
|
|
22
|
+
return `[foir] ${client} was given an admin key. An admin key is a machine's own: nobody signs in through it, and an app never holds one. Build this client from your publishable key, or from your server key on the server that renders your pages. A machine that acts for itself uses createServerClient from @foir/sdk/server. See ${FOIR_CREDENTIALS_DOC}`;
|
|
23
|
+
}
|
|
24
|
+
function isPublishableFoirKey(apiKey) {
|
|
25
|
+
return foirKeyKind(apiKey) === "publishable";
|
|
26
|
+
}
|
|
27
|
+
|
|
4
28
|
// src/graphql/cdn-route.ts
|
|
5
29
|
import { print } from "graphql";
|
|
6
30
|
|
|
@@ -125,6 +149,28 @@ function refuseCredentialHeaders(extra) {
|
|
|
125
149
|
}
|
|
126
150
|
}
|
|
127
151
|
var FOIR_VISITOR_IP_HEADER = "X-Foir-Visitor-IP";
|
|
152
|
+
function sentCoordinate(value) {
|
|
153
|
+
return Math.round(value * 100) / 100;
|
|
154
|
+
}
|
|
155
|
+
async function resolveFoirVisitor(source) {
|
|
156
|
+
const value = typeof source === "function" ? await source() : source;
|
|
157
|
+
if (value === null || value === void 0) return { place: {} };
|
|
158
|
+
if (typeof value === "string") {
|
|
159
|
+
const address2 = value.trim();
|
|
160
|
+
return { address: address2 || void 0, place: {} };
|
|
161
|
+
}
|
|
162
|
+
const address = value.address?.trim();
|
|
163
|
+
const country = value.country?.trim();
|
|
164
|
+
const place = {};
|
|
165
|
+
if (country) place.country = country;
|
|
166
|
+
if (Number.isFinite(value.latitude) && Number.isFinite(value.longitude)) {
|
|
167
|
+
place.location = {
|
|
168
|
+
latitude: sentCoordinate(value.latitude),
|
|
169
|
+
longitude: sentCoordinate(value.longitude)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return { address: address || void 0, place };
|
|
173
|
+
}
|
|
128
174
|
function buildFoirHeaders(input) {
|
|
129
175
|
const headers = {
|
|
130
176
|
"Content-Type": "application/json"
|
|
@@ -171,8 +217,8 @@ var FoirAuthDowngradedError = class extends FoirRequestError {
|
|
|
171
217
|
downgrade;
|
|
172
218
|
constructor(downgrade) {
|
|
173
219
|
super(
|
|
174
|
-
`[foir] your
|
|
175
|
-
{ graphQLErrors: [{ message: "
|
|
220
|
+
`[foir] your user token was ${downgrade.userToken} (${downgrade.reason}), so the request was served as ${downgrade.servedAs}`,
|
|
221
|
+
{ graphQLErrors: [{ message: "user token rejected", extensions: { code: "AUTH_DOWNGRADED" } }] }
|
|
176
222
|
);
|
|
177
223
|
this.name = "FoirAuthDowngradedError";
|
|
178
224
|
this.downgrade = downgrade;
|
|
@@ -180,7 +226,7 @@ var FoirAuthDowngradedError = class extends FoirRequestError {
|
|
|
180
226
|
};
|
|
181
227
|
var FoirSessionExpiredError = class extends FoirRequestError {
|
|
182
228
|
constructor(options) {
|
|
183
|
-
super("[foir] the
|
|
229
|
+
super("[foir] the user session has expired and could not be refreshed: sign in again", {
|
|
184
230
|
cause: options?.cause,
|
|
185
231
|
graphQLErrors: [{ message: "session expired", extensions: { code: "SESSION_EXPIRED" } }]
|
|
186
232
|
});
|
|
@@ -197,7 +243,7 @@ function reportAuthDowngrade(envelope) {
|
|
|
197
243
|
if (warnedDowngrade === downgrade.reason) return;
|
|
198
244
|
warnedDowngrade = downgrade.reason;
|
|
199
245
|
console.warn(
|
|
200
|
-
`[foir] your
|
|
246
|
+
`[foir] your user token was ${downgrade.userToken} (${downgrade.reason}) \u2014 this request was served as ${downgrade.servedAs}, so auth-targeted content resolved to its anonymous variant. ` + (downgrade.reason === "expired" ? "Re-exchange the token and retry." : "Check which credential you are sending.")
|
|
201
247
|
);
|
|
202
248
|
}
|
|
203
249
|
function refusePrunedDocument(json, options) {
|
|
@@ -277,7 +323,7 @@ function isCdnEligible(env, documentText, bearerToken, orgId) {
|
|
|
277
323
|
if (orgId) return false;
|
|
278
324
|
if (isFoirPreview(env)) return false;
|
|
279
325
|
const apiKey = resolveFoirApiKey(env);
|
|
280
|
-
if (!apiKey
|
|
326
|
+
if (!isPublishableFoirKey(apiKey)) return false;
|
|
281
327
|
return !/\bmutation\b/.test(documentText);
|
|
282
328
|
}
|
|
283
329
|
|
|
@@ -290,6 +336,9 @@ async function foirRequest(env, document, variables, options) {
|
|
|
290
336
|
"[foir] PLATFORM_API_URL or PLATFORM_API_KEY is not set \u2014 add them to .env"
|
|
291
337
|
);
|
|
292
338
|
}
|
|
339
|
+
if (foirKeyKind(apiKey) === "admin") {
|
|
340
|
+
throw new FoirRequestError(adminKeyRefusal("foirRequest"));
|
|
341
|
+
}
|
|
293
342
|
const headers = buildFoirHeaders({
|
|
294
343
|
apiKey,
|
|
295
344
|
draftChannel: isFoirPreview(env),
|
|
@@ -852,10 +901,7 @@ function foirDataEndpoint(apiUrl, path) {
|
|
|
852
901
|
return foirApiEndpoint(apiUrl, path);
|
|
853
902
|
}
|
|
854
903
|
async function resolveVisitorAddress(config) {
|
|
855
|
-
|
|
856
|
-
const value = typeof source === "function" ? await source() : source;
|
|
857
|
-
const address = value?.trim();
|
|
858
|
-
return address ? address : void 0;
|
|
904
|
+
return (await resolveFoirVisitor(config.visitor)).address;
|
|
859
905
|
}
|
|
860
906
|
async function resolveBearerToken(config) {
|
|
861
907
|
const source = config.bearerToken;
|
|
@@ -896,7 +942,7 @@ async function resolveFoirAuth(config) {
|
|
|
896
942
|
};
|
|
897
943
|
}
|
|
898
944
|
function foirRoute(input) {
|
|
899
|
-
if (!input.apiKey
|
|
945
|
+
if (!isPublishableFoirKey(input.apiKey)) return { lane: "origin", reason: "key" };
|
|
900
946
|
if (input.bearerToken) return { lane: "origin", reason: "bearer" };
|
|
901
947
|
if (input.orgId || input.orgUrl) return { lane: "origin", reason: "org" };
|
|
902
948
|
if (input.mutation) return { lane: "origin", reason: "operation" };
|
|
@@ -1030,42 +1076,63 @@ var AUTH_OPS = {
|
|
|
1030
1076
|
authConfig: "authConfig",
|
|
1031
1077
|
authProviders: "authProviders",
|
|
1032
1078
|
defaultAuthProvider: "defaultAuthProvider",
|
|
1033
|
-
|
|
1079
|
+
signupContext: "signupContext",
|
|
1034
1080
|
// Post-credential self reads.
|
|
1035
1081
|
currentUser: "currentUser",
|
|
1036
1082
|
myProviderToken: "myProviderToken",
|
|
1037
|
-
// What the calling credential may do, and for a signed-in
|
|
1083
|
+
// What the calling credential may do, and for a signed-in user the roles
|
|
1038
1084
|
// they hold. `./access` reads it.
|
|
1039
1085
|
whoami: "whoami",
|
|
1040
1086
|
// Methods.
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1087
|
+
login: "login",
|
|
1088
|
+
register: "register",
|
|
1089
|
+
requestOTP: "requestOTP",
|
|
1090
|
+
loginWithOTP: "loginWithOTP",
|
|
1045
1091
|
// Session lifecycle.
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
// Password management and account lifecycle
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1092
|
+
refreshToken: "refreshToken",
|
|
1093
|
+
logout: "logout",
|
|
1094
|
+
joinWaitlist: "joinWaitlist",
|
|
1095
|
+
// Password management and account lifecycle.
|
|
1096
|
+
requestPasswordReset: "requestPasswordReset",
|
|
1097
|
+
resetPassword: "resetPassword",
|
|
1098
|
+
updatePassword: "updatePassword",
|
|
1099
|
+
verifyEmail: "verifyEmail",
|
|
1054
1100
|
deleteMyAccount: "deleteMyAccount",
|
|
1055
|
-
// The
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
//
|
|
1060
|
-
//
|
|
1101
|
+
// The user's own profile, and the apps they have agreed may act for them.
|
|
1102
|
+
updateMyProfile: "updateMyProfile",
|
|
1103
|
+
myAuthorizedApps: "myAuthorizedApps",
|
|
1104
|
+
revokeAuthorizedApp: "revokeAuthorizedApp",
|
|
1105
|
+
// Where they are signed in, their personal access tokens, and the
|
|
1106
|
+
// providers they sign in with.
|
|
1107
|
+
mySessions: "mySessions",
|
|
1108
|
+
endMySession: "endMySession",
|
|
1109
|
+
signOutEverywhere: "signOutEverywhere",
|
|
1110
|
+
createPersonalAccessToken: "createPersonalAccessToken",
|
|
1111
|
+
mySignInMethods: "mySignInMethods",
|
|
1112
|
+
connectSignInMethod: "connectSignInMethod",
|
|
1113
|
+
disconnectSignInMethod: "disconnectSignInMethod",
|
|
1114
|
+
// Their second factors. A change needs one proven first, by a step-up.
|
|
1115
|
+
myFactors: "myFactors",
|
|
1116
|
+
beginStepUp: "beginStepUp",
|
|
1117
|
+
completeStepUp: "completeStepUp",
|
|
1118
|
+
beginFactorEnrollment: "beginFactorEnrollment",
|
|
1119
|
+
completeFactorEnrollment: "completeFactorEnrollment",
|
|
1120
|
+
removeFactor: "removeFactor",
|
|
1121
|
+
makeRecoveryCodes: "makeRecoveryCodes",
|
|
1122
|
+
// The organizations the caller may stand in. Nothing selects one: a request
|
|
1123
|
+
// names the organization it acts in (`orgId` or `orgUrl` below), and the
|
|
1124
|
+
// platform checks the caller stands there every time.
|
|
1061
1125
|
myOrganizations: "myOrganizations",
|
|
1062
|
-
selectMyOrganization: "selectMyOrganization",
|
|
1063
1126
|
// Founding one. Present in a manifest only when the project opted in.
|
|
1064
|
-
|
|
1127
|
+
registerOrganization: "registerOrganization",
|
|
1065
1128
|
// External identity providers.
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1129
|
+
loginWithProvider: "loginWithProvider",
|
|
1130
|
+
providerCallback: "providerCallback",
|
|
1131
|
+
providerVerifyOTP: "providerVerifyOTP",
|
|
1132
|
+
// A provider sign-in that names an address somebody already holds, waiting
|
|
1133
|
+
// for the code sent to it.
|
|
1134
|
+
confirmProviderLink: "confirmProviderLink",
|
|
1135
|
+
resendProviderLinkCode: "resendProviderLinkCode"
|
|
1069
1136
|
};
|
|
1070
1137
|
function authClientConfig(env, options) {
|
|
1071
1138
|
const apiKey = resolveFoirApiKey(env);
|
|
@@ -1128,32 +1195,32 @@ function stripUndefined(args) {
|
|
|
1128
1195
|
// src/auth/auth.ts
|
|
1129
1196
|
var TOKEN_KEY = "foir:token";
|
|
1130
1197
|
var REFRESH_KEY = "foir:refreshToken";
|
|
1131
|
-
var
|
|
1198
|
+
var USER_ID_KEY = "foir:userId";
|
|
1132
1199
|
var EMAIL_KEY = "foir:email";
|
|
1133
|
-
async function
|
|
1134
|
-
return authOp(env, AUTH_OPS.
|
|
1200
|
+
async function refreshTokens(env, refreshToken) {
|
|
1201
|
+
return authOp(env, AUTH_OPS.refreshToken, { refreshToken });
|
|
1135
1202
|
}
|
|
1136
1203
|
async function saveSession(adapter, result) {
|
|
1137
1204
|
await adapter.set(TOKEN_KEY, result.token);
|
|
1138
1205
|
await adapter.set(REFRESH_KEY, result.refreshToken);
|
|
1139
|
-
if (!result.
|
|
1140
|
-
return await
|
|
1206
|
+
if (!result.userId) {
|
|
1207
|
+
return await getSessionUser(adapter) ?? { id: "", email: "" };
|
|
1141
1208
|
}
|
|
1142
|
-
await adapter.set(
|
|
1209
|
+
await adapter.set(USER_ID_KEY, result.userId);
|
|
1143
1210
|
await adapter.set(EMAIL_KEY, result.email);
|
|
1144
|
-
return { id: result.
|
|
1211
|
+
return { id: result.userId, email: result.email };
|
|
1145
1212
|
}
|
|
1146
1213
|
async function clearSession(adapter) {
|
|
1147
1214
|
await adapter.unset(TOKEN_KEY);
|
|
1148
1215
|
await adapter.unset(REFRESH_KEY);
|
|
1149
|
-
await adapter.unset(
|
|
1216
|
+
await adapter.unset(USER_ID_KEY);
|
|
1150
1217
|
await adapter.unset(EMAIL_KEY);
|
|
1151
1218
|
}
|
|
1152
1219
|
async function getSessionToken(adapter) {
|
|
1153
1220
|
return await adapter.get(TOKEN_KEY) ?? null;
|
|
1154
1221
|
}
|
|
1155
|
-
async function
|
|
1156
|
-
const id = await adapter.get(
|
|
1222
|
+
async function getSessionUser(adapter) {
|
|
1223
|
+
const id = await adapter.get(USER_ID_KEY) ?? null;
|
|
1157
1224
|
const email = await adapter.get(EMAIL_KEY) ?? null;
|
|
1158
1225
|
if (!id) return null;
|
|
1159
1226
|
return { id, email: email ?? "" };
|
|
@@ -1162,7 +1229,7 @@ async function refreshSession(adapter, env) {
|
|
|
1162
1229
|
const refreshToken = await adapter.get(REFRESH_KEY) ?? null;
|
|
1163
1230
|
if (!refreshToken) return null;
|
|
1164
1231
|
try {
|
|
1165
|
-
const result = await
|
|
1232
|
+
const result = await refreshTokens(env, refreshToken);
|
|
1166
1233
|
await saveSession(adapter, result);
|
|
1167
1234
|
return { token: result.token, refreshToken: result.refreshToken };
|
|
1168
1235
|
} catch {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { 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 {
|
|
@@ -46,7 +46,7 @@ export interface FoirFeedbackProps {
|
|
|
46
46
|
*/
|
|
47
47
|
onSignIn?: () => void;
|
|
48
48
|
/** Called with the note once it is written. */
|
|
49
|
-
onSubmitted?: (note:
|
|
49
|
+
onSubmitted?: (note: FoirUserNote) => void;
|
|
50
50
|
/**
|
|
51
51
|
* Called with anything that failed. The widget also logs it — this is for
|
|
52
52
|
* routing it into your own error reporting, not for making it visible.
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { AuthProviderPublic, FoirAccess,
|
|
2
|
+
import type { AuthProviderPublic, FoirAccess, FoirUser, FoirSessionConfig, SessionAdapter } from '../auth';
|
|
3
3
|
import type { ProviderCallbackInput, ProviderLoginInput } from '../auth';
|
|
4
4
|
import type { FoirEnv } from '../graphql/foir-request';
|
|
5
5
|
/** Where the session hydration stands. */
|
|
6
6
|
export type FoirStatus = 'loading' | 'authenticated' | 'unauthenticated';
|
|
7
7
|
/**
|
|
8
|
-
* Where reading the signed-in
|
|
8
|
+
* Where reading the signed-in user's access stands. `idle` until something
|
|
9
9
|
* asks for it; `unavailable` when the platform could not be asked.
|
|
10
10
|
*/
|
|
11
11
|
export type FoirAccessStatus = 'idle' | 'loading' | 'ready' | 'unavailable';
|
|
12
12
|
/** The full context value. Read it through the hooks below, not directly. */
|
|
13
13
|
export interface FoirContextValue {
|
|
14
14
|
status: FoirStatus;
|
|
15
|
-
|
|
16
|
-
/** The signed-in
|
|
15
|
+
user: FoirUser | null;
|
|
16
|
+
/** The signed-in user's access, once read. See {@link useFoirAccess}. */
|
|
17
17
|
access: FoirAccess | null;
|
|
18
18
|
accessStatus: FoirAccessStatus;
|
|
19
|
-
/** Ask the platform for the signed-in
|
|
19
|
+
/** Ask the platform for the signed-in user's access, again if it was read before. */
|
|
20
20
|
loadAccess(): Promise<void>;
|
|
21
21
|
/** The same, unless a read is already under way: then that one is awaited. */
|
|
22
22
|
ensureAccess(): Promise<void>;
|
|
@@ -27,22 +27,22 @@ export interface FoirContextValue {
|
|
|
27
27
|
signIn(vars: {
|
|
28
28
|
email: string;
|
|
29
29
|
password: string;
|
|
30
|
-
}): Promise<
|
|
30
|
+
}): Promise<FoirUser>;
|
|
31
31
|
register(vars: {
|
|
32
32
|
email: string;
|
|
33
33
|
password: string;
|
|
34
|
-
}): Promise<
|
|
34
|
+
}): Promise<FoirUser>;
|
|
35
35
|
requestOtp(email: string): Promise<boolean>;
|
|
36
36
|
signInWithOtp(vars: {
|
|
37
37
|
email: string;
|
|
38
38
|
otp: string;
|
|
39
|
-
}): Promise<
|
|
39
|
+
}): Promise<FoirUser>;
|
|
40
40
|
/** Start a provider (OAuth/OIDC) login. Redirect providers navigate away. */
|
|
41
41
|
signInWithProvider(providerKey: string, opts?: Omit<ProviderLoginInput, 'providerKey'>): Promise<void>;
|
|
42
42
|
/** Complete a provider login from your callback route (code + state). */
|
|
43
43
|
completeProviderLogin(input: Omit<ProviderCallbackInput, 'providerKey'> & {
|
|
44
44
|
providerKey: string;
|
|
45
|
-
}): Promise<
|
|
45
|
+
}): Promise<FoirUser>;
|
|
46
46
|
signOut(): Promise<void>;
|
|
47
47
|
/** Rotate the access token; returns false (and signs out) if it can't. */
|
|
48
48
|
refresh(): Promise<boolean>;
|
|
@@ -78,8 +78,8 @@ export interface FoirProviderProps {
|
|
|
78
78
|
children: ReactNode;
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
|
-
* Provides Foir
|
|
82
|
-
* then exposes the current
|
|
81
|
+
* Provides Foir user auth to a React tree. Hydrates the session on mount,
|
|
82
|
+
* then exposes the current user and sign-in/out actions through
|
|
83
83
|
* {@link useFoirSession}, {@link useFoirUser}, and {@link useFoirAuth}.
|
|
84
84
|
*
|
|
85
85
|
* Two session models:
|
|
@@ -93,14 +93,14 @@ export declare function FoirProvider({ env, adapter, hostedSession, children, }:
|
|
|
93
93
|
/** The current session state. */
|
|
94
94
|
export interface FoirSession {
|
|
95
95
|
status: FoirStatus;
|
|
96
|
-
|
|
96
|
+
user: FoirUser | null;
|
|
97
97
|
isLoading: boolean;
|
|
98
98
|
isAuthenticated: boolean;
|
|
99
99
|
}
|
|
100
|
-
/** Read the current session status and
|
|
100
|
+
/** Read the current session status and user. */
|
|
101
101
|
export declare function useFoirSession(): FoirSession;
|
|
102
|
-
/** The signed-in
|
|
103
|
-
export declare function useFoirUser():
|
|
102
|
+
/** The signed-in user (`{ id, email }`), or null. */
|
|
103
|
+
export declare function useFoirUser(): FoirUser | null;
|
|
104
104
|
/** What {@link useFoirAccess} returns. */
|
|
105
105
|
export interface FoirAccessState {
|
|
106
106
|
/**
|
|
@@ -110,17 +110,17 @@ export interface FoirAccessState {
|
|
|
110
110
|
*/
|
|
111
111
|
status: 'loading' | 'ready' | 'unavailable';
|
|
112
112
|
isLoading: boolean;
|
|
113
|
-
/** The signed-in
|
|
113
|
+
/** The signed-in user's access, or null: signed out, loading or unavailable. */
|
|
114
114
|
access: FoirAccess | null;
|
|
115
|
-
/** Whether the
|
|
115
|
+
/** Whether the user holds the role with this key. False until known. */
|
|
116
116
|
hasRole(key: string): boolean;
|
|
117
|
-
/** Whether the
|
|
117
|
+
/** Whether the user's scopes cover this one. False until known. */
|
|
118
118
|
hasScope(scope: string): boolean;
|
|
119
119
|
/** Ask the platform again, for a role given or taken away since. */
|
|
120
120
|
reload(): Promise<void>;
|
|
121
121
|
}
|
|
122
122
|
/**
|
|
123
|
-
* The roles the signed-in
|
|
123
|
+
* The roles the signed-in user holds and the scopes they resolve to, as the
|
|
124
124
|
* platform resolved them. Read on first use, again when the session refreshes,
|
|
125
125
|
* and whenever `reload` is called.
|
|
126
126
|
*
|
|
@@ -133,20 +133,20 @@ export declare function useFoirAuth(): {
|
|
|
133
133
|
signIn: (vars: {
|
|
134
134
|
email: string;
|
|
135
135
|
password: string;
|
|
136
|
-
}) => Promise<
|
|
136
|
+
}) => Promise<FoirUser>;
|
|
137
137
|
register: (vars: {
|
|
138
138
|
email: string;
|
|
139
139
|
password: string;
|
|
140
|
-
}) => Promise<
|
|
140
|
+
}) => Promise<FoirUser>;
|
|
141
141
|
requestOtp: (email: string) => Promise<boolean>;
|
|
142
142
|
signInWithOtp: (vars: {
|
|
143
143
|
email: string;
|
|
144
144
|
otp: string;
|
|
145
|
-
}) => Promise<
|
|
145
|
+
}) => Promise<FoirUser>;
|
|
146
146
|
signInWithProvider: (providerKey: string, opts?: Omit<ProviderLoginInput, "providerKey">) => Promise<void>;
|
|
147
147
|
completeProviderLogin: (input: Omit<ProviderCallbackInput, "providerKey"> & {
|
|
148
148
|
providerKey: string;
|
|
149
|
-
}) => Promise<
|
|
149
|
+
}) => Promise<FoirUser>;
|
|
150
150
|
signOut: () => Promise<void>;
|
|
151
151
|
refresh: () => Promise<boolean>;
|
|
152
152
|
getToken: () => Promise<string | null>;
|
package/dist/react/SignIn.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FoirUser } from '../auth';
|
|
2
2
|
export interface SignInProps {
|
|
3
3
|
/** `password` (default) or `otp` (email one-time code). Ignored when `hostedUrl` is set. */
|
|
4
4
|
method?: 'password' | 'otp';
|
|
@@ -8,12 +8,12 @@ export interface SignInProps {
|
|
|
8
8
|
oauth?: boolean;
|
|
9
9
|
/** Navigate here after a successful sign-in. */
|
|
10
10
|
afterSignInUrl?: string;
|
|
11
|
-
/** Called with the
|
|
12
|
-
onSignIn?(
|
|
11
|
+
/** Called with the user on a successful sign-in (before any redirect). */
|
|
12
|
+
onSignIn?(user: FoirUser): void;
|
|
13
13
|
className?: string;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* A drop-in
|
|
16
|
+
* A drop-in user sign-in. Renders an email/password (or OTP) form and,
|
|
17
17
|
* unless disabled, OAuth provider buttons. Set `hostedUrl` to instead render a
|
|
18
18
|
* button that redirects to a hosted login page. All state is handled through
|
|
19
19
|
* {@link FoirProvider}; on success it calls `onSignIn` and optionally navigates
|
|
@@ -6,7 +6,7 @@ export interface UserButtonProps {
|
|
|
6
6
|
className?: string;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* A minimal signed-in affordance: the
|
|
9
|
+
* A minimal signed-in affordance: the user's email plus a sign-out button.
|
|
10
10
|
* Renders nothing when signed out. Style it via `className` (the default is an
|
|
11
11
|
* unopinionated inline layout).
|
|
12
12
|
*/
|
|
@@ -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 {
|
|
10
|
+
import type { FoirUserNote, FoirNoteConfig, 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.
|
|
@@ -44,9 +44,9 @@ export interface FoirFeedbackClient {
|
|
|
44
44
|
* widget can name.
|
|
45
45
|
*
|
|
46
46
|
* The distinction earns its place because the three have different audiences.
|
|
47
|
-
* `signIn` is the visitor's to fix. `scope` is the
|
|
47
|
+
* `signIn` is the visitor's to fix. `scope` is the PROJECT's: a role that does
|
|
48
48
|
* not grant `notes:create` produces `missing required scope: notes:create`, which
|
|
49
|
-
* reads like a bad API key and is not one; the key is fine and the
|
|
49
|
+
* reads like a bad API key and is not one; the key is fine and the user's
|
|
50
50
|
* role is short a capability. `other` is everything the widget will not pretend
|
|
51
51
|
* to understand.
|
|
52
52
|
*/
|
|
@@ -98,4 +98,4 @@ export interface FoirFeedbackSubmission {
|
|
|
98
98
|
* worse than one that did not arrive: they have no way to tell, and the thing
|
|
99
99
|
* they wrote ("as you can see in the screenshot") stops making sense.
|
|
100
100
|
*/
|
|
101
|
-
export declare function submitFeedbackNote(client: FoirFeedbackClient, submission: FoirFeedbackSubmission): Promise<
|
|
101
|
+
export declare function submitFeedbackNote(client: FoirFeedbackClient, submission: FoirFeedbackSubmission): Promise<FoirUserNote>;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@foir/sdk/react` — drop-in React components for Foir
|
|
2
|
+
* `@foir/sdk/react` — drop-in React components for Foir users.
|
|
3
3
|
*
|
|
4
4
|
* Wrap your app in `<FoirProvider>`, then read the session with
|
|
5
|
-
* `useFoirSession` / `useFoirUser`, ask what the
|
|
5
|
+
* `useFoirSession` / `useFoirUser`, ask what the user may see with
|
|
6
6
|
* `useFoirAccess`, and drive it with `useFoirAuth` — or drop in
|
|
7
7
|
* the `<SignIn>`, `<OAuthButtons>`, and `<UserButton>` components. Built entirely
|
|
8
8
|
* on `@foir/sdk/auth`; `react` is the only peer dependency.
|
|
@@ -22,5 +22,5 @@ export type { UserButtonProps } from './UserButton';
|
|
|
22
22
|
export { FoirFeedback } from './FoirFeedback';
|
|
23
23
|
export type { FoirFeedbackProps, FoirFeedbackClient } from './FoirFeedback';
|
|
24
24
|
export { createLocalStorageAdapter } from './localStorageAdapter';
|
|
25
|
-
export type { FoirAccess,
|
|
25
|
+
export type { FoirAccess, FoirUser, SessionAdapter, AuthProviderPublic } from '../auth';
|
|
26
26
|
export type { FoirEnv } from '../graphql/foir-request';
|