@feelflow/ffid-sdk 5.12.0 → 5.14.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.
@@ -119,6 +119,9 @@ function normalizeUserinfo(raw) {
119
119
  return {
120
120
  sub: raw.sub,
121
121
  email: raw.email,
122
+ // #3791: undefined → undefined を保つ(false に丸めない)。古い FFID は wire に
123
+ // 含めないため、consumer は undefined を「未確認」ではなく「不明」として扱える。
124
+ emailVerified: raw.email_verified,
122
125
  name: raw.name,
123
126
  picture: raw.picture,
124
127
  companyName: raw.company_name ?? null,
@@ -374,6 +377,9 @@ function createVerifyAccessToken(deps) {
374
377
  const base = {
375
378
  sub: introspectResponse.sub,
376
379
  email: introspectResponse.email ?? null,
380
+ // #3791: preserve undefined (do NOT coerce to false) so introspect-strategy
381
+ // callers can distinguish "verified" / "not verified" / "older server".
382
+ email_verified: introspectResponse.email_verified,
377
383
  name: introspectResponse.name ?? null,
378
384
  picture: introspectResponse.picture ?? null,
379
385
  company_name: introspectResponse.company_name ?? null,
@@ -928,8 +934,163 @@ function createProfileMethods(deps) {
928
934
  return { getProfile, updateProfile };
929
935
  }
930
936
 
937
+ // src/client/login-history-methods.ts
938
+ var EXT_LOGIN_HISTORY_ENDPOINT = "/api/v1/users/ext/me/login-history";
939
+ function createLoginHistoryMethods(deps) {
940
+ const { fetchWithAuth, createError } = deps;
941
+ async function getLoginHistory(params) {
942
+ if (params?.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit <= 0)) {
943
+ return {
944
+ error: createError(
945
+ "VALIDATION_ERROR",
946
+ "limit \u306F1\u4EE5\u4E0A\u306E\u6574\u6570\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
947
+ )
948
+ };
949
+ }
950
+ const query = new URLSearchParams();
951
+ if (params?.userId !== void 0) {
952
+ query.set("userId", params.userId);
953
+ }
954
+ if (params?.limit !== void 0) {
955
+ query.set("limit", String(params.limit));
956
+ }
957
+ const queryString = query.toString();
958
+ const endpoint = queryString ? `${EXT_LOGIN_HISTORY_ENDPOINT}?${queryString}` : EXT_LOGIN_HISTORY_ENDPOINT;
959
+ return fetchWithAuth(endpoint);
960
+ }
961
+ return { getLoginHistory };
962
+ }
963
+
964
+ // src/client/organization-domains-methods.ts
965
+ var EXT_DOMAINS_ENDPOINT = "/api/v1/organizations/ext/domains";
966
+ function createOrganizationDomainsMethods(deps) {
967
+ const { fetchWithAuth, createError, serviceCode } = deps;
968
+ function buildQuery(organizationId) {
969
+ return new URLSearchParams({ organizationId, serviceCode }).toString();
970
+ }
971
+ async function getOrganizationDomains(params) {
972
+ if (!params.organizationId) {
973
+ return {
974
+ error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
975
+ };
976
+ }
977
+ return fetchWithAuth(
978
+ `${EXT_DOMAINS_ENDPOINT}?${buildQuery(params.organizationId)}`
979
+ );
980
+ }
981
+ return { getOrganizationDomains };
982
+ }
983
+
984
+ // src/client/notification-preferences-methods.ts
985
+ var EXT_NOTIFICATION_PREFERENCES_ENDPOINT = "/api/v1/organizations/ext/notification-preferences";
986
+ function createNotificationPreferencesMethods(deps) {
987
+ const { fetchWithAuth, createError, serviceCode } = deps;
988
+ function buildQuery(organizationId) {
989
+ return new URLSearchParams({ organizationId, serviceCode }).toString();
990
+ }
991
+ async function getNotificationPreferences(params) {
992
+ if (!params.organizationId) {
993
+ return {
994
+ error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
995
+ };
996
+ }
997
+ return fetchWithAuth(
998
+ `${EXT_NOTIFICATION_PREFERENCES_ENDPOINT}?${buildQuery(params.organizationId)}`
999
+ );
1000
+ }
1001
+ async function updateNotificationPreferences(params) {
1002
+ if (!params.organizationId) {
1003
+ return {
1004
+ error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
1005
+ };
1006
+ }
1007
+ if (params.preferences === null || typeof params.preferences !== "object" || Array.isArray(params.preferences)) {
1008
+ return {
1009
+ error: createError("VALIDATION_ERROR", "preferences \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059")
1010
+ };
1011
+ }
1012
+ if (Object.keys(params.preferences).length === 0) {
1013
+ return {
1014
+ error: createError("VALIDATION_ERROR", "\u66F4\u65B0\u3059\u308B\u30D5\u30A3\u30FC\u30EB\u30C9\u30921\u3064\u4EE5\u4E0A\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044")
1015
+ };
1016
+ }
1017
+ return fetchWithAuth(
1018
+ `${EXT_NOTIFICATION_PREFERENCES_ENDPOINT}?${buildQuery(params.organizationId)}`,
1019
+ {
1020
+ method: "PATCH",
1021
+ body: JSON.stringify({ preferences: params.preferences })
1022
+ }
1023
+ );
1024
+ }
1025
+ return { getNotificationPreferences, updateNotificationPreferences };
1026
+ }
1027
+
1028
+ // src/client/invite-methods.ts
1029
+ var EXT_INVITE_ENDPOINT = "/api/v1/organizations/ext/invite";
1030
+ function createInviteMethods(deps) {
1031
+ const { fetchWithAuth, createError, serviceCode } = deps;
1032
+ const assignableRoles = /* @__PURE__ */ new Set(["admin", "member", "viewer"]);
1033
+ async function inviteMember(params) {
1034
+ if (!params.organizationId || !params.email) {
1035
+ return {
1036
+ error: createError("VALIDATION_ERROR", "organizationId \u3068 email \u306F\u5FC5\u9808\u3067\u3059")
1037
+ };
1038
+ }
1039
+ if (params.role !== void 0 && !assignableRoles.has(params.role)) {
1040
+ return {
1041
+ error: createError(
1042
+ "VALIDATION_ERROR",
1043
+ "role \u306F admin\u3001member\u3001viewer \u306E\u3044\u305A\u308C\u304B\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
1044
+ )
1045
+ };
1046
+ }
1047
+ const query = new URLSearchParams({
1048
+ organizationId: params.organizationId,
1049
+ serviceCode
1050
+ }).toString();
1051
+ return fetchWithAuth(`${EXT_INVITE_ENDPOINT}?${query}`, {
1052
+ method: "POST",
1053
+ body: JSON.stringify({ email: params.email, role: params.role })
1054
+ });
1055
+ }
1056
+ return { inviteMember };
1057
+ }
1058
+
1059
+ // src/client/org-membership-methods.ts
1060
+ var EXT_USER_ORGANIZATIONS_ENDPOINT = "/api/v1/users/ext/me/organizations";
1061
+ function createOrgMembershipMethods(deps) {
1062
+ const { fetchWithAuth, createError } = deps;
1063
+ async function leaveOrganization(params) {
1064
+ if (!params || !params.organizationId) {
1065
+ return {
1066
+ error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
1067
+ };
1068
+ }
1069
+ let endpoint = `${EXT_USER_ORGANIZATIONS_ENDPOINT}/${encodeURIComponent(params.organizationId)}`;
1070
+ if (params.userId) {
1071
+ endpoint += `?${new URLSearchParams({ userId: params.userId }).toString()}`;
1072
+ }
1073
+ return fetchWithAuth(endpoint, {
1074
+ method: "DELETE"
1075
+ });
1076
+ }
1077
+ return { leaveOrganization };
1078
+ }
1079
+
1080
+ // src/client/noncontract-methods.ts
1081
+ function createNonContractMethods(deps) {
1082
+ const { fetchWithAuth, createError, serviceCode } = deps;
1083
+ return {
1084
+ ...createLoginHistoryMethods({ fetchWithAuth, createError }),
1085
+ ...createOrganizationDomainsMethods({ fetchWithAuth, createError, serviceCode }),
1086
+ ...createNotificationPreferencesMethods({ fetchWithAuth, createError, serviceCode }),
1087
+ ...createInviteMethods({ fetchWithAuth, createError, serviceCode }),
1088
+ ...createOrgMembershipMethods({ fetchWithAuth, createError })
1089
+ };
1090
+ }
1091
+
931
1092
  // src/client/version-check.ts
932
- var SDK_VERSION = "5.12.0";
1093
+ var SDK_VERSION = "5.14.0";
933
1094
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
934
1095
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
935
1096
  function sdkHeaders() {
@@ -1001,9 +1162,20 @@ function createOAuthTokenMethods(deps) {
1001
1162
  logger,
1002
1163
  errorCodes
1003
1164
  } = deps;
1004
- async function exchangeCodeForTokens(code, codeVerifier) {
1165
+ async function exchangeCodeForTokens(code, codeVerifier, opts) {
1005
1166
  const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
1006
1167
  logger.debug("Exchanging code for tokens:", url);
1168
+ if (opts?.expectedState !== void 0 || opts?.actualState !== void 0) {
1169
+ if (opts?.expectedState !== opts?.actualState) {
1170
+ logger.error("Token exchange aborted: OAuth state mismatch or omitted (possible CSRF)");
1171
+ return {
1172
+ error: {
1173
+ code: errorCodes.STATE_MISMATCH_ERROR,
1174
+ message: "OAuth state \u304C\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002CSRF \u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081\u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
1175
+ }
1176
+ };
1177
+ }
1178
+ }
1007
1179
  const effectiveRedirectUri = resolvedRedirectUri ?? (typeof window !== "undefined" ? window.location.origin + window.location.pathname : null);
1008
1180
  if (!effectiveRedirectUri) {
1009
1181
  logger.error("redirectUri is required for token exchange in SSR environments. Set config.redirectUri explicitly.");
@@ -1448,6 +1620,46 @@ function base64UrlEncode(buffer) {
1448
1620
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1449
1621
  }
1450
1622
 
1623
+ // src/auth/state.ts
1624
+ var STATE_STORAGE_KEY = "ffid_oauth_state";
1625
+ var STATE_FALLBACK_STORAGE_KEY = "ffid_oauth_state_fb";
1626
+ var STATE_FALLBACK_TIMESTAMP_KEY = "ffid_oauth_state_fb_ts";
1627
+ function storeState(state, logger) {
1628
+ if (typeof window === "undefined") {
1629
+ logger?.warn("storeState: storage is not available in SSR context");
1630
+ return false;
1631
+ }
1632
+ let sessionStored = false;
1633
+ try {
1634
+ window.sessionStorage.setItem(STATE_STORAGE_KEY, state);
1635
+ sessionStored = true;
1636
+ } catch (error) {
1637
+ logger?.warn("storeState: sessionStorage \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1638
+ }
1639
+ let localStored = false;
1640
+ try {
1641
+ window.localStorage.setItem(STATE_FALLBACK_STORAGE_KEY, state);
1642
+ window.localStorage.setItem(STATE_FALLBACK_TIMESTAMP_KEY, String(Date.now()));
1643
+ localStored = true;
1644
+ } catch (error) {
1645
+ logger?.warn("storeState: localStorage fallback \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1646
+ }
1647
+ return sessionStored || localStored;
1648
+ }
1649
+ function cleanupStateStorage(logger) {
1650
+ try {
1651
+ window.sessionStorage.removeItem(STATE_STORAGE_KEY);
1652
+ } catch (error) {
1653
+ logger?.warn("retrieveState: sessionStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1654
+ }
1655
+ try {
1656
+ window.localStorage.removeItem(STATE_FALLBACK_STORAGE_KEY);
1657
+ window.localStorage.removeItem(STATE_FALLBACK_TIMESTAMP_KEY);
1658
+ } catch (error) {
1659
+ logger?.warn("retrieveState: localStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1660
+ }
1661
+ }
1662
+
1451
1663
  // src/client/redirect.ts
1452
1664
  var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
1453
1665
  var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
@@ -1579,6 +1791,7 @@ function createRedirectMethods(deps) {
1579
1791
  const recentCount = getRecentRedirectCount(authorizeKey, now, logger);
1580
1792
  if (recentCount >= REDIRECT_LOOP_THRESHOLD) {
1581
1793
  cleanupVerifierStorage(logger);
1794
+ cleanupStateStorage(logger);
1582
1795
  logger.warn("[FFID SDK] redirect loop detected \u2014 \u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F", {
1583
1796
  authorizeKey,
1584
1797
  recentCount,
@@ -1602,6 +1815,11 @@ function createRedirectMethods(deps) {
1602
1815
  return { success: false, error: errorMessage };
1603
1816
  }
1604
1817
  const state = generateRandomState();
1818
+ if (!storeState(state, logger)) {
1819
+ logger.error(
1820
+ "redirectToAuthorize: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u3067\u306E CSRF \u691C\u8A3C\u304C\u5931\u6557\u3057\u307E\u3059"
1821
+ );
1822
+ }
1605
1823
  const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
1606
1824
  const params = new URLSearchParams({
1607
1825
  response_type: "code",
@@ -2316,7 +2534,8 @@ var FFID_ERROR_CODES = {
2316
2534
  TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
2317
2535
  TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
2318
2536
  NO_TOKENS: "NO_TOKENS",
2319
- TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
2537
+ TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR",
2538
+ STATE_MISMATCH_ERROR: "STATE_MISMATCH_ERROR"
2320
2539
  };
2321
2540
  var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
2322
2541
  var DEFAULT_ALLOW_GRACE = true;
@@ -2704,6 +2923,18 @@ function createFFIDClient(config) {
2704
2923
  fetchWithAuth,
2705
2924
  createError
2706
2925
  });
2926
+ const {
2927
+ getLoginHistory,
2928
+ getOrganizationDomains,
2929
+ getNotificationPreferences,
2930
+ updateNotificationPreferences,
2931
+ inviteMember,
2932
+ leaveOrganization
2933
+ } = createNonContractMethods({
2934
+ fetchWithAuth,
2935
+ createError,
2936
+ serviceCode: config.serviceCode
2937
+ });
2707
2938
  const { getAnalyticsConfig } = createAnalyticsMethods({
2708
2939
  fetchWithAuth,
2709
2940
  createError
@@ -2784,6 +3015,13 @@ function createFFIDClient(config) {
2784
3015
  removeMember,
2785
3016
  getProfile,
2786
3017
  updateProfile,
3018
+ // Non-contract ext API coverage (#3783)
3019
+ getLoginHistory,
3020
+ getOrganizationDomains,
3021
+ getNotificationPreferences,
3022
+ updateNotificationPreferences,
3023
+ inviteMember,
3024
+ leaveOrganization,
2787
3025
  getAnalyticsConfig,
2788
3026
  createCheckoutSession,
2789
3027
  createPortalSession,
@@ -1,5 +1,5 @@
1
- import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo } from '../ffid-client-Dx4oc4Cw.cjs';
2
- export { f as FFIDAddMemberParams, g as FFIDAddMemberRequest, h as FFIDAddMemberResponse, i as FFIDAssignableMemberRole, j as FFIDCacheConfig, k as FFIDClient, l as FFIDConfig, m as FFIDListMembersResponse, n as FFIDMemberRole, o as FFIDOrganization, p as FFIDOrganizationMember, q as FFIDOtpSendResponse, r as FFIDOtpVerifyResponse, s as FFIDPasswordResetConfirmResponse, t as FFIDPasswordResetResponse, u as FFIDPasswordResetVerifyResponse, v as FFIDProfileCallOptions, w as FFIDRemoveMemberResponse, x as FFIDResetSessionResponse, y as FFIDSubscription, z as FFIDUpdateMemberRoleResponse, A as FFIDUpdateUserProfileRequest, B as FFIDUser, C as FFIDUserProfile, T as TokenData, D as TokenStore, E as createFFIDClient, G as createTokenStore } from '../ffid-client-Dx4oc4Cw.cjs';
1
+ import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo } from '../ffid-client-CKFxY60u.cjs';
2
+ export { f as FFIDAddMemberParams, g as FFIDAddMemberRequest, h as FFIDAddMemberResponse, i as FFIDAssignableMemberRole, j as FFIDCacheConfig, k as FFIDClient, l as FFIDConfig, m as FFIDListMembersResponse, n as FFIDMemberRole, o as FFIDOrganization, p as FFIDOrganizationMember, q as FFIDOtpSendResponse, r as FFIDOtpVerifyResponse, s as FFIDPasswordResetConfirmResponse, t as FFIDPasswordResetResponse, u as FFIDPasswordResetVerifyResponse, v as FFIDProfileCallOptions, w as FFIDRemoveMemberResponse, x as FFIDResetSessionResponse, y as FFIDSubscription, z as FFIDUpdateMemberRoleResponse, A as FFIDUpdateUserProfileRequest, B as FFIDUser, C as FFIDUserProfile, T as TokenData, D as TokenStore, E as createFFIDClient, G as createTokenStore } from '../ffid-client-CKFxY60u.cjs';
3
3
  export { D as DEFAULT_API_BASE_URL, a as DEFAULT_OAUTH_SCOPES } from '../constants-D61jqRIO.cjs';
4
4
  import { z } from 'zod';
5
5
  import '../types-BeVl-z12.cjs';
@@ -1,5 +1,5 @@
1
- import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo } from '../ffid-client-5IprSUVR.js';
2
- export { f as FFIDAddMemberParams, g as FFIDAddMemberRequest, h as FFIDAddMemberResponse, i as FFIDAssignableMemberRole, j as FFIDCacheConfig, k as FFIDClient, l as FFIDConfig, m as FFIDListMembersResponse, n as FFIDMemberRole, o as FFIDOrganization, p as FFIDOrganizationMember, q as FFIDOtpSendResponse, r as FFIDOtpVerifyResponse, s as FFIDPasswordResetConfirmResponse, t as FFIDPasswordResetResponse, u as FFIDPasswordResetVerifyResponse, v as FFIDProfileCallOptions, w as FFIDRemoveMemberResponse, x as FFIDResetSessionResponse, y as FFIDSubscription, z as FFIDUpdateMemberRoleResponse, A as FFIDUpdateUserProfileRequest, B as FFIDUser, C as FFIDUserProfile, T as TokenData, D as TokenStore, E as createFFIDClient, G as createTokenStore } from '../ffid-client-5IprSUVR.js';
1
+ import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo } from '../ffid-client-9-lh0G_h.js';
2
+ export { f as FFIDAddMemberParams, g as FFIDAddMemberRequest, h as FFIDAddMemberResponse, i as FFIDAssignableMemberRole, j as FFIDCacheConfig, k as FFIDClient, l as FFIDConfig, m as FFIDListMembersResponse, n as FFIDMemberRole, o as FFIDOrganization, p as FFIDOrganizationMember, q as FFIDOtpSendResponse, r as FFIDOtpVerifyResponse, s as FFIDPasswordResetConfirmResponse, t as FFIDPasswordResetResponse, u as FFIDPasswordResetVerifyResponse, v as FFIDProfileCallOptions, w as FFIDRemoveMemberResponse, x as FFIDResetSessionResponse, y as FFIDSubscription, z as FFIDUpdateMemberRoleResponse, A as FFIDUpdateUserProfileRequest, B as FFIDUser, C as FFIDUserProfile, T as TokenData, D as TokenStore, E as createFFIDClient, G as createTokenStore } from '../ffid-client-9-lh0G_h.js';
3
3
  export { D as DEFAULT_API_BASE_URL, a as DEFAULT_OAUTH_SCOPES } from '../constants-D61jqRIO.js';
4
4
  import { z } from 'zod';
5
5
  import '../types-BeVl-z12.js';
@@ -118,6 +118,9 @@ function normalizeUserinfo(raw) {
118
118
  return {
119
119
  sub: raw.sub,
120
120
  email: raw.email,
121
+ // #3791: undefined → undefined を保つ(false に丸めない)。古い FFID は wire に
122
+ // 含めないため、consumer は undefined を「未確認」ではなく「不明」として扱える。
123
+ emailVerified: raw.email_verified,
121
124
  name: raw.name,
122
125
  picture: raw.picture,
123
126
  companyName: raw.company_name ?? null,
@@ -373,6 +376,9 @@ function createVerifyAccessToken(deps) {
373
376
  const base = {
374
377
  sub: introspectResponse.sub,
375
378
  email: introspectResponse.email ?? null,
379
+ // #3791: preserve undefined (do NOT coerce to false) so introspect-strategy
380
+ // callers can distinguish "verified" / "not verified" / "older server".
381
+ email_verified: introspectResponse.email_verified,
376
382
  name: introspectResponse.name ?? null,
377
383
  picture: introspectResponse.picture ?? null,
378
384
  company_name: introspectResponse.company_name ?? null,
@@ -927,8 +933,163 @@ function createProfileMethods(deps) {
927
933
  return { getProfile, updateProfile };
928
934
  }
929
935
 
936
+ // src/client/login-history-methods.ts
937
+ var EXT_LOGIN_HISTORY_ENDPOINT = "/api/v1/users/ext/me/login-history";
938
+ function createLoginHistoryMethods(deps) {
939
+ const { fetchWithAuth, createError } = deps;
940
+ async function getLoginHistory(params) {
941
+ if (params?.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit <= 0)) {
942
+ return {
943
+ error: createError(
944
+ "VALIDATION_ERROR",
945
+ "limit \u306F1\u4EE5\u4E0A\u306E\u6574\u6570\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
946
+ )
947
+ };
948
+ }
949
+ const query = new URLSearchParams();
950
+ if (params?.userId !== void 0) {
951
+ query.set("userId", params.userId);
952
+ }
953
+ if (params?.limit !== void 0) {
954
+ query.set("limit", String(params.limit));
955
+ }
956
+ const queryString = query.toString();
957
+ const endpoint = queryString ? `${EXT_LOGIN_HISTORY_ENDPOINT}?${queryString}` : EXT_LOGIN_HISTORY_ENDPOINT;
958
+ return fetchWithAuth(endpoint);
959
+ }
960
+ return { getLoginHistory };
961
+ }
962
+
963
+ // src/client/organization-domains-methods.ts
964
+ var EXT_DOMAINS_ENDPOINT = "/api/v1/organizations/ext/domains";
965
+ function createOrganizationDomainsMethods(deps) {
966
+ const { fetchWithAuth, createError, serviceCode } = deps;
967
+ function buildQuery(organizationId) {
968
+ return new URLSearchParams({ organizationId, serviceCode }).toString();
969
+ }
970
+ async function getOrganizationDomains(params) {
971
+ if (!params.organizationId) {
972
+ return {
973
+ error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
974
+ };
975
+ }
976
+ return fetchWithAuth(
977
+ `${EXT_DOMAINS_ENDPOINT}?${buildQuery(params.organizationId)}`
978
+ );
979
+ }
980
+ return { getOrganizationDomains };
981
+ }
982
+
983
+ // src/client/notification-preferences-methods.ts
984
+ var EXT_NOTIFICATION_PREFERENCES_ENDPOINT = "/api/v1/organizations/ext/notification-preferences";
985
+ function createNotificationPreferencesMethods(deps) {
986
+ const { fetchWithAuth, createError, serviceCode } = deps;
987
+ function buildQuery(organizationId) {
988
+ return new URLSearchParams({ organizationId, serviceCode }).toString();
989
+ }
990
+ async function getNotificationPreferences(params) {
991
+ if (!params.organizationId) {
992
+ return {
993
+ error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
994
+ };
995
+ }
996
+ return fetchWithAuth(
997
+ `${EXT_NOTIFICATION_PREFERENCES_ENDPOINT}?${buildQuery(params.organizationId)}`
998
+ );
999
+ }
1000
+ async function updateNotificationPreferences(params) {
1001
+ if (!params.organizationId) {
1002
+ return {
1003
+ error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
1004
+ };
1005
+ }
1006
+ if (params.preferences === null || typeof params.preferences !== "object" || Array.isArray(params.preferences)) {
1007
+ return {
1008
+ error: createError("VALIDATION_ERROR", "preferences \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059")
1009
+ };
1010
+ }
1011
+ if (Object.keys(params.preferences).length === 0) {
1012
+ return {
1013
+ error: createError("VALIDATION_ERROR", "\u66F4\u65B0\u3059\u308B\u30D5\u30A3\u30FC\u30EB\u30C9\u30921\u3064\u4EE5\u4E0A\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044")
1014
+ };
1015
+ }
1016
+ return fetchWithAuth(
1017
+ `${EXT_NOTIFICATION_PREFERENCES_ENDPOINT}?${buildQuery(params.organizationId)}`,
1018
+ {
1019
+ method: "PATCH",
1020
+ body: JSON.stringify({ preferences: params.preferences })
1021
+ }
1022
+ );
1023
+ }
1024
+ return { getNotificationPreferences, updateNotificationPreferences };
1025
+ }
1026
+
1027
+ // src/client/invite-methods.ts
1028
+ var EXT_INVITE_ENDPOINT = "/api/v1/organizations/ext/invite";
1029
+ function createInviteMethods(deps) {
1030
+ const { fetchWithAuth, createError, serviceCode } = deps;
1031
+ const assignableRoles = /* @__PURE__ */ new Set(["admin", "member", "viewer"]);
1032
+ async function inviteMember(params) {
1033
+ if (!params.organizationId || !params.email) {
1034
+ return {
1035
+ error: createError("VALIDATION_ERROR", "organizationId \u3068 email \u306F\u5FC5\u9808\u3067\u3059")
1036
+ };
1037
+ }
1038
+ if (params.role !== void 0 && !assignableRoles.has(params.role)) {
1039
+ return {
1040
+ error: createError(
1041
+ "VALIDATION_ERROR",
1042
+ "role \u306F admin\u3001member\u3001viewer \u306E\u3044\u305A\u308C\u304B\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
1043
+ )
1044
+ };
1045
+ }
1046
+ const query = new URLSearchParams({
1047
+ organizationId: params.organizationId,
1048
+ serviceCode
1049
+ }).toString();
1050
+ return fetchWithAuth(`${EXT_INVITE_ENDPOINT}?${query}`, {
1051
+ method: "POST",
1052
+ body: JSON.stringify({ email: params.email, role: params.role })
1053
+ });
1054
+ }
1055
+ return { inviteMember };
1056
+ }
1057
+
1058
+ // src/client/org-membership-methods.ts
1059
+ var EXT_USER_ORGANIZATIONS_ENDPOINT = "/api/v1/users/ext/me/organizations";
1060
+ function createOrgMembershipMethods(deps) {
1061
+ const { fetchWithAuth, createError } = deps;
1062
+ async function leaveOrganization(params) {
1063
+ if (!params || !params.organizationId) {
1064
+ return {
1065
+ error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
1066
+ };
1067
+ }
1068
+ let endpoint = `${EXT_USER_ORGANIZATIONS_ENDPOINT}/${encodeURIComponent(params.organizationId)}`;
1069
+ if (params.userId) {
1070
+ endpoint += `?${new URLSearchParams({ userId: params.userId }).toString()}`;
1071
+ }
1072
+ return fetchWithAuth(endpoint, {
1073
+ method: "DELETE"
1074
+ });
1075
+ }
1076
+ return { leaveOrganization };
1077
+ }
1078
+
1079
+ // src/client/noncontract-methods.ts
1080
+ function createNonContractMethods(deps) {
1081
+ const { fetchWithAuth, createError, serviceCode } = deps;
1082
+ return {
1083
+ ...createLoginHistoryMethods({ fetchWithAuth, createError }),
1084
+ ...createOrganizationDomainsMethods({ fetchWithAuth, createError, serviceCode }),
1085
+ ...createNotificationPreferencesMethods({ fetchWithAuth, createError, serviceCode }),
1086
+ ...createInviteMethods({ fetchWithAuth, createError, serviceCode }),
1087
+ ...createOrgMembershipMethods({ fetchWithAuth, createError })
1088
+ };
1089
+ }
1090
+
930
1091
  // src/client/version-check.ts
931
- var SDK_VERSION = "5.12.0";
1092
+ var SDK_VERSION = "5.14.0";
932
1093
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
933
1094
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
934
1095
  function sdkHeaders() {
@@ -1000,9 +1161,20 @@ function createOAuthTokenMethods(deps) {
1000
1161
  logger,
1001
1162
  errorCodes
1002
1163
  } = deps;
1003
- async function exchangeCodeForTokens(code, codeVerifier) {
1164
+ async function exchangeCodeForTokens(code, codeVerifier, opts) {
1004
1165
  const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
1005
1166
  logger.debug("Exchanging code for tokens:", url);
1167
+ if (opts?.expectedState !== void 0 || opts?.actualState !== void 0) {
1168
+ if (opts?.expectedState !== opts?.actualState) {
1169
+ logger.error("Token exchange aborted: OAuth state mismatch or omitted (possible CSRF)");
1170
+ return {
1171
+ error: {
1172
+ code: errorCodes.STATE_MISMATCH_ERROR,
1173
+ message: "OAuth state \u304C\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002CSRF \u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081\u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
1174
+ }
1175
+ };
1176
+ }
1177
+ }
1006
1178
  const effectiveRedirectUri = resolvedRedirectUri ?? (typeof window !== "undefined" ? window.location.origin + window.location.pathname : null);
1007
1179
  if (!effectiveRedirectUri) {
1008
1180
  logger.error("redirectUri is required for token exchange in SSR environments. Set config.redirectUri explicitly.");
@@ -1447,6 +1619,46 @@ function base64UrlEncode(buffer) {
1447
1619
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1448
1620
  }
1449
1621
 
1622
+ // src/auth/state.ts
1623
+ var STATE_STORAGE_KEY = "ffid_oauth_state";
1624
+ var STATE_FALLBACK_STORAGE_KEY = "ffid_oauth_state_fb";
1625
+ var STATE_FALLBACK_TIMESTAMP_KEY = "ffid_oauth_state_fb_ts";
1626
+ function storeState(state, logger) {
1627
+ if (typeof window === "undefined") {
1628
+ logger?.warn("storeState: storage is not available in SSR context");
1629
+ return false;
1630
+ }
1631
+ let sessionStored = false;
1632
+ try {
1633
+ window.sessionStorage.setItem(STATE_STORAGE_KEY, state);
1634
+ sessionStored = true;
1635
+ } catch (error) {
1636
+ logger?.warn("storeState: sessionStorage \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1637
+ }
1638
+ let localStored = false;
1639
+ try {
1640
+ window.localStorage.setItem(STATE_FALLBACK_STORAGE_KEY, state);
1641
+ window.localStorage.setItem(STATE_FALLBACK_TIMESTAMP_KEY, String(Date.now()));
1642
+ localStored = true;
1643
+ } catch (error) {
1644
+ logger?.warn("storeState: localStorage fallback \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1645
+ }
1646
+ return sessionStored || localStored;
1647
+ }
1648
+ function cleanupStateStorage(logger) {
1649
+ try {
1650
+ window.sessionStorage.removeItem(STATE_STORAGE_KEY);
1651
+ } catch (error) {
1652
+ logger?.warn("retrieveState: sessionStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1653
+ }
1654
+ try {
1655
+ window.localStorage.removeItem(STATE_FALLBACK_STORAGE_KEY);
1656
+ window.localStorage.removeItem(STATE_FALLBACK_TIMESTAMP_KEY);
1657
+ } catch (error) {
1658
+ logger?.warn("retrieveState: localStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1659
+ }
1660
+ }
1661
+
1450
1662
  // src/client/redirect.ts
1451
1663
  var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
1452
1664
  var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
@@ -1578,6 +1790,7 @@ function createRedirectMethods(deps) {
1578
1790
  const recentCount = getRecentRedirectCount(authorizeKey, now, logger);
1579
1791
  if (recentCount >= REDIRECT_LOOP_THRESHOLD) {
1580
1792
  cleanupVerifierStorage(logger);
1793
+ cleanupStateStorage(logger);
1581
1794
  logger.warn("[FFID SDK] redirect loop detected \u2014 \u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F", {
1582
1795
  authorizeKey,
1583
1796
  recentCount,
@@ -1601,6 +1814,11 @@ function createRedirectMethods(deps) {
1601
1814
  return { success: false, error: errorMessage };
1602
1815
  }
1603
1816
  const state = generateRandomState();
1817
+ if (!storeState(state, logger)) {
1818
+ logger.error(
1819
+ "redirectToAuthorize: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u3067\u306E CSRF \u691C\u8A3C\u304C\u5931\u6557\u3057\u307E\u3059"
1820
+ );
1821
+ }
1604
1822
  const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
1605
1823
  const params = new URLSearchParams({
1606
1824
  response_type: "code",
@@ -2315,7 +2533,8 @@ var FFID_ERROR_CODES = {
2315
2533
  TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
2316
2534
  TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
2317
2535
  NO_TOKENS: "NO_TOKENS",
2318
- TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
2536
+ TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR",
2537
+ STATE_MISMATCH_ERROR: "STATE_MISMATCH_ERROR"
2319
2538
  };
2320
2539
  var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
2321
2540
  var DEFAULT_ALLOW_GRACE = true;
@@ -2703,6 +2922,18 @@ function createFFIDClient(config) {
2703
2922
  fetchWithAuth,
2704
2923
  createError
2705
2924
  });
2925
+ const {
2926
+ getLoginHistory,
2927
+ getOrganizationDomains,
2928
+ getNotificationPreferences,
2929
+ updateNotificationPreferences,
2930
+ inviteMember,
2931
+ leaveOrganization
2932
+ } = createNonContractMethods({
2933
+ fetchWithAuth,
2934
+ createError,
2935
+ serviceCode: config.serviceCode
2936
+ });
2706
2937
  const { getAnalyticsConfig } = createAnalyticsMethods({
2707
2938
  fetchWithAuth,
2708
2939
  createError
@@ -2783,6 +3014,13 @@ function createFFIDClient(config) {
2783
3014
  removeMember,
2784
3015
  getProfile,
2785
3016
  updateProfile,
3017
+ // Non-contract ext API coverage (#3783)
3018
+ getLoginHistory,
3019
+ getOrganizationDomains,
3020
+ getNotificationPreferences,
3021
+ updateNotificationPreferences,
3022
+ inviteMember,
3023
+ leaveOrganization,
2786
3024
  getAnalyticsConfig,
2787
3025
  createCheckoutSession,
2788
3026
  createPortalSession,
@@ -1,4 +1,4 @@
1
- import { k as FFIDClient, e as FFIDOAuthUserInfo } from '../../ffid-client-Dx4oc4Cw.cjs';
1
+ import { k as FFIDClient, e as FFIDOAuthUserInfo } from '../../ffid-client-CKFxY60u.cjs';
2
2
  import '../../types-BeVl-z12.cjs';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { k as FFIDClient, e as FFIDOAuthUserInfo } from '../../ffid-client-5IprSUVR.js';
1
+ import { k as FFIDClient, e as FFIDOAuthUserInfo } from '../../ffid-client-9-lh0G_h.js';
2
2
  import '../../types-BeVl-z12.js';
3
3
 
4
4
  /**