@feelflow/ffid-sdk 1.11.0 → 1.13.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.
@@ -433,7 +433,7 @@ function createBillingMethods(deps) {
433
433
  }
434
434
 
435
435
  // src/client/version-check.ts
436
- var SDK_VERSION = "1.11.0";
436
+ var SDK_VERSION = "1.13.0";
437
437
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
438
438
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
439
439
  function sdkHeaders() {
@@ -479,6 +479,22 @@ npm install @feelflow/ffid-sdk@latest \u3067\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8
479
479
  var OAUTH_TOKEN_ENDPOINT = "/api/v1/oauth/token";
480
480
  var OAUTH_REVOKE_ENDPOINT = "/api/v1/oauth/revoke";
481
481
  var MS_PER_SECOND = 1e3;
482
+ function validateTokenResponse(tokenResponse) {
483
+ const invalid = [];
484
+ if (!tokenResponse.access_token) {
485
+ invalid.push("access_token");
486
+ }
487
+ if (!tokenResponse.refresh_token) {
488
+ invalid.push("refresh_token");
489
+ }
490
+ if (typeof tokenResponse.expires_in !== "number" || tokenResponse.expires_in <= 0) {
491
+ invalid.push("expires_in");
492
+ }
493
+ if (invalid.length > 0) {
494
+ return `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306B\u4E0D\u6B63\u306A\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u3042\u308A\u307E\u3059: ${invalid.join(", ")}`;
495
+ }
496
+ return null;
497
+ }
482
498
  function createOAuthTokenMethods(deps) {
483
499
  const {
484
500
  baseUrl,
@@ -548,6 +564,16 @@ function createOAuthTokenMethods(deps) {
548
564
  }
549
565
  };
550
566
  }
567
+ const validationError = validateTokenResponse(tokenResponse);
568
+ if (validationError) {
569
+ logger.error("Token exchange validation failed:", validationError);
570
+ return {
571
+ error: {
572
+ code: errorCodes.TOKEN_EXCHANGE_ERROR,
573
+ message: validationError
574
+ }
575
+ };
576
+ }
551
577
  tokenStore.setTokens({
552
578
  accessToken: tokenResponse.access_token,
553
579
  refreshToken: tokenResponse.refresh_token,
@@ -616,6 +642,16 @@ function createOAuthTokenMethods(deps) {
616
642
  }
617
643
  };
618
644
  }
645
+ const validationError = validateTokenResponse(tokenResponse);
646
+ if (validationError) {
647
+ logger.error("Token refresh validation failed:", validationError);
648
+ return {
649
+ error: {
650
+ code: errorCodes.TOKEN_REFRESH_ERROR,
651
+ message: validationError
652
+ }
653
+ };
654
+ }
619
655
  tokenStore.setTokens({
620
656
  accessToken: tokenResponse.access_token,
621
657
  refreshToken: tokenResponse.refresh_token,
@@ -869,11 +905,17 @@ async function generateCodeChallenge(verifier) {
869
905
  const digest = await crypto.subtle.digest("SHA-256", data);
870
906
  return base64UrlEncode(digest);
871
907
  }
872
- function storeCodeVerifier(verifier) {
908
+ function storeCodeVerifier(verifier, logger) {
873
909
  try {
874
- if (typeof window === "undefined") return;
910
+ if (typeof window === "undefined") {
911
+ logger?.warn("storeCodeVerifier: sessionStorage is not available in SSR context");
912
+ return false;
913
+ }
875
914
  window.sessionStorage.setItem(VERIFIER_STORAGE_KEY, verifier);
876
- } catch {
915
+ return true;
916
+ } catch (error) {
917
+ logger?.warn("storeCodeVerifier: sessionStorage \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
918
+ return false;
877
919
  }
878
920
  }
879
921
  function base64UrlEncode(buffer) {
@@ -905,32 +947,34 @@ function createRedirectMethods(deps) {
905
947
  } = deps;
906
948
  async function redirectToAuthorize() {
907
949
  const verifier = generateCodeVerifier();
908
- storeCodeVerifier(verifier);
950
+ storeCodeVerifier(verifier, logger);
951
+ let challenge;
909
952
  try {
910
- const challenge = await generateCodeChallenge(verifier);
911
- const state = generateRandomState();
912
- const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
913
- const params = new URLSearchParams({
914
- response_type: "code",
915
- client_id: clientId,
916
- redirect_uri: redirectUri,
917
- state,
918
- code_challenge: challenge,
919
- code_challenge_method: "S256"
920
- });
921
- const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
922
- logger.debug("Redirecting to authorize:", authorizeUrl);
923
- window.location.href = authorizeUrl;
924
- return true;
953
+ challenge = await generateCodeChallenge(verifier);
925
954
  } catch (error) {
955
+ const errorMessage = error instanceof Error ? error.message : "PKCE \u30B3\u30FC\u30C9\u30C1\u30E3\u30EC\u30F3\u30B8\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F";
926
956
  logger.error("PKCE \u30B3\u30FC\u30C9\u30C1\u30E3\u30EC\u30F3\u30B8\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
927
- return false;
957
+ return { success: false, error: errorMessage };
928
958
  }
959
+ const state = generateRandomState();
960
+ const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
961
+ const params = new URLSearchParams({
962
+ response_type: "code",
963
+ client_id: clientId,
964
+ redirect_uri: redirectUri,
965
+ state,
966
+ code_challenge: challenge,
967
+ code_challenge_method: "S256"
968
+ });
969
+ const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
970
+ logger.debug("Redirecting to authorize:", authorizeUrl);
971
+ window.location.href = authorizeUrl;
972
+ return { success: true };
929
973
  }
930
974
  async function redirectToLogin() {
931
975
  if (typeof window === "undefined") {
932
- logger.debug("Cannot redirect in SSR context");
933
- return false;
976
+ logger.warn("SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093");
977
+ return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
934
978
  }
935
979
  if (authMode === "token") {
936
980
  return redirectToAuthorize();
@@ -939,19 +983,273 @@ function createRedirectMethods(deps) {
939
983
  const loginUrl = `${baseUrl}/login?redirect=${encodeURIComponent(currentUrl)}&service=${encodeURIComponent(serviceCode)}`;
940
984
  logger.debug("Redirecting to login:", loginUrl);
941
985
  window.location.href = loginUrl;
942
- return true;
986
+ return { success: true };
943
987
  }
944
988
  function getLoginUrl(redirectUrl) {
945
- const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
989
+ let redirect;
990
+ if (redirectUrl != null) {
991
+ redirect = redirectUrl;
992
+ } else if (typeof window !== "undefined") {
993
+ redirect = window.location.href;
994
+ } else {
995
+ logger.warn("getLoginUrl: SSR \u74B0\u5883\u3067 redirectUrl \u304C\u672A\u6307\u5B9A\u306E\u305F\u3081\u7A7A\u6587\u5B57\u306B\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3057\u307E\u3059");
996
+ redirect = "";
997
+ }
946
998
  return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
947
999
  }
948
1000
  function getSignupUrl(redirectUrl) {
949
- const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
1001
+ let redirect;
1002
+ if (redirectUrl != null) {
1003
+ redirect = redirectUrl;
1004
+ } else if (typeof window !== "undefined") {
1005
+ redirect = window.location.href;
1006
+ } else {
1007
+ logger.warn("getSignupUrl: SSR \u74B0\u5883\u3067 redirectUrl \u304C\u672A\u6307\u5B9A\u306E\u305F\u3081\u7A7A\u6587\u5B57\u306B\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3057\u307E\u3059");
1008
+ redirect = "";
1009
+ }
950
1010
  return `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
951
1011
  }
952
1012
  return { redirectToLogin, redirectToAuthorize, getLoginUrl, getSignupUrl };
953
1013
  }
954
1014
 
1015
+ // src/client/password-reset.ts
1016
+ var RESET_PASSWORD_BASE = "/api/v1/auth/reset-password";
1017
+ function isBlank(value) {
1018
+ return !value || !value.trim();
1019
+ }
1020
+ function createPasswordResetMethods(deps) {
1021
+ const { baseUrl, logger, createError, fetchWithAuth, errorCodes } = deps;
1022
+ async function fetchPublic(endpoint, options = {}) {
1023
+ const url = `${baseUrl}${endpoint}`;
1024
+ logger.debug("Fetching (public):", url);
1025
+ let response;
1026
+ try {
1027
+ response = await fetch(url, {
1028
+ ...options,
1029
+ credentials: "include",
1030
+ headers: {
1031
+ "Content-Type": "application/json",
1032
+ ...sdkHeaders(),
1033
+ ...options.headers
1034
+ }
1035
+ });
1036
+ } catch (error) {
1037
+ logger.error("Network error:", error);
1038
+ return {
1039
+ error: {
1040
+ code: errorCodes.NETWORK_ERROR,
1041
+ message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
1042
+ }
1043
+ };
1044
+ }
1045
+ let raw;
1046
+ try {
1047
+ raw = await response.json();
1048
+ } catch (parseError) {
1049
+ logger.error("Parse error:", parseError, "Status:", response.status);
1050
+ return {
1051
+ error: {
1052
+ code: errorCodes.PARSE_ERROR,
1053
+ message: `\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u4E0D\u6B63\u306A\u30EC\u30B9\u30DD\u30F3\u30B9\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F (status: ${response.status})`
1054
+ }
1055
+ };
1056
+ }
1057
+ logger.debug("Response (public):", response.status, raw);
1058
+ checkVersionHeader(response, logger);
1059
+ if (!response.ok) {
1060
+ return {
1061
+ error: raw.error ?? {
1062
+ code: errorCodes.UNKNOWN_ERROR,
1063
+ message: "\u30D1\u30B9\u30EF\u30FC\u30C9\u30EA\u30BB\u30C3\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
1064
+ }
1065
+ };
1066
+ }
1067
+ if (raw.data === void 0) {
1068
+ return {
1069
+ error: {
1070
+ code: errorCodes.UNKNOWN_ERROR,
1071
+ message: "\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u30C7\u30FC\u30BF\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F"
1072
+ }
1073
+ };
1074
+ }
1075
+ return { data: raw.data };
1076
+ }
1077
+ async function requestPasswordReset(email) {
1078
+ if (isBlank(email)) {
1079
+ return {
1080
+ error: createError("VALIDATION_ERROR", "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306F\u5FC5\u9808\u3067\u3059")
1081
+ };
1082
+ }
1083
+ return fetchPublic(
1084
+ RESET_PASSWORD_BASE,
1085
+ {
1086
+ method: "POST",
1087
+ body: JSON.stringify({ email })
1088
+ }
1089
+ );
1090
+ }
1091
+ async function verifyPasswordResetToken(accessToken) {
1092
+ if (isBlank(accessToken)) {
1093
+ return {
1094
+ error: createError("VALIDATION_ERROR", "\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
1095
+ };
1096
+ }
1097
+ const query = new URLSearchParams({
1098
+ access_token: accessToken,
1099
+ type: "recovery"
1100
+ });
1101
+ return fetchPublic(
1102
+ `${RESET_PASSWORD_BASE}/verify?${query.toString()}`
1103
+ );
1104
+ }
1105
+ async function establishResetSession(accessToken, refreshToken) {
1106
+ if (isBlank(accessToken)) {
1107
+ return {
1108
+ error: createError("VALIDATION_ERROR", "\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
1109
+ };
1110
+ }
1111
+ if (isBlank(refreshToken)) {
1112
+ return {
1113
+ error: createError("VALIDATION_ERROR", "\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
1114
+ };
1115
+ }
1116
+ return fetchPublic(
1117
+ `${RESET_PASSWORD_BASE}/session`,
1118
+ {
1119
+ method: "POST",
1120
+ body: JSON.stringify({ accessToken, refreshToken })
1121
+ }
1122
+ );
1123
+ }
1124
+ async function confirmPasswordReset(password) {
1125
+ if (isBlank(password)) {
1126
+ return {
1127
+ error: createError("VALIDATION_ERROR", "\u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u5FC5\u9808\u3067\u3059")
1128
+ };
1129
+ }
1130
+ return fetchWithAuth(
1131
+ `${RESET_PASSWORD_BASE}/confirm`,
1132
+ {
1133
+ method: "POST",
1134
+ body: JSON.stringify({ password })
1135
+ }
1136
+ );
1137
+ }
1138
+ return {
1139
+ requestPasswordReset,
1140
+ verifyPasswordResetToken,
1141
+ establishResetSession,
1142
+ confirmPasswordReset
1143
+ };
1144
+ }
1145
+
1146
+ // src/client/otp.ts
1147
+ var OTP_BASE = "/api/v1/auth/otp";
1148
+ function isBlank2(value) {
1149
+ return !value || !value.trim();
1150
+ }
1151
+ function createOtpMethods(deps) {
1152
+ const { baseUrl, logger, createError, errorCodes } = deps;
1153
+ async function fetchPublic(endpoint, options = {}) {
1154
+ const url = `${baseUrl}${endpoint}`;
1155
+ logger.debug("Fetching (public):", url);
1156
+ let response;
1157
+ try {
1158
+ response = await fetch(url, {
1159
+ ...options,
1160
+ credentials: "include",
1161
+ headers: {
1162
+ "Content-Type": "application/json",
1163
+ ...sdkHeaders(),
1164
+ ...options.headers
1165
+ }
1166
+ });
1167
+ } catch (error) {
1168
+ logger.error("Network error:", error);
1169
+ return {
1170
+ error: {
1171
+ code: errorCodes.NETWORK_ERROR,
1172
+ message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
1173
+ }
1174
+ };
1175
+ }
1176
+ let raw;
1177
+ try {
1178
+ raw = await response.json();
1179
+ } catch (parseError) {
1180
+ logger.error("Parse error:", parseError, "Status:", response.status);
1181
+ return {
1182
+ error: {
1183
+ code: errorCodes.PARSE_ERROR,
1184
+ message: `\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u4E0D\u6B63\u306A\u30EC\u30B9\u30DD\u30F3\u30B9\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F (status: ${response.status})`
1185
+ }
1186
+ };
1187
+ }
1188
+ logger.debug("Response (public):", response.status, raw);
1189
+ checkVersionHeader(response, logger);
1190
+ if (!response.ok) {
1191
+ return {
1192
+ error: raw.error ?? {
1193
+ code: errorCodes.UNKNOWN_ERROR,
1194
+ message: "OTP\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
1195
+ }
1196
+ };
1197
+ }
1198
+ if (raw.data === void 0) {
1199
+ return {
1200
+ error: {
1201
+ code: errorCodes.UNKNOWN_ERROR,
1202
+ message: "\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u30C7\u30FC\u30BF\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F"
1203
+ }
1204
+ };
1205
+ }
1206
+ return { data: raw.data };
1207
+ }
1208
+ async function sendOtp(email, options) {
1209
+ if (isBlank2(email)) {
1210
+ return {
1211
+ error: createError("VALIDATION_ERROR", "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306F\u5FC5\u9808\u3067\u3059")
1212
+ };
1213
+ }
1214
+ return fetchPublic(
1215
+ `${OTP_BASE}/send`,
1216
+ {
1217
+ method: "POST",
1218
+ body: JSON.stringify({
1219
+ email,
1220
+ ...options?.redirectUrl ? { redirectUrl: options.redirectUrl } : {}
1221
+ })
1222
+ }
1223
+ );
1224
+ }
1225
+ async function verifyOtp(params) {
1226
+ if (isBlank2(params.accessToken)) {
1227
+ return {
1228
+ error: createError("VALIDATION_ERROR", "\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
1229
+ };
1230
+ }
1231
+ if (isBlank2(params.refreshToken)) {
1232
+ return {
1233
+ error: createError("VALIDATION_ERROR", "\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
1234
+ };
1235
+ }
1236
+ return fetchPublic(
1237
+ `${OTP_BASE}/verify`,
1238
+ {
1239
+ method: "POST",
1240
+ body: JSON.stringify({
1241
+ accessToken: params.accessToken,
1242
+ refreshToken: params.refreshToken
1243
+ })
1244
+ }
1245
+ );
1246
+ }
1247
+ return {
1248
+ sendOtp,
1249
+ verifyOtp
1250
+ };
1251
+ }
1252
+
955
1253
  // src/client/ffid-client.ts
956
1254
  var UNAUTHORIZED_STATUS2 = 401;
957
1255
  var SDK_LOG_PREFIX = "[FFID SDK]";
@@ -1084,6 +1382,9 @@ function createFFIDClient(config) {
1084
1382
  }
1085
1383
  };
1086
1384
  }
1385
+ } else {
1386
+ logger.warn("Token refresh failed, returning refresh error:", refreshResult.error);
1387
+ return { error: refreshResult.error };
1087
1388
  }
1088
1389
  }
1089
1390
  let raw;
@@ -1161,6 +1462,24 @@ function createFFIDClient(config) {
1161
1462
  fetchWithAuth,
1162
1463
  createError
1163
1464
  });
1465
+ const {
1466
+ requestPasswordReset,
1467
+ verifyPasswordResetToken,
1468
+ establishResetSession,
1469
+ confirmPasswordReset
1470
+ } = createPasswordResetMethods({
1471
+ baseUrl,
1472
+ logger,
1473
+ createError,
1474
+ fetchWithAuth,
1475
+ errorCodes: FFID_ERROR_CODES
1476
+ });
1477
+ const { sendOtp, verifyOtp } = createOtpMethods({
1478
+ baseUrl,
1479
+ logger,
1480
+ createError,
1481
+ errorCodes: FFID_ERROR_CODES
1482
+ });
1164
1483
  const verifyAccessToken = createVerifyAccessToken({
1165
1484
  authMode,
1166
1485
  baseUrl,
@@ -1186,6 +1505,12 @@ function createFFIDClient(config) {
1186
1505
  createCheckoutSession,
1187
1506
  createPortalSession,
1188
1507
  verifyAccessToken,
1508
+ requestPasswordReset,
1509
+ verifyPasswordResetToken,
1510
+ establishResetSession,
1511
+ confirmPasswordReset,
1512
+ sendOtp,
1513
+ verifyOtp,
1189
1514
  /** Token store (token mode only) */
1190
1515
  tokenStore,
1191
1516
  /** Resolved auth mode */
@@ -301,6 +301,55 @@ interface FFIDCreatePortalParams {
301
301
  /** URL to redirect when user exits the portal */
302
302
  returnUrl: string;
303
303
  }
304
+ /**
305
+ * Result of a redirect operation (redirectToLogin / redirectToAuthorize)
306
+ *
307
+ * Structured return type so callers can inspect failure reasons
308
+ * instead of receiving a bare `false`.
309
+ */
310
+ type FFIDRedirectResult = {
311
+ success: true;
312
+ } | {
313
+ success: false;
314
+ error: string;
315
+ };
316
+
317
+ /** OTP / magic link methods - sendOtp / verifyOtp */
318
+
319
+ /** Response from sendOtp */
320
+ interface FFIDOtpSendResponse {
321
+ message: string;
322
+ }
323
+ /** Response from verifyOtp */
324
+ interface FFIDOtpVerifyResponse {
325
+ user: {
326
+ id: string;
327
+ email: string;
328
+ displayName: string | null;
329
+ avatarUrl: string | null;
330
+ };
331
+ session: {
332
+ accessToken: string;
333
+ refreshToken: string;
334
+ expiresAt: number;
335
+ expiresIn: number;
336
+ };
337
+ }
338
+
339
+ /** Password reset methods - requestPasswordReset / verifyPasswordResetToken / establishResetSession / confirmPasswordReset */
340
+
341
+ /** Response from requestPasswordReset */
342
+ interface FFIDPasswordResetResponse {
343
+ message: string;
344
+ }
345
+ /** Response from verifyPasswordResetToken */
346
+ interface FFIDPasswordResetVerifyResponse {
347
+ valid: boolean;
348
+ }
349
+ /** Response from establishResetSession */
350
+ type FFIDResetSessionResponse = FFIDPasswordResetResponse;
351
+ /** Response from confirmPasswordReset */
352
+ type FFIDPasswordResetConfirmResponse = FFIDPasswordResetResponse;
304
353
 
305
354
  /**
306
355
  * Token Store
@@ -347,7 +396,7 @@ declare function createTokenStore(storageType?: 'localStorage' | 'memory'): Toke
347
396
  declare function createFFIDClient(config: FFIDConfig): {
348
397
  getSession: () => Promise<FFIDApiResponse<FFIDSessionResponse>>;
349
398
  signOut: () => Promise<FFIDApiResponse<void>>;
350
- redirectToLogin: () => Promise<boolean>;
399
+ redirectToLogin: () => Promise<FFIDRedirectResult>;
351
400
  getLoginUrl: (redirectUrl?: string) => string;
352
401
  getSignupUrl: (redirectUrl?: string) => string;
353
402
  createError: (code: string, message: string) => FFIDError;
@@ -360,6 +409,17 @@ declare function createFFIDClient(config: FFIDConfig): {
360
409
  createCheckoutSession: (params: FFIDCreateCheckoutParams) => Promise<FFIDApiResponse<FFIDCheckoutSessionResponse>>;
361
410
  createPortalSession: (params: FFIDCreatePortalParams) => Promise<FFIDApiResponse<FFIDPortalSessionResponse>>;
362
411
  verifyAccessToken: (accessToken: string, options?: FFIDVerifyAccessTokenOptions) => Promise<FFIDApiResponse<FFIDOAuthUserInfo>>;
412
+ requestPasswordReset: (email: string) => Promise<FFIDApiResponse<FFIDPasswordResetResponse>>;
413
+ verifyPasswordResetToken: (accessToken: string) => Promise<FFIDApiResponse<FFIDPasswordResetVerifyResponse>>;
414
+ establishResetSession: (accessToken: string, refreshToken: string) => Promise<FFIDApiResponse<FFIDResetSessionResponse>>;
415
+ confirmPasswordReset: (password: string) => Promise<FFIDApiResponse<FFIDPasswordResetConfirmResponse>>;
416
+ sendOtp: (email: string, options?: {
417
+ redirectUrl?: string;
418
+ }) => Promise<FFIDApiResponse<FFIDOtpSendResponse>>;
419
+ verifyOtp: (params: {
420
+ accessToken: string;
421
+ refreshToken: string;
422
+ }) => Promise<FFIDApiResponse<FFIDOtpVerifyResponse>>;
363
423
  /** Token store (token mode only) */
364
424
  tokenStore: TokenStore;
365
425
  /** Resolved auth mode */
@@ -428,4 +488,4 @@ interface KVNamespaceLike {
428
488
  */
429
489
  declare function createKVCacheAdapter(kv: KVNamespaceLike): FFIDCacheAdapter;
430
490
 
431
- export { type FFIDCacheAdapter, type FFIDCacheConfig, type FFIDClient, type FFIDConfig, type FFIDOAuthUserInfo, type FFIDOrganization, type FFIDSubscription, type FFIDUser, type FFIDVerifyAccessTokenOptions, type KVNamespaceLike, type TokenData, type TokenStore, createFFIDClient, createKVCacheAdapter, createMemoryCacheAdapter, createTokenStore, createVerifyAccessToken };
491
+ export { type FFIDCacheAdapter, type FFIDCacheConfig, type FFIDClient, type FFIDConfig, type FFIDOAuthUserInfo, type FFIDOrganization, type FFIDOtpSendResponse, type FFIDOtpVerifyResponse, type FFIDPasswordResetConfirmResponse, type FFIDPasswordResetResponse, type FFIDPasswordResetVerifyResponse, type FFIDResetSessionResponse, type FFIDSubscription, type FFIDUser, type FFIDVerifyAccessTokenOptions, type KVNamespaceLike, type TokenData, type TokenStore, createFFIDClient, createKVCacheAdapter, createMemoryCacheAdapter, createTokenStore, createVerifyAccessToken };
@@ -301,6 +301,55 @@ interface FFIDCreatePortalParams {
301
301
  /** URL to redirect when user exits the portal */
302
302
  returnUrl: string;
303
303
  }
304
+ /**
305
+ * Result of a redirect operation (redirectToLogin / redirectToAuthorize)
306
+ *
307
+ * Structured return type so callers can inspect failure reasons
308
+ * instead of receiving a bare `false`.
309
+ */
310
+ type FFIDRedirectResult = {
311
+ success: true;
312
+ } | {
313
+ success: false;
314
+ error: string;
315
+ };
316
+
317
+ /** OTP / magic link methods - sendOtp / verifyOtp */
318
+
319
+ /** Response from sendOtp */
320
+ interface FFIDOtpSendResponse {
321
+ message: string;
322
+ }
323
+ /** Response from verifyOtp */
324
+ interface FFIDOtpVerifyResponse {
325
+ user: {
326
+ id: string;
327
+ email: string;
328
+ displayName: string | null;
329
+ avatarUrl: string | null;
330
+ };
331
+ session: {
332
+ accessToken: string;
333
+ refreshToken: string;
334
+ expiresAt: number;
335
+ expiresIn: number;
336
+ };
337
+ }
338
+
339
+ /** Password reset methods - requestPasswordReset / verifyPasswordResetToken / establishResetSession / confirmPasswordReset */
340
+
341
+ /** Response from requestPasswordReset */
342
+ interface FFIDPasswordResetResponse {
343
+ message: string;
344
+ }
345
+ /** Response from verifyPasswordResetToken */
346
+ interface FFIDPasswordResetVerifyResponse {
347
+ valid: boolean;
348
+ }
349
+ /** Response from establishResetSession */
350
+ type FFIDResetSessionResponse = FFIDPasswordResetResponse;
351
+ /** Response from confirmPasswordReset */
352
+ type FFIDPasswordResetConfirmResponse = FFIDPasswordResetResponse;
304
353
 
305
354
  /**
306
355
  * Token Store
@@ -347,7 +396,7 @@ declare function createTokenStore(storageType?: 'localStorage' | 'memory'): Toke
347
396
  declare function createFFIDClient(config: FFIDConfig): {
348
397
  getSession: () => Promise<FFIDApiResponse<FFIDSessionResponse>>;
349
398
  signOut: () => Promise<FFIDApiResponse<void>>;
350
- redirectToLogin: () => Promise<boolean>;
399
+ redirectToLogin: () => Promise<FFIDRedirectResult>;
351
400
  getLoginUrl: (redirectUrl?: string) => string;
352
401
  getSignupUrl: (redirectUrl?: string) => string;
353
402
  createError: (code: string, message: string) => FFIDError;
@@ -360,6 +409,17 @@ declare function createFFIDClient(config: FFIDConfig): {
360
409
  createCheckoutSession: (params: FFIDCreateCheckoutParams) => Promise<FFIDApiResponse<FFIDCheckoutSessionResponse>>;
361
410
  createPortalSession: (params: FFIDCreatePortalParams) => Promise<FFIDApiResponse<FFIDPortalSessionResponse>>;
362
411
  verifyAccessToken: (accessToken: string, options?: FFIDVerifyAccessTokenOptions) => Promise<FFIDApiResponse<FFIDOAuthUserInfo>>;
412
+ requestPasswordReset: (email: string) => Promise<FFIDApiResponse<FFIDPasswordResetResponse>>;
413
+ verifyPasswordResetToken: (accessToken: string) => Promise<FFIDApiResponse<FFIDPasswordResetVerifyResponse>>;
414
+ establishResetSession: (accessToken: string, refreshToken: string) => Promise<FFIDApiResponse<FFIDResetSessionResponse>>;
415
+ confirmPasswordReset: (password: string) => Promise<FFIDApiResponse<FFIDPasswordResetConfirmResponse>>;
416
+ sendOtp: (email: string, options?: {
417
+ redirectUrl?: string;
418
+ }) => Promise<FFIDApiResponse<FFIDOtpSendResponse>>;
419
+ verifyOtp: (params: {
420
+ accessToken: string;
421
+ refreshToken: string;
422
+ }) => Promise<FFIDApiResponse<FFIDOtpVerifyResponse>>;
363
423
  /** Token store (token mode only) */
364
424
  tokenStore: TokenStore;
365
425
  /** Resolved auth mode */
@@ -428,4 +488,4 @@ interface KVNamespaceLike {
428
488
  */
429
489
  declare function createKVCacheAdapter(kv: KVNamespaceLike): FFIDCacheAdapter;
430
490
 
431
- export { type FFIDCacheAdapter, type FFIDCacheConfig, type FFIDClient, type FFIDConfig, type FFIDOAuthUserInfo, type FFIDOrganization, type FFIDSubscription, type FFIDUser, type FFIDVerifyAccessTokenOptions, type KVNamespaceLike, type TokenData, type TokenStore, createFFIDClient, createKVCacheAdapter, createMemoryCacheAdapter, createTokenStore, createVerifyAccessToken };
491
+ export { type FFIDCacheAdapter, type FFIDCacheConfig, type FFIDClient, type FFIDConfig, type FFIDOAuthUserInfo, type FFIDOrganization, type FFIDOtpSendResponse, type FFIDOtpVerifyResponse, type FFIDPasswordResetConfirmResponse, type FFIDPasswordResetResponse, type FFIDPasswordResetVerifyResponse, type FFIDResetSessionResponse, type FFIDSubscription, type FFIDUser, type FFIDVerifyAccessTokenOptions, type KVNamespaceLike, type TokenData, type TokenStore, createFFIDClient, createKVCacheAdapter, createMemoryCacheAdapter, createTokenStore, createVerifyAccessToken };