@foir/sdk 0.30.0 → 0.31.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 +14 -0
- package/README.md +1 -1
- package/dist/ai/index.d.ts +10 -15
- package/dist/ai.js +185 -8
- package/dist/app/client.d.ts +9 -5
- package/dist/app.js +12 -13
- package/dist/auth/auth.d.ts +80 -101
- package/dist/auth/hosted.d.ts +12 -12
- package/dist/auth/index.d.ts +11 -7
- package/dist/auth/operations.d.ts +17 -32
- package/dist/auth/ops.d.ts +4 -13
- package/dist/auth/session-client.d.ts +4 -4
- package/dist/auth/steps.d.ts +50 -0
- package/dist/auth-session.js +3 -3
- package/dist/auth.js +157 -75
- package/dist/content/content-renderer.d.ts +2 -2
- package/dist/content/richtext.d.ts +2 -2
- package/dist/content.js +7 -7
- package/dist/data/builtins.d.ts +64 -33
- 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/selection.d.ts +3 -3
- package/dist/data.js +5 -2
- package/dist/next/index.d.ts +1 -1
- package/dist/next/session.d.ts +27 -11
- package/dist/next.js +98 -40
- package/dist/push.js +9 -19
- package/dist/react/FoirProvider.d.ts +18 -16
- package/dist/react/feedback-notes.d.ts +1 -2
- package/dist/react.js +167 -84
- package/dist/runs/step-reply.d.ts +81 -0
- package/dist/server/callback-client.d.ts +11 -16
- package/dist/server/index.d.ts +2 -0
- package/dist/server/operation-types.d.ts +6 -13
- package/dist/server.js +76 -107
- package/dist/transport/foir-http.d.ts +20 -6
- package/package.json +3 -3
package/dist/react.js
CHANGED
|
@@ -943,7 +943,7 @@ async function fetchFoirManifest(config, options = {}) {
|
|
|
943
943
|
|
|
944
944
|
// src/auth/ops.ts
|
|
945
945
|
var AUTH_OPS = {
|
|
946
|
-
// Pre-credential reads a
|
|
946
|
+
// Pre-credential reads a sign-in screen renders itself from.
|
|
947
947
|
authConfig: "authConfig",
|
|
948
948
|
authProviders: "authProviders",
|
|
949
949
|
defaultAuthProvider: "defaultAuthProvider",
|
|
@@ -955,17 +955,15 @@ var AUTH_OPS = {
|
|
|
955
955
|
// they hold. `./access` reads it.
|
|
956
956
|
whoami: "whoami",
|
|
957
957
|
// Methods.
|
|
958
|
-
|
|
958
|
+
signIn: "signIn",
|
|
959
959
|
register: "register",
|
|
960
960
|
requestOTP: "requestOTP",
|
|
961
|
-
loginWithOTP: "loginWithOTP",
|
|
962
961
|
// Session lifecycle.
|
|
963
962
|
refreshToken: "refreshToken",
|
|
964
|
-
|
|
963
|
+
signOut: "signOut",
|
|
965
964
|
joinWaitlist: "joinWaitlist",
|
|
966
965
|
// Password management and account lifecycle.
|
|
967
966
|
requestPasswordReset: "requestPasswordReset",
|
|
968
|
-
resetPassword: "resetPassword",
|
|
969
967
|
updatePassword: "updatePassword",
|
|
970
968
|
deleteMyAccount: "deleteMyAccount",
|
|
971
969
|
// Ending their own membership where the request stands.
|
|
@@ -979,10 +977,10 @@ var AUTH_OPS = {
|
|
|
979
977
|
mySessions: "mySessions",
|
|
980
978
|
endMySession: "endMySession",
|
|
981
979
|
signOutEverywhere: "signOutEverywhere",
|
|
982
|
-
// Their email addresses: one added is kept once the code it was sent
|
|
980
|
+
// Their email addresses: one added is kept once the code it was sent comes
|
|
981
|
+
// back on the step addMyAddress answers, on its own route (proveMyAddress).
|
|
983
982
|
myAddresses: "myAddresses",
|
|
984
983
|
addMyAddress: "addMyAddress",
|
|
985
|
-
proveMyAddress: "proveMyAddress",
|
|
986
984
|
makeMyAddressPrimary: "makeMyAddressPrimary",
|
|
987
985
|
removeMyAddress: "removeMyAddress",
|
|
988
986
|
// A copy of their own data.
|
|
@@ -993,10 +991,8 @@ var AUTH_OPS = {
|
|
|
993
991
|
disconnectSignInMethod: "disconnectSignInMethod",
|
|
994
992
|
// Their second factors. A change needs one proven first, by a step-up.
|
|
995
993
|
myFactors: "myFactors",
|
|
996
|
-
|
|
997
|
-
completeStepUp: "completeStepUp",
|
|
994
|
+
stepUp: "stepUp",
|
|
998
995
|
beginFactorEnrollment: "beginFactorEnrollment",
|
|
999
|
-
completeFactorEnrollment: "completeFactorEnrollment",
|
|
1000
996
|
removeFactor: "removeFactor",
|
|
1001
997
|
renameFactor: "renameFactor",
|
|
1002
998
|
makeRecoveryCodes: "makeRecoveryCodes",
|
|
@@ -1010,13 +1006,7 @@ var AUTH_OPS = {
|
|
|
1010
1006
|
// Founding one. Present in a manifest only when the project opted in.
|
|
1011
1007
|
registerOrganization: "registerOrganization",
|
|
1012
1008
|
// External identity providers.
|
|
1013
|
-
|
|
1014
|
-
providerCallback: "providerCallback",
|
|
1015
|
-
providerVerifyOTP: "providerVerifyOTP",
|
|
1016
|
-
// A provider sign-in that names an address somebody already holds, waiting
|
|
1017
|
-
// for the code sent to it.
|
|
1018
|
-
confirmProviderLink: "confirmProviderLink",
|
|
1019
|
-
resendProviderLinkCode: "resendProviderLinkCode"
|
|
1009
|
+
signInWithProvider: "signInWithProvider"
|
|
1020
1010
|
};
|
|
1021
1011
|
function authClientConfig(env, options) {
|
|
1022
1012
|
const apiKey = resolveFoirApiKey(env);
|
|
@@ -1076,35 +1066,101 @@ function stripUndefined(args) {
|
|
|
1076
1066
|
return out;
|
|
1077
1067
|
}
|
|
1078
1068
|
|
|
1069
|
+
// src/auth/steps.ts
|
|
1070
|
+
async function postSignInStep(env, path, body, stepToken) {
|
|
1071
|
+
const apiUrl = env.PLATFORM_API_URL;
|
|
1072
|
+
if (!apiUrl) {
|
|
1073
|
+
throw new FoirRequestError("[foir] no api url configured: set PLATFORM_API_URL");
|
|
1074
|
+
}
|
|
1075
|
+
const headers = { "Content-Type": "application/json" };
|
|
1076
|
+
if (stepToken) headers["X-Foir-Token"] = stepToken;
|
|
1077
|
+
const res = await fetch(foirApiEndpoint(apiUrl, path), {
|
|
1078
|
+
method: "POST",
|
|
1079
|
+
headers,
|
|
1080
|
+
body: JSON.stringify(body)
|
|
1081
|
+
});
|
|
1082
|
+
const data = await res.json().catch(() => ({}));
|
|
1083
|
+
if (!res.ok) {
|
|
1084
|
+
const message = typeof data.error === "string" ? data.error : `[foir] POST ${path} failed: HTTP ${res.status}`;
|
|
1085
|
+
const code = typeof data.code === "string" ? data.code : void 0;
|
|
1086
|
+
const extensions = {};
|
|
1087
|
+
if (code) extensions.code = code;
|
|
1088
|
+
if (data.signIn && typeof data.signIn === "object") extensions.signIn = data.signIn;
|
|
1089
|
+
if (data.providerSignIn && typeof data.providerSignIn === "object") extensions.providerSignIn = data.providerSignIn;
|
|
1090
|
+
throw new FoirRequestError(message, {
|
|
1091
|
+
status: res.status,
|
|
1092
|
+
graphQLErrors: Object.keys(extensions).length > 0 ? [{ message, extensions }] : void 0
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
return data;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1079
1098
|
// src/auth/auth.ts
|
|
1080
1099
|
var TOKEN_KEY = "foir:token";
|
|
1081
1100
|
var REFRESH_KEY = "foir:refreshToken";
|
|
1082
1101
|
var USER_ID_KEY = "foir:userId";
|
|
1083
1102
|
var EMAIL_KEY = "foir:email";
|
|
1084
|
-
async function login(env, vars) {
|
|
1085
|
-
return authOp(env, AUTH_OPS.login, vars);
|
|
1086
|
-
}
|
|
1087
|
-
async function register(env, vars) {
|
|
1088
|
-
return authOp(env, AUTH_OPS.register, vars);
|
|
1089
|
-
}
|
|
1090
1103
|
async function requestOTP(env, email) {
|
|
1091
1104
|
return authOp(env, AUTH_OPS.requestOTP, { email });
|
|
1092
1105
|
}
|
|
1093
|
-
async function
|
|
1094
|
-
|
|
1106
|
+
async function codeSignIn(env, vars) {
|
|
1107
|
+
const signedIn = await postSignInStep(env, `/v1/sign-ins/${encodeURIComponent(vars.step.id)}/code`, { code: vars.otp }, vars.step.token);
|
|
1108
|
+
return {
|
|
1109
|
+
token: signedIn.accessToken ?? "",
|
|
1110
|
+
refreshToken: signedIn.refreshToken ?? "",
|
|
1111
|
+
userId: signedIn.user?.id ?? "",
|
|
1112
|
+
email: signedIn.user?.email ?? ""
|
|
1113
|
+
};
|
|
1095
1114
|
}
|
|
1096
|
-
async function
|
|
1115
|
+
async function rotateTokens(env, refreshToken) {
|
|
1097
1116
|
return authOp(env, AUTH_OPS.refreshToken, { refreshToken });
|
|
1098
1117
|
}
|
|
1099
|
-
async function
|
|
1100
|
-
|
|
1118
|
+
async function signInWithProvider(adapter, env, input) {
|
|
1119
|
+
const started = await authOp(
|
|
1120
|
+
env,
|
|
1121
|
+
AUTH_OPS.signInWithProvider,
|
|
1122
|
+
{ input }
|
|
1123
|
+
);
|
|
1124
|
+
if (started.step) await adapter.set(PROVIDER_STEP_KEY, JSON.stringify(started.step));
|
|
1125
|
+
else await adapter.unset(PROVIDER_STEP_KEY);
|
|
1126
|
+
return { redirectUrl: started.redirectUrl ?? null };
|
|
1127
|
+
}
|
|
1128
|
+
async function finishProviderSignIn(adapter, env, input) {
|
|
1129
|
+
const step = await keptProviderStep(adapter, "no provider sign-in was started here: start one before finishing it");
|
|
1130
|
+
return signedInOnStep(adapter, env, step, "provider", {
|
|
1131
|
+
providerKey: input.providerKey,
|
|
1132
|
+
code: input.code,
|
|
1133
|
+
state: input.state
|
|
1134
|
+
});
|
|
1101
1135
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1136
|
+
var PROVIDER_STEP_KEY = "foir:providerStep";
|
|
1137
|
+
async function keptProviderStep(adapter, missing) {
|
|
1138
|
+
const raw = await adapter.get(PROVIDER_STEP_KEY);
|
|
1139
|
+
if (raw) {
|
|
1140
|
+
try {
|
|
1141
|
+
const step = JSON.parse(raw);
|
|
1142
|
+
if (typeof step.id === "string" && typeof step.token === "string") return step;
|
|
1143
|
+
} catch {
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
throw new Error(`[foir] ${missing}`);
|
|
1147
|
+
}
|
|
1148
|
+
async function signedInOnStep(adapter, env, step, route, body) {
|
|
1149
|
+
const signedIn = await postSignInStep(env, `/v1/sign-ins/${encodeURIComponent(step.id)}/${route}`, body, step.token);
|
|
1150
|
+
await adapter.unset(PROVIDER_STEP_KEY);
|
|
1151
|
+
return saveSession(adapter, {
|
|
1152
|
+
token: signedIn.accessToken ?? "",
|
|
1153
|
+
refreshToken: signedIn.refreshToken ?? "",
|
|
1154
|
+
userId: signedIn.userId ?? "",
|
|
1155
|
+
email: signedIn.email ?? ""
|
|
1156
|
+
});
|
|
1104
1157
|
}
|
|
1105
1158
|
async function getAuthProviders(env) {
|
|
1106
1159
|
return authOp(env, AUTH_OPS.authProviders);
|
|
1107
1160
|
}
|
|
1161
|
+
async function endSignIn(env, options) {
|
|
1162
|
+
return authOp(env, AUTH_OPS.signOut, void 0, options);
|
|
1163
|
+
}
|
|
1108
1164
|
async function saveSession(adapter, result) {
|
|
1109
1165
|
await adapter.set(TOKEN_KEY, result.token);
|
|
1110
1166
|
await adapter.set(REFRESH_KEY, result.refreshToken);
|
|
@@ -1131,19 +1187,29 @@ async function getSessionUser(adapter) {
|
|
|
1131
1187
|
return { id, email: email ?? "" };
|
|
1132
1188
|
}
|
|
1133
1189
|
async function signInWithPassword(adapter, env, vars) {
|
|
1134
|
-
return saveSession(adapter, await
|
|
1190
|
+
return saveSession(adapter, await authOp(env, AUTH_OPS.signIn, vars));
|
|
1135
1191
|
}
|
|
1136
1192
|
async function signUpWithPassword(env, vars) {
|
|
1137
|
-
|
|
1193
|
+
return authOp(env, AUTH_OPS.register, vars);
|
|
1138
1194
|
}
|
|
1139
1195
|
async function signInWithOTP(adapter, env, vars) {
|
|
1140
|
-
return saveSession(adapter, await
|
|
1196
|
+
return saveSession(adapter, await codeSignIn(env, vars));
|
|
1197
|
+
}
|
|
1198
|
+
async function signOut(adapter, env) {
|
|
1199
|
+
const token = await getSessionToken(adapter);
|
|
1200
|
+
if (token) {
|
|
1201
|
+
try {
|
|
1202
|
+
await endSignIn(env, { bearerToken: token });
|
|
1203
|
+
} catch {
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
await clearSession(adapter);
|
|
1141
1207
|
}
|
|
1142
1208
|
async function refreshSession(adapter, env) {
|
|
1143
1209
|
const refreshToken = await adapter.get(REFRESH_KEY) ?? null;
|
|
1144
1210
|
if (!refreshToken) return null;
|
|
1145
1211
|
try {
|
|
1146
|
-
const result = await
|
|
1212
|
+
const result = await rotateTokens(env, refreshToken);
|
|
1147
1213
|
await saveSession(adapter, result);
|
|
1148
1214
|
return { token: result.token, refreshToken: result.refreshToken };
|
|
1149
1215
|
} catch {
|
|
@@ -1447,14 +1513,14 @@ function hasScope(access, scope) {
|
|
|
1447
1513
|
|
|
1448
1514
|
// src/auth/session-client.ts
|
|
1449
1515
|
var DEFAULT_SESSION_PATH = "/session/token";
|
|
1450
|
-
var
|
|
1516
|
+
var DEFAULT_SIGN_OUT_PATH = "/session/logout";
|
|
1451
1517
|
var DEFAULT_REFRESH_SKEW_SECONDS = 60;
|
|
1452
1518
|
var MIN_REFRESH_DELAY_MS = 5e3;
|
|
1453
1519
|
var CSRF_HEADER = { "X-Foir-Session": "1" };
|
|
1454
1520
|
function createFoirSessionClient(config) {
|
|
1455
1521
|
const forApp = config.clientId ? `?client_id=${encodeURIComponent(config.clientId)}` : "";
|
|
1456
1522
|
const sessionUrl = `${trimSlash(config.loginOrigin)}${config.sessionPath ?? DEFAULT_SESSION_PATH}${forApp}`;
|
|
1457
|
-
const
|
|
1523
|
+
const signOutUrl = `${trimSlash(config.loginOrigin)}${config.signOutPath ?? DEFAULT_SIGN_OUT_PATH}${forApp}`;
|
|
1458
1524
|
const skewMs = (config.refreshSkewSeconds ?? DEFAULT_REFRESH_SKEW_SECONDS) * 1e3;
|
|
1459
1525
|
let cached = null;
|
|
1460
1526
|
let inflight = null;
|
|
@@ -1550,7 +1616,7 @@ function createFoirSessionClient(config) {
|
|
|
1550
1616
|
},
|
|
1551
1617
|
async signOut() {
|
|
1552
1618
|
try {
|
|
1553
|
-
await fetch(
|
|
1619
|
+
await fetch(signOutUrl, {
|
|
1554
1620
|
method: "POST",
|
|
1555
1621
|
credentials: "include",
|
|
1556
1622
|
headers: { ...CSRF_HEADER }
|
|
@@ -1635,18 +1701,18 @@ function FoirProvider({
|
|
|
1635
1701
|
}, []);
|
|
1636
1702
|
const loginOrigin = hostedSession?.loginOrigin;
|
|
1637
1703
|
const sessionPath = hostedSession?.sessionPath;
|
|
1638
|
-
const
|
|
1704
|
+
const signOutPath = hostedSession?.signOutPath;
|
|
1639
1705
|
const refreshSkewSeconds = hostedSession?.refreshSkewSeconds;
|
|
1640
1706
|
const hosted = useMemo(() => {
|
|
1641
1707
|
if (!loginOrigin) return null;
|
|
1642
1708
|
return createFoirSessionClient({
|
|
1643
1709
|
loginOrigin,
|
|
1644
1710
|
sessionPath,
|
|
1645
|
-
|
|
1711
|
+
signOutPath,
|
|
1646
1712
|
refreshSkewSeconds,
|
|
1647
1713
|
onSignOut: () => apply(null)
|
|
1648
1714
|
});
|
|
1649
|
-
}, [loginOrigin, sessionPath,
|
|
1715
|
+
}, [loginOrigin, sessionPath, signOutPath, refreshSkewSeconds, apply]);
|
|
1650
1716
|
useEffect(() => {
|
|
1651
1717
|
let cancelled = false;
|
|
1652
1718
|
void (async () => {
|
|
@@ -1685,33 +1751,41 @@ function FoirProvider({
|
|
|
1685
1751
|
},
|
|
1686
1752
|
[apply, assertTokenMode]
|
|
1687
1753
|
);
|
|
1688
|
-
const
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1754
|
+
const stepsRef = useRef(/* @__PURE__ */ new Map());
|
|
1755
|
+
const stepKey = (email) => email.trim().toLowerCase();
|
|
1756
|
+
const signUp = useCallback(
|
|
1757
|
+
async (vars) => {
|
|
1758
|
+
assertTokenMode("signUp");
|
|
1759
|
+
stepsRef.current.set(stepKey(vars.email), await signUpWithPassword(envRef.current, vars));
|
|
1692
1760
|
},
|
|
1693
1761
|
[assertTokenMode]
|
|
1694
1762
|
);
|
|
1695
1763
|
const requestOtp = useCallback(
|
|
1696
|
-
(email) => {
|
|
1764
|
+
async (email) => {
|
|
1697
1765
|
assertTokenMode("requestOtp");
|
|
1698
|
-
|
|
1766
|
+
stepsRef.current.set(stepKey(email), await requestOTP(envRef.current, email));
|
|
1767
|
+
return true;
|
|
1699
1768
|
},
|
|
1700
1769
|
[assertTokenMode]
|
|
1701
1770
|
);
|
|
1702
1771
|
const signInWithOtp = useCallback(
|
|
1703
1772
|
async (vars) => {
|
|
1704
1773
|
assertTokenMode("signInWithOtp");
|
|
1705
|
-
const
|
|
1774
|
+
const step = stepsRef.current.get(stepKey(vars.email));
|
|
1775
|
+
if (!step) {
|
|
1776
|
+
throw new Error("[foir] no code was sent to this address here: call requestOtp or signUp first");
|
|
1777
|
+
}
|
|
1778
|
+
const c = await signInWithOTP(adapterRef.current, envRef.current, { step, otp: vars.otp });
|
|
1779
|
+
stepsRef.current.delete(stepKey(vars.email));
|
|
1706
1780
|
apply(c);
|
|
1707
1781
|
return c;
|
|
1708
1782
|
},
|
|
1709
1783
|
[apply, assertTokenMode]
|
|
1710
1784
|
);
|
|
1711
|
-
const
|
|
1785
|
+
const signInWithProvider2 = useCallback(
|
|
1712
1786
|
async (providerKey, opts) => {
|
|
1713
1787
|
assertTokenMode("signInWithProvider");
|
|
1714
|
-
const res = await
|
|
1788
|
+
const res = await signInWithProvider(adapterRef.current, envRef.current, {
|
|
1715
1789
|
providerKey,
|
|
1716
1790
|
...opts
|
|
1717
1791
|
});
|
|
@@ -1721,11 +1795,10 @@ function FoirProvider({
|
|
|
1721
1795
|
},
|
|
1722
1796
|
[assertTokenMode]
|
|
1723
1797
|
);
|
|
1724
|
-
const
|
|
1798
|
+
const finishProviderSignIn2 = useCallback(
|
|
1725
1799
|
async (input) => {
|
|
1726
|
-
assertTokenMode("
|
|
1727
|
-
const
|
|
1728
|
-
const c = await saveSession(adapterRef.current, result);
|
|
1800
|
+
assertTokenMode("finishProviderSignIn");
|
|
1801
|
+
const c = await finishProviderSignIn(adapterRef.current, envRef.current, input);
|
|
1729
1802
|
apply(c);
|
|
1730
1803
|
return c;
|
|
1731
1804
|
},
|
|
@@ -1736,7 +1809,7 @@ function FoirProvider({
|
|
|
1736
1809
|
await hosted.signOut();
|
|
1737
1810
|
return;
|
|
1738
1811
|
}
|
|
1739
|
-
await
|
|
1812
|
+
await signOut(adapterRef.current, envRef.current);
|
|
1740
1813
|
apply(null);
|
|
1741
1814
|
}, [apply, hosted]);
|
|
1742
1815
|
const refresh = useCallback(async () => {
|
|
@@ -1788,7 +1861,7 @@ function FoirProvider({
|
|
|
1788
1861
|
return ok;
|
|
1789
1862
|
}, [refresh, loadAccess, accessWasRead]);
|
|
1790
1863
|
const getProviders = useCallback(() => getAuthProviders(envRef.current), []);
|
|
1791
|
-
const
|
|
1864
|
+
const hostedSignInUrl2 = useMemo(
|
|
1792
1865
|
() => hosted ? (returnTo, options) => hosted.signInUrl(returnTo, options) : null,
|
|
1793
1866
|
[hosted]
|
|
1794
1867
|
);
|
|
@@ -1803,16 +1876,16 @@ function FoirProvider({
|
|
|
1803
1876
|
env,
|
|
1804
1877
|
adapter: adapterRef.current,
|
|
1805
1878
|
signIn,
|
|
1806
|
-
|
|
1879
|
+
signUp,
|
|
1807
1880
|
requestOtp,
|
|
1808
1881
|
signInWithOtp,
|
|
1809
|
-
signInWithProvider,
|
|
1810
|
-
|
|
1882
|
+
signInWithProvider: signInWithProvider2,
|
|
1883
|
+
finishProviderSignIn: finishProviderSignIn2,
|
|
1811
1884
|
signOut: signOut2,
|
|
1812
1885
|
refresh: refreshWithAccess,
|
|
1813
1886
|
getToken,
|
|
1814
1887
|
getProviders,
|
|
1815
|
-
hostedSignInUrl
|
|
1888
|
+
hostedSignInUrl: hostedSignInUrl2
|
|
1816
1889
|
}),
|
|
1817
1890
|
[
|
|
1818
1891
|
status,
|
|
@@ -1823,16 +1896,16 @@ function FoirProvider({
|
|
|
1823
1896
|
ensureAccess,
|
|
1824
1897
|
env,
|
|
1825
1898
|
signIn,
|
|
1826
|
-
|
|
1899
|
+
signUp,
|
|
1827
1900
|
requestOtp,
|
|
1828
1901
|
signInWithOtp,
|
|
1829
|
-
|
|
1830
|
-
|
|
1902
|
+
signInWithProvider2,
|
|
1903
|
+
finishProviderSignIn2,
|
|
1831
1904
|
signOut2,
|
|
1832
1905
|
refreshWithAccess,
|
|
1833
1906
|
getToken,
|
|
1834
1907
|
getProviders,
|
|
1835
|
-
|
|
1908
|
+
hostedSignInUrl2
|
|
1836
1909
|
]
|
|
1837
1910
|
);
|
|
1838
1911
|
return /* @__PURE__ */ jsx(FoirContext.Provider, { value, children });
|
|
@@ -1877,29 +1950,29 @@ function useFoirAccess() {
|
|
|
1877
1950
|
function useFoirAuth() {
|
|
1878
1951
|
const {
|
|
1879
1952
|
signIn,
|
|
1880
|
-
|
|
1953
|
+
signUp,
|
|
1881
1954
|
requestOtp,
|
|
1882
1955
|
signInWithOtp,
|
|
1883
|
-
signInWithProvider,
|
|
1884
|
-
|
|
1956
|
+
signInWithProvider: signInWithProvider2,
|
|
1957
|
+
finishProviderSignIn: finishProviderSignIn2,
|
|
1885
1958
|
signOut: signOut2,
|
|
1886
1959
|
refresh,
|
|
1887
1960
|
getToken,
|
|
1888
1961
|
getProviders,
|
|
1889
|
-
hostedSignInUrl
|
|
1962
|
+
hostedSignInUrl: hostedSignInUrl2
|
|
1890
1963
|
} = useFoirContext();
|
|
1891
1964
|
return {
|
|
1892
1965
|
signIn,
|
|
1893
|
-
|
|
1966
|
+
signUp,
|
|
1894
1967
|
requestOtp,
|
|
1895
1968
|
signInWithOtp,
|
|
1896
|
-
signInWithProvider,
|
|
1897
|
-
|
|
1969
|
+
signInWithProvider: signInWithProvider2,
|
|
1970
|
+
finishProviderSignIn: finishProviderSignIn2,
|
|
1898
1971
|
signOut: signOut2,
|
|
1899
1972
|
refresh,
|
|
1900
1973
|
getToken,
|
|
1901
1974
|
getProviders,
|
|
1902
|
-
hostedSignInUrl
|
|
1975
|
+
hostedSignInUrl: hostedSignInUrl2
|
|
1903
1976
|
};
|
|
1904
1977
|
}
|
|
1905
1978
|
|
|
@@ -1927,7 +2000,7 @@ function OAuthButtons({
|
|
|
1927
2000
|
className,
|
|
1928
2001
|
onError
|
|
1929
2002
|
}) {
|
|
1930
|
-
const { getProviders, signInWithProvider } = useFoirAuth();
|
|
2003
|
+
const { getProviders, signInWithProvider: signInWithProvider2 } = useFoirAuth();
|
|
1931
2004
|
const [providers, setProviders] = useState2([]);
|
|
1932
2005
|
useEffect2(() => {
|
|
1933
2006
|
let cancelled = false;
|
|
@@ -1943,7 +2016,7 @@ function OAuthButtons({
|
|
|
1943
2016
|
);
|
|
1944
2017
|
if (shown.length === 0) return null;
|
|
1945
2018
|
const start = (p) => {
|
|
1946
|
-
void
|
|
2019
|
+
void signInWithProvider2(p.key, { returnTo, redirectUri }).catch(
|
|
1947
2020
|
(err) => onError?.(err)
|
|
1948
2021
|
);
|
|
1949
2022
|
};
|
|
@@ -1996,7 +2069,7 @@ function SignIn({
|
|
|
1996
2069
|
const [error, setError] = useState3(null);
|
|
1997
2070
|
const styled = !className;
|
|
1998
2071
|
if (hostedUrl) {
|
|
1999
|
-
return /* @__PURE__ */ jsx3("div", { className, "data-foir-signin": true, children: /* @__PURE__ */ jsx3("a", { href: hostedUrl, "data-foir-hosted-
|
|
2072
|
+
return /* @__PURE__ */ jsx3("div", { className, "data-foir-signin": true, children: /* @__PURE__ */ jsx3("a", { href: hostedUrl, "data-foir-hosted-sign-in": true, style: styled ? submitStyle : void 0, children: "Sign in" }) });
|
|
2000
2073
|
}
|
|
2001
2074
|
const finish = (user) => {
|
|
2002
2075
|
onSignIn?.(user);
|
|
@@ -2306,7 +2379,6 @@ function noteArgs(subject, body, anchorField) {
|
|
|
2306
2379
|
const args = {
|
|
2307
2380
|
subjectType: "record",
|
|
2308
2381
|
subjectId: subject.recordId,
|
|
2309
|
-
subjectModelKey: subject.modelKey,
|
|
2310
2382
|
body
|
|
2311
2383
|
};
|
|
2312
2384
|
if (anchorField) args.anchorField = anchorField;
|
|
@@ -2320,7 +2392,7 @@ async function uploadFeedbackAttachment(client, screenshot, fetchImpl = globalTh
|
|
|
2320
2392
|
);
|
|
2321
2393
|
}
|
|
2322
2394
|
const ticket = readUploadTicket(
|
|
2323
|
-
await files.
|
|
2395
|
+
await files.uploadFile({
|
|
2324
2396
|
filename: screenshotFilename(screenshot.mimeType),
|
|
2325
2397
|
mimeType: screenshot.mimeType,
|
|
2326
2398
|
// The blob's own size, never a number computed alongside it: this is what
|
|
@@ -2339,24 +2411,35 @@ async function uploadFeedbackAttachment(client, screenshot, fetchImpl = globalTh
|
|
|
2339
2411
|
`[foir] uploading the screenshot failed: HTTP ${res.status} ${res.statusText}. The note was not written.`
|
|
2340
2412
|
);
|
|
2341
2413
|
}
|
|
2342
|
-
const
|
|
2343
|
-
|
|
2414
|
+
const landed = await fetchImpl(ticket.landedEndpoint, {
|
|
2415
|
+
method: "POST",
|
|
2416
|
+
headers: { "X-Foir-Token": ticket.landedToken }
|
|
2417
|
+
});
|
|
2418
|
+
if (!landed.ok) {
|
|
2419
|
+
throw new Error(
|
|
2420
|
+
`[foir] the screenshot's upload was refused when it landed: HTTP ${landed.status} ${landed.statusText}. The note was not written.`
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
const settled = await landed.json().catch(() => null);
|
|
2424
|
+
const id = settled?.id;
|
|
2344
2425
|
if (typeof id !== "string" || id === "") {
|
|
2345
2426
|
throw new Error(
|
|
2346
|
-
"[foir]
|
|
2427
|
+
"[foir] the upload landed and was answered without a file id, so there is nothing to attach the note to. The note was not written."
|
|
2347
2428
|
);
|
|
2348
2429
|
}
|
|
2349
2430
|
return id;
|
|
2350
2431
|
}
|
|
2351
2432
|
function readUploadTicket(result) {
|
|
2352
|
-
const
|
|
2353
|
-
const uploadUrl =
|
|
2354
|
-
|
|
2433
|
+
const r = result;
|
|
2434
|
+
const uploadUrl = r?.uploadUrl;
|
|
2435
|
+
const landedEndpoint = r?.landed?.endpoint;
|
|
2436
|
+
const landedToken = r?.landed?.token;
|
|
2437
|
+
if (typeof uploadUrl !== "string" || !uploadUrl || typeof landedEndpoint !== "string" || !landedEndpoint || typeof landedToken !== "string" || !landedToken) {
|
|
2355
2438
|
throw new Error(
|
|
2356
|
-
"[foir]
|
|
2439
|
+
"[foir] uploadFile answered without an uploadUrl and a way to say the bytes landed, so the screenshot has nowhere to go. The note was not written."
|
|
2357
2440
|
);
|
|
2358
2441
|
}
|
|
2359
|
-
return {
|
|
2442
|
+
return { uploadUrl, landedEndpoint, landedToken };
|
|
2360
2443
|
}
|
|
2361
2444
|
function readCreatedNote(result) {
|
|
2362
2445
|
const note = result;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A step's reply to its run.
|
|
3
|
+
*
|
|
4
|
+
* A step that answers later replies with the token its run handed it alone,
|
|
5
|
+
* signed with the key that came with it, at the address that came with it:
|
|
6
|
+
* an operation's endpoint, a local development session forwarding a
|
|
7
|
+
* dispatch, or the page of a run's own caller answering a call to one of its
|
|
8
|
+
* tools. Nobody else holds such a token, so a reply reaches the one step it
|
|
9
|
+
* was minted for.
|
|
10
|
+
*
|
|
11
|
+
* Each reply is POSTed to the step's own reply route as the contract's reply
|
|
12
|
+
* itself (`{"result": ...}` and the rest), and carries:
|
|
13
|
+
* - `Authorization: Bearer <reply.token>`
|
|
14
|
+
* - `X-Foir-Signature: sha256=<hex>`, the HMAC-SHA256 of the raw request
|
|
15
|
+
* body under `reply.signingKey`.
|
|
16
|
+
*
|
|
17
|
+
* A reply that gets no answer, or a 5xx or a 429, is tried again: every reply
|
|
18
|
+
* is safe to repeat, and a step whose reply is lost waits until its time is up.
|
|
19
|
+
*/
|
|
20
|
+
import { type FoirRetryPolicy } from '../transport/retry';
|
|
21
|
+
/** How a step replies to its run, as the platform hands it to whoever runs the step. */
|
|
22
|
+
export interface StepReply {
|
|
23
|
+
token: string;
|
|
24
|
+
signingKey: string;
|
|
25
|
+
/** The step's own reply route at the data API. */
|
|
26
|
+
endpoint: string;
|
|
27
|
+
/** ISO-8601. After it the token is refused. */
|
|
28
|
+
expiresAt?: string | null;
|
|
29
|
+
}
|
|
30
|
+
/** A step's reply, in the runs contract's own shape: exactly one of these. */
|
|
31
|
+
export type StepReplyBody = {
|
|
32
|
+
result: unknown;
|
|
33
|
+
} | {
|
|
34
|
+
progress: {
|
|
35
|
+
percent?: number;
|
|
36
|
+
message?: string;
|
|
37
|
+
metadata?: Record<string, unknown>;
|
|
38
|
+
};
|
|
39
|
+
} | {
|
|
40
|
+
failure: {
|
|
41
|
+
code: string;
|
|
42
|
+
message: string;
|
|
43
|
+
retryable?: boolean;
|
|
44
|
+
details?: Record<string, unknown>;
|
|
45
|
+
};
|
|
46
|
+
} | {
|
|
47
|
+
continuation: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
/** The step stops its own run. */
|
|
50
|
+
| {
|
|
51
|
+
cancel: Record<string, never>;
|
|
52
|
+
};
|
|
53
|
+
/** What the platform made of a reply. */
|
|
54
|
+
export interface StepReplyResult {
|
|
55
|
+
/** The step's status after the reply: running, held, completed, failed, cancelled, timed_out or pending. */
|
|
56
|
+
status: string;
|
|
57
|
+
/** True when the step had been cancelled, so the reply was not taken. */
|
|
58
|
+
cancelled: boolean;
|
|
59
|
+
/** False when the reply changed nothing: the step had ended, or progress came out of order. */
|
|
60
|
+
applied: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** Reply to a run from one of its steps, with the reply its run handed it. */
|
|
63
|
+
export declare function replyToStep(reply: StepReply, body: StepReplyBody, opts?: {
|
|
64
|
+
fetch?: typeof fetch;
|
|
65
|
+
retry?: FoirRetryPolicy;
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
}): Promise<StepReplyResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Send one reply to a step's reply route, signed with `signingKey`. Shared by
|
|
70
|
+
* `replyToStep` and the async endpoint's callback client.
|
|
71
|
+
*/
|
|
72
|
+
export declare function sendReply(endpoint: string, token: string, signingKey: string, body: StepReplyBody, opts?: {
|
|
73
|
+
fetch?: typeof fetch;
|
|
74
|
+
retry?: FoirRetryPolicy;
|
|
75
|
+
signal?: AbortSignal;
|
|
76
|
+
}): Promise<StepReplyResult>;
|
|
77
|
+
/**
|
|
78
|
+
* The lowercase hex HMAC-SHA256 of a body, through Web Crypto, so it runs in
|
|
79
|
+
* a browser, a worker and Node alike.
|
|
80
|
+
*/
|
|
81
|
+
export declare function hmacSha256Hex(secret: string, body: string): Promise<string>;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The platform dispatches with `mode='async'` and includes a `callback`
|
|
5
5
|
* block in the operation payload. The extension acks 202, kicks the work
|
|
6
6
|
* off in its own durable runtime (CF Queue, BullMQ, etc.), and later
|
|
7
|
-
*
|
|
7
|
+
* replies at the run's own reply route (`callback.endpoint`) with one of:
|
|
8
8
|
*
|
|
9
9
|
* - `complete(result)` — operation succeeded; result conforms to the
|
|
10
10
|
* operation's `output_schema` (per-op typed via codegen).
|
|
@@ -27,18 +27,15 @@
|
|
|
27
27
|
* with a 5xx or a 429, is tried again: a report that is lost leaves the run to
|
|
28
28
|
* be closed by the platform's watchdog as timed out, and every one of these
|
|
29
29
|
* calls is safe to repeat.
|
|
30
|
-
*
|
|
31
|
-
* Both response body shape and content-type follow the GraphQL spec —
|
|
32
|
-
* extensions importing this client receive typed result data.
|
|
33
30
|
*/
|
|
34
|
-
import {
|
|
31
|
+
import type { FoirRetryPolicy } from '../transport/retry';
|
|
35
32
|
import type { OperationCallback } from './operation-types.js';
|
|
36
|
-
/**
|
|
33
|
+
/** What the platform made of a reply. */
|
|
37
34
|
export interface CallbackResult {
|
|
38
35
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
36
|
+
* The run's status as the platform sees it AFTER the call: pending,
|
|
37
|
+
* running, completed, failed, cancelled or timed_out. Always set, even on
|
|
38
|
+
* a repeated or a cancelled call.
|
|
42
39
|
*/
|
|
43
40
|
status: string;
|
|
44
41
|
/**
|
|
@@ -48,16 +45,14 @@ export interface CallbackResult {
|
|
|
48
45
|
*/
|
|
49
46
|
cancelled: boolean;
|
|
50
47
|
/**
|
|
51
|
-
*
|
|
52
|
-
* out
|
|
53
|
-
* reordering without erroring; missing on complete/fail/cancel.
|
|
48
|
+
* False when the reply changed nothing: the run had already ended, or a
|
|
49
|
+
* progress report came out of order (the monotonic guard dropped it).
|
|
54
50
|
*/
|
|
55
51
|
applied?: boolean;
|
|
56
52
|
}
|
|
57
53
|
/**
|
|
58
54
|
* Per-operation callback client. `TResult` is the operation's typed result
|
|
59
|
-
* shape
|
|
60
|
-
* api-public schema (`Complete{Op}ExecutionMutationVariables['result']`).
|
|
55
|
+
* shape, the operation's output type.
|
|
61
56
|
*/
|
|
62
57
|
export interface CallbackClient<TResult = unknown> {
|
|
63
58
|
complete(result: TResult): Promise<CallbackResult>;
|
|
@@ -97,9 +92,9 @@ export interface CallbackClient<TResult = unknown> {
|
|
|
97
92
|
* told otherwise; `{ attempts: 1 }` turns it off).
|
|
98
93
|
*
|
|
99
94
|
* `apiUrl` is the Foir API origin the endpoint is configured with. The
|
|
100
|
-
* dispatch names the address to
|
|
95
|
+
* dispatch names the address to reply at and that address is the one used; a
|
|
101
96
|
* platform with no public address of its own configured names none, and the
|
|
102
|
-
* run then
|
|
97
|
+
* run then replies at its route under `apiUrl`.
|
|
103
98
|
*/
|
|
104
99
|
export declare function createCallbackClient<TResult = unknown>(callback: OperationCallback, opts: {
|
|
105
100
|
executionId: string;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type { OperationPayload, OperationTrigger, OperationRecordContext, Operat
|
|
|
9
9
|
export { continueRun, CONTINUATION_MEDIA_TYPE, MAX_CONTINUATION_CURSOR_BYTES, MAX_CONTINUATION_DELAY_MS, type Continuation, } from './continue-run';
|
|
10
10
|
export { createCallbackClient } from './callback-client';
|
|
11
11
|
export type { CallbackClient, CallbackResult } from './callback-client';
|
|
12
|
+
export { replyToStep } from '../runs/step-reply';
|
|
13
|
+
export type { StepReply, StepReplyBody, StepReplyResult } from '../runs/step-reply';
|
|
12
14
|
export { createEventsClient, FoirEventCursorExpiredError, isFoirEventCursorExpired, FOIR_EVENTS_PATH, FOIR_EVENTS_SCOPE, FOIR_EVENTS_RETENTION_DAYS, } from './events-client';
|
|
13
15
|
export type { FoirEvent, FoirEventActor, FoirEventPage, FoirEventRetention, FoirEventsClient, FoirEventsClientConfig, FoirEventsListParams, FoirEventsStreamParams, FoirEventsSubscribeParams, } from './events-client';
|
|
14
16
|
export type { FoirSubscription, FoirSubscriptionHandlers } from '../data/ws-transport';
|