@feelflow/ffid-sdk 2.12.0 → 2.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.
package/README.md CHANGED
@@ -228,6 +228,44 @@ const PremiumFeature = withSubscription(MyComponent, {
228
228
  })
229
229
  ```
230
230
 
231
+ ### getProfile() / updateProfile()
232
+
233
+ ログイン中ユーザー自身のプロフィールを取得・更新するメソッド(`createFFIDClient` から呼び出し)。
234
+
235
+ - エンドポイント: `GET /api/v1/users/ext/me` / `PUT /api/v1/users/ext/me`
236
+ - 対応 authMode: `token`(Bearer)のみが SDK 経由で成立する
237
+ - **cookie モードは非対応** — ext エンドポイントはクロスオリジン用途のため、Bearer token または `X-Service-Api-Key` のどちらかが必須。FFID 自身の UI(同一オリジン)は従来通り `/api/v1/users/me` を使う
238
+ - **service-key モードも SDK 経由では非対応** — API Key 認証時はバックエンドが `?userId=<uuid>` クエリを要求するが、`getProfile()` / `updateProfile()` は自分自身(ambient user)を前提とするため、`userId` を受け取らない。サーバー間で任意ユーザーのプロフィールを操作したい場合は `fetchWithAuth` で直接エンドポイントを叩いてください
239
+ - 外部サービス(hub 等)のフロントエンドから token モードで呼ぶのが想定される主要パターン
240
+
241
+ ```tsx
242
+ import { createFFIDClient } from '@feelflow/ffid-sdk'
243
+
244
+ const client = createFFIDClient({
245
+ serviceCode: 'hub',
246
+ authMode: 'token',
247
+ apiBaseUrl: 'https://id.feelflow.net',
248
+ })
249
+
250
+ // 取得
251
+ const { data: profile, error } = await client.getProfile()
252
+ if (error) {
253
+ console.error('プロフィール取得失敗:', error.message)
254
+ } else {
255
+ console.log(profile.email, profile.displayName, profile.timezone)
256
+ }
257
+
258
+ // 更新(部分更新 — 渡したフィールドだけ差し替え)
259
+ const { data: updated, error: updateError } = await client.updateProfile({
260
+ displayName: '山田 太郎',
261
+ timezone: 'Asia/Tokyo',
262
+ locale: 'ja',
263
+ preferences: { theme: 'dark' },
264
+ })
265
+ ```
266
+
267
+ `updateProfile` に空オブジェクト `{}` を渡すと `VALIDATION_ERROR` が返ります(無意味なラウンドトリップを防止)。
268
+
231
269
  ## 型定義
232
270
 
233
271
  ```typescript
@@ -758,8 +758,34 @@ function createMembersMethods(deps) {
758
758
  return { listMembers, updateMemberRole, removeMember };
759
759
  }
760
760
 
761
+ // src/client/profile-methods.ts
762
+ var EXT_PROFILE_ENDPOINT = "/api/v1/users/ext/me";
763
+ function createProfileMethods(deps) {
764
+ const { fetchWithAuth, createError } = deps;
765
+ async function getProfile() {
766
+ return fetchWithAuth(EXT_PROFILE_ENDPOINT);
767
+ }
768
+ async function updateProfile(data) {
769
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
770
+ return {
771
+ error: createError("VALIDATION_ERROR", "data \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059")
772
+ };
773
+ }
774
+ if (Object.keys(data).length === 0) {
775
+ return {
776
+ 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")
777
+ };
778
+ }
779
+ return fetchWithAuth(EXT_PROFILE_ENDPOINT, {
780
+ method: "PUT",
781
+ body: JSON.stringify(data)
782
+ });
783
+ }
784
+ return { getProfile, updateProfile };
785
+ }
786
+
761
787
  // src/client/version-check.ts
762
- var SDK_VERSION = "2.12.0";
788
+ var SDK_VERSION = "2.14.0";
763
789
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
764
790
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
765
791
  function sdkHeaders() {
@@ -1382,6 +1408,23 @@ function createRedirectMethods(deps) {
1382
1408
  return { redirectToLogin, redirectToAuthorize, getLoginUrl, getSignupUrl, getLogoutUrl, redirectToLogout };
1383
1409
  }
1384
1410
 
1411
+ // src/client/redirect-uri.ts
1412
+ var AUTHORITY_BOUNDARY = /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/?#]+)([/?#]?)/;
1413
+ function normalizeRedirectUri(input) {
1414
+ const url = new URL(input);
1415
+ const isRootPath = url.pathname === "" || url.pathname === "/";
1416
+ if (!isRootPath) {
1417
+ return { normalized: input, changed: false };
1418
+ }
1419
+ const match = input.match(AUTHORITY_BOUNDARY);
1420
+ if (match === null || match[2] === "/") {
1421
+ return { normalized: input, changed: false };
1422
+ }
1423
+ const authority = match[1];
1424
+ const rest = input.slice(authority.length);
1425
+ return { normalized: `${authority}/${rest}`, changed: true };
1426
+ }
1427
+
1385
1428
  // src/client/password-reset.ts
1386
1429
  var RESET_PASSWORD_BASE = "/api/v1/auth/reset-password";
1387
1430
  function isBlank(value) {
@@ -1892,6 +1935,24 @@ var FFID_ERROR_CODES = {
1892
1935
  TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
1893
1936
  };
1894
1937
  var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
1938
+ function resolveRedirectUri(raw, logger) {
1939
+ if (raw === null) return null;
1940
+ try {
1941
+ const { normalized, changed } = normalizeRedirectUri(raw);
1942
+ if (changed) {
1943
+ logger.warn(
1944
+ `FFID Client: redirect_uri \u3092\u6B63\u898F\u5316\u3057\u307E\u3057\u305F (${raw} \u2192 ${normalized})\u3002FFID \u7BA1\u7406\u753B\u9762\u3067\u306E\u767B\u9332\u5024\u3068\u4E00\u81F4\u3055\u305B\u3066\u304F\u3060\u3055\u3044\u3002`
1945
+ );
1946
+ }
1947
+ return normalized;
1948
+ } catch (error) {
1949
+ logger.warn(
1950
+ `FFID Client: redirectUri \u306E\u30D1\u30FC\u30B9\u306B\u5931\u6557\u3057\u305F\u305F\u3081\u6B63\u898F\u5316\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F (${raw})`,
1951
+ error
1952
+ );
1953
+ return raw;
1954
+ }
1955
+ }
1895
1956
  function createFFIDClient(config) {
1896
1957
  if (!config.serviceCode || !config.serviceCode.trim()) {
1897
1958
  throw new Error("FFID Client: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
@@ -1899,7 +1960,7 @@ function createFFIDClient(config) {
1899
1960
  const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
1900
1961
  const authMode = config.authMode ?? "cookie";
1901
1962
  const clientId = config.clientId ?? config.serviceCode;
1902
- const resolvedRedirectUri = config.redirectUri ?? null;
1963
+ const rawRedirectUri = config.redirectUri ?? null;
1903
1964
  const serviceApiKey = config.serviceApiKey?.trim();
1904
1965
  const verifyStrategy = config.verifyStrategy ?? "jwt";
1905
1966
  const cache = config.cache;
@@ -1914,6 +1975,7 @@ function createFFIDClient(config) {
1914
1975
  throw new Error("FFID Client: timeout \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
1915
1976
  }
1916
1977
  const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
1978
+ const resolvedRedirectUri = resolveRedirectUri(rawRedirectUri, logger);
1917
1979
  const tokenStore = authMode === "token" ? createTokenStore() : createTokenStore("memory");
1918
1980
  function createError(code, message) {
1919
1981
  return { code, message };
@@ -2107,6 +2169,10 @@ function createFFIDClient(config) {
2107
2169
  createError,
2108
2170
  serviceCode: config.serviceCode
2109
2171
  });
2172
+ const { getProfile, updateProfile } = createProfileMethods({
2173
+ fetchWithAuth,
2174
+ createError
2175
+ });
2110
2176
  const {
2111
2177
  requestPasswordReset,
2112
2178
  verifyPasswordResetToken,
@@ -2178,6 +2244,8 @@ function createFFIDClient(config) {
2178
2244
  listMembers,
2179
2245
  updateMemberRole,
2180
2246
  removeMember,
2247
+ getProfile,
2248
+ updateProfile,
2181
2249
  createCheckoutSession,
2182
2250
  createPortalSession,
2183
2251
  listPlans,
@@ -3562,8 +3630,34 @@ var FFID_INQUIRY_CATEGORIES = [
3562
3630
  "press",
3563
3631
  "other"
3564
3632
  ];
3633
+ var FFID_INQUIRY_CATEGORIES_SITE_2026 = [
3634
+ // サービス
3635
+ "consulting",
3636
+ "saas",
3637
+ "development",
3638
+ // プロダクト
3639
+ "agent-hub",
3640
+ "ai-feel-chatbot",
3641
+ "knowledge-db",
3642
+ "biz-simulator",
3643
+ "discussion-board",
3644
+ "realtime-ai",
3645
+ // その他
3646
+ "partnership",
3647
+ "media",
3648
+ "recruiting",
3649
+ "other"
3650
+ ];
3651
+ var isFFIDInquiryCategorySite2026 = (value) => FFID_INQUIRY_CATEGORIES_SITE_2026.includes(value);
3565
3652
  var CATEGORY_PLACEHOLDER_LABEL_JA = "\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044";
3566
3653
  var CATEGORY_REQUIRED_ERROR_JA = "\u304A\u554F\u3044\u5408\u308F\u305B\u7A2E\u5225\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
3654
+ var TERMS_REQUIRED_ERROR_JA = "\u5229\u7528\u898F\u7D04\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002";
3655
+ var PRIVACY_REQUIRED_ERROR_JA = "\u30D7\u30E9\u30A4\u30D0\u30B7\u30FC\u30DD\u30EA\u30B7\u30FC\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002";
3656
+ var TURNSTILE_PENDING_ERROR_JA = "\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u30C1\u30A7\u30C3\u30AF\u306E\u5B8C\u4E86\u5F8C\u306B\u9001\u4FE1\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
3657
+ function hasSatisfiedTurnstile(turnstileSlot, turnstileToken) {
3658
+ if (turnstileSlot == null) return true;
3659
+ return (turnstileToken ?? "").trim().length > 0;
3660
+ }
3567
3661
  var SUBMIT_BUTTON_OPACITY_PENDING = 0.6;
3568
3662
  var labelStyle = {
3569
3663
  display: "block",
@@ -3633,6 +3727,23 @@ function defaultCategories() {
3633
3727
  label: DEFAULT_CATEGORY_LABELS_JA[value]
3634
3728
  }));
3635
3729
  }
3730
+ function warnOnGroupLabelConflicts(options) {
3731
+ if (process.env.NODE_ENV === "production") return;
3732
+ const firstExplicitLabel = /* @__PURE__ */ new Map();
3733
+ const warnedGroups = /* @__PURE__ */ new Set();
3734
+ for (const opt of options) {
3735
+ if (!opt.group || opt.groupLabel === void 0) continue;
3736
+ const firstLabel = firstExplicitLabel.get(opt.group);
3737
+ if (firstLabel === void 0) {
3738
+ firstExplicitLabel.set(opt.group, opt.groupLabel);
3739
+ } else if (firstLabel !== opt.groupLabel && !warnedGroups.has(opt.group)) {
3740
+ warnedGroups.add(opt.group);
3741
+ console.warn(
3742
+ `[FFIDInquiryForm] group "${opt.group}" has conflicting groupLabels: "${firstLabel}" (first-wins) vs "${opt.groupLabel}" (ignored). Pass the same groupLabel for every item in the group, or omit it on all but the first.`
3743
+ );
3744
+ }
3745
+ }
3746
+ }
3636
3747
  function renderCategoryOptions(options) {
3637
3748
  const hasAnyGroup = options.some((o) => !!o.group);
3638
3749
  if (!hasAnyGroup) {
@@ -3685,6 +3796,9 @@ function FFIDInquiryForm({
3685
3796
  () => categories && categories.length > 0 ? categories : defaultCategories(),
3686
3797
  [categories]
3687
3798
  );
3799
+ useEffect(() => {
3800
+ warnOnGroupLabelConflicts(categoryOptions);
3801
+ }, [categoryOptions]);
3688
3802
  const initialOrgId = useMemo(() => {
3689
3803
  if (!isAuth) return null;
3690
3804
  if (preselectedOrganizationId !== void 0) return preselectedOrganizationId;
@@ -3718,6 +3832,8 @@ function FFIDInquiryForm({
3718
3832
  const [agreedLegal, setAgreedLegal] = useState(false);
3719
3833
  const [agreedTerms, setAgreedTerms] = useState(false);
3720
3834
  const [agreedPrivacy, setAgreedPrivacy] = useState(false);
3835
+ const [agreedTermsError, setAgreedTermsError] = useState(false);
3836
+ const [agreedPrivacyError, setAgreedPrivacyError] = useState(false);
3721
3837
  const [submitting, setSubmitting] = useState(false);
3722
3838
  const [formError, setFormError] = useState(null);
3723
3839
  const [successMessage, setSuccessMessage] = useState(null);
@@ -3735,7 +3851,7 @@ function FFIDInquiryForm({
3735
3851
  onChangeRef.current = onChange;
3736
3852
  }, [onChange]);
3737
3853
  useEffect(() => {
3738
- if (category === "") return;
3854
+ if (category === "" || successMessage) return;
3739
3855
  onChangeRef.current?.({
3740
3856
  name: name.trim(),
3741
3857
  email: email.trim(),
@@ -3764,25 +3880,19 @@ function FFIDInquiryForm({
3764
3880
  termsVersion,
3765
3881
  privacyVersion,
3766
3882
  turnstileToken,
3767
- locale
3883
+ locale,
3884
+ successMessage
3768
3885
  ]);
3769
3886
  const s = (style) => styleOrUndefined(style, unstyled);
3770
3887
  async function handleSubmit(e) {
3771
3888
  e.preventDefault();
3772
3889
  setFormError(null);
3773
3890
  if (separateLegalCheckboxes) {
3774
- if (!agreedTerms && !agreedPrivacy) {
3775
- setFormError("\u5229\u7528\u898F\u7D04\u3068\u30D7\u30E9\u30A4\u30D0\u30B7\u30FC\u30DD\u30EA\u30B7\u30FC\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002");
3776
- return;
3777
- }
3778
- if (!agreedTerms) {
3779
- setFormError("\u5229\u7528\u898F\u7D04\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002");
3780
- return;
3781
- }
3782
- if (!agreedPrivacy) {
3783
- setFormError("\u30D7\u30E9\u30A4\u30D0\u30B7\u30FC\u30DD\u30EA\u30B7\u30FC\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002");
3784
- return;
3785
- }
3891
+ const termsInvalid = !agreedTerms;
3892
+ const privacyInvalid = !agreedPrivacy;
3893
+ setAgreedTermsError(termsInvalid);
3894
+ setAgreedPrivacyError(privacyInvalid);
3895
+ if (termsInvalid || privacyInvalid) return;
3786
3896
  } else if (!agreedLegal) {
3787
3897
  setFormError("\u5229\u7528\u898F\u7D04\u3068\u30D7\u30E9\u30A4\u30D0\u30B7\u30FC\u30DD\u30EA\u30B7\u30FC\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002");
3788
3898
  return;
@@ -3799,6 +3909,10 @@ function FFIDInquiryForm({
3799
3909
  setFormError("\u304A\u540D\u524D\u3068\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
3800
3910
  return;
3801
3911
  }
3912
+ if (!isAuth && !hasSatisfiedTurnstile(turnstileSlot, turnstileToken)) {
3913
+ setFormError(TURNSTILE_PENDING_ERROR_JA);
3914
+ return;
3915
+ }
3802
3916
  setSubmitting(true);
3803
3917
  try {
3804
3918
  const result = await onSubmit({
@@ -3876,7 +3990,7 @@ function FFIDInquiryForm({
3876
3990
  onSubmit: handleSubmit,
3877
3991
  className: cx(className, classNames?.form),
3878
3992
  noValidate: true,
3879
- "data-hydrated": hydrated ? "true" : void 0,
3993
+ "data-hydrated": hydrated ? "true" : "false",
3880
3994
  children: [
3881
3995
  showOrgBlock && /* @__PURE__ */ jsxs("div", { style: s(fieldsetStyle), className: cx(classNames?.field, classNames?.orgSelectWrapper), children: [
3882
3996
  /* @__PURE__ */ jsx("label", { style: s(labelStyle), className: classNames?.label, htmlFor: "ffid-inquiry-org", children: "\u554F\u3044\u5408\u308F\u305B\u308B\u7ACB\u5834" }),
@@ -4043,7 +4157,11 @@ function FFIDInquiryForm({
4043
4157
  type: "checkbox",
4044
4158
  className: classNames?.legalCheckbox,
4045
4159
  checked: agreedTerms,
4046
- onChange: (e) => setAgreedTerms(e.target.checked),
4160
+ onChange: (e) => {
4161
+ const checked = e.target.checked;
4162
+ setAgreedTerms(checked);
4163
+ if (checked) setAgreedTermsError(false);
4164
+ },
4047
4165
  required: true
4048
4166
  }
4049
4167
  ),
@@ -4052,6 +4170,7 @@ function FFIDInquiryForm({
4052
4170
  " \u306B\u540C\u610F\u3057\u307E\u3059\u3002"
4053
4171
  ] })
4054
4172
  ] }),
4173
+ agreedTermsError && /* @__PURE__ */ jsx("p", { role: "alert", style: s(errorTextStyle), className: classNames?.errorText, children: TERMS_REQUIRED_ERROR_JA }),
4055
4174
  /* @__PURE__ */ jsxs("label", { style: s(legalRowLast), className: classNames?.legalRow, children: [
4056
4175
  /* @__PURE__ */ jsx(
4057
4176
  "input",
@@ -4059,7 +4178,11 @@ function FFIDInquiryForm({
4059
4178
  type: "checkbox",
4060
4179
  className: classNames?.legalCheckbox,
4061
4180
  checked: agreedPrivacy,
4062
- onChange: (e) => setAgreedPrivacy(e.target.checked),
4181
+ onChange: (e) => {
4182
+ const checked = e.target.checked;
4183
+ setAgreedPrivacy(checked);
4184
+ if (checked) setAgreedPrivacyError(false);
4185
+ },
4063
4186
  required: true
4064
4187
  }
4065
4188
  ),
@@ -4067,7 +4190,8 @@ function FFIDInquiryForm({
4067
4190
  renderPrivacyLink(),
4068
4191
  " \u306B\u540C\u610F\u3057\u307E\u3059\u3002"
4069
4192
  ] })
4070
- ] })
4193
+ ] }),
4194
+ agreedPrivacyError && /* @__PURE__ */ jsx("p", { role: "alert", style: s(errorTextStyle), className: classNames?.errorText, children: PRIVACY_REQUIRED_ERROR_JA })
4071
4195
  ] }) : /* @__PURE__ */ jsx("div", { style: s(fieldsetStyle), className: classNames?.legalBlock, children: /* @__PURE__ */ jsxs("label", { style: s(legalRowLast), className: classNames?.legalRow, children: [
4072
4196
  /* @__PURE__ */ jsx(
4073
4197
  "input",
@@ -4115,4 +4239,4 @@ function FFIDInquiryForm({
4115
4239
  );
4116
4240
  }
4117
4241
 
4118
- export { DEFAULT_API_BASE_URL, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_INQUIRY_CATEGORIES, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, retrieveCodeVerifier, storeCodeVerifier, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, withSubscription };
4242
+ export { DEFAULT_API_BASE_URL, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_INQUIRY_CATEGORIES, FFID_INQUIRY_CATEGORIES_SITE_2026, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, isFFIDInquiryCategorySite2026, normalizeRedirectUri, retrieveCodeVerifier, storeCodeVerifier, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, withSubscription };
@@ -760,8 +760,34 @@ function createMembersMethods(deps) {
760
760
  return { listMembers, updateMemberRole, removeMember };
761
761
  }
762
762
 
763
+ // src/client/profile-methods.ts
764
+ var EXT_PROFILE_ENDPOINT = "/api/v1/users/ext/me";
765
+ function createProfileMethods(deps) {
766
+ const { fetchWithAuth, createError } = deps;
767
+ async function getProfile() {
768
+ return fetchWithAuth(EXT_PROFILE_ENDPOINT);
769
+ }
770
+ async function updateProfile(data) {
771
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
772
+ return {
773
+ error: createError("VALIDATION_ERROR", "data \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059")
774
+ };
775
+ }
776
+ if (Object.keys(data).length === 0) {
777
+ return {
778
+ 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")
779
+ };
780
+ }
781
+ return fetchWithAuth(EXT_PROFILE_ENDPOINT, {
782
+ method: "PUT",
783
+ body: JSON.stringify(data)
784
+ });
785
+ }
786
+ return { getProfile, updateProfile };
787
+ }
788
+
763
789
  // src/client/version-check.ts
764
- var SDK_VERSION = "2.12.0";
790
+ var SDK_VERSION = "2.14.0";
765
791
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
766
792
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
767
793
  function sdkHeaders() {
@@ -1384,6 +1410,23 @@ function createRedirectMethods(deps) {
1384
1410
  return { redirectToLogin, redirectToAuthorize, getLoginUrl, getSignupUrl, getLogoutUrl, redirectToLogout };
1385
1411
  }
1386
1412
 
1413
+ // src/client/redirect-uri.ts
1414
+ var AUTHORITY_BOUNDARY = /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/?#]+)([/?#]?)/;
1415
+ function normalizeRedirectUri(input) {
1416
+ const url = new URL(input);
1417
+ const isRootPath = url.pathname === "" || url.pathname === "/";
1418
+ if (!isRootPath) {
1419
+ return { normalized: input, changed: false };
1420
+ }
1421
+ const match = input.match(AUTHORITY_BOUNDARY);
1422
+ if (match === null || match[2] === "/") {
1423
+ return { normalized: input, changed: false };
1424
+ }
1425
+ const authority = match[1];
1426
+ const rest = input.slice(authority.length);
1427
+ return { normalized: `${authority}/${rest}`, changed: true };
1428
+ }
1429
+
1387
1430
  // src/client/password-reset.ts
1388
1431
  var RESET_PASSWORD_BASE = "/api/v1/auth/reset-password";
1389
1432
  function isBlank(value) {
@@ -1894,6 +1937,24 @@ var FFID_ERROR_CODES = {
1894
1937
  TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
1895
1938
  };
1896
1939
  var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
1940
+ function resolveRedirectUri(raw, logger) {
1941
+ if (raw === null) return null;
1942
+ try {
1943
+ const { normalized, changed } = normalizeRedirectUri(raw);
1944
+ if (changed) {
1945
+ logger.warn(
1946
+ `FFID Client: redirect_uri \u3092\u6B63\u898F\u5316\u3057\u307E\u3057\u305F (${raw} \u2192 ${normalized})\u3002FFID \u7BA1\u7406\u753B\u9762\u3067\u306E\u767B\u9332\u5024\u3068\u4E00\u81F4\u3055\u305B\u3066\u304F\u3060\u3055\u3044\u3002`
1947
+ );
1948
+ }
1949
+ return normalized;
1950
+ } catch (error) {
1951
+ logger.warn(
1952
+ `FFID Client: redirectUri \u306E\u30D1\u30FC\u30B9\u306B\u5931\u6557\u3057\u305F\u305F\u3081\u6B63\u898F\u5316\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F (${raw})`,
1953
+ error
1954
+ );
1955
+ return raw;
1956
+ }
1957
+ }
1897
1958
  function createFFIDClient(config) {
1898
1959
  if (!config.serviceCode || !config.serviceCode.trim()) {
1899
1960
  throw new Error("FFID Client: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
@@ -1901,7 +1962,7 @@ function createFFIDClient(config) {
1901
1962
  const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
1902
1963
  const authMode = config.authMode ?? "cookie";
1903
1964
  const clientId = config.clientId ?? config.serviceCode;
1904
- const resolvedRedirectUri = config.redirectUri ?? null;
1965
+ const rawRedirectUri = config.redirectUri ?? null;
1905
1966
  const serviceApiKey = config.serviceApiKey?.trim();
1906
1967
  const verifyStrategy = config.verifyStrategy ?? "jwt";
1907
1968
  const cache = config.cache;
@@ -1916,6 +1977,7 @@ function createFFIDClient(config) {
1916
1977
  throw new Error("FFID Client: timeout \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
1917
1978
  }
1918
1979
  const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
1980
+ const resolvedRedirectUri = resolveRedirectUri(rawRedirectUri, logger);
1919
1981
  const tokenStore = authMode === "token" ? createTokenStore() : createTokenStore("memory");
1920
1982
  function createError(code, message) {
1921
1983
  return { code, message };
@@ -2109,6 +2171,10 @@ function createFFIDClient(config) {
2109
2171
  createError,
2110
2172
  serviceCode: config.serviceCode
2111
2173
  });
2174
+ const { getProfile, updateProfile } = createProfileMethods({
2175
+ fetchWithAuth,
2176
+ createError
2177
+ });
2112
2178
  const {
2113
2179
  requestPasswordReset,
2114
2180
  verifyPasswordResetToken,
@@ -2180,6 +2246,8 @@ function createFFIDClient(config) {
2180
2246
  listMembers,
2181
2247
  updateMemberRole,
2182
2248
  removeMember,
2249
+ getProfile,
2250
+ updateProfile,
2183
2251
  createCheckoutSession,
2184
2252
  createPortalSession,
2185
2253
  listPlans,
@@ -3564,8 +3632,34 @@ var FFID_INQUIRY_CATEGORIES = [
3564
3632
  "press",
3565
3633
  "other"
3566
3634
  ];
3635
+ var FFID_INQUIRY_CATEGORIES_SITE_2026 = [
3636
+ // サービス
3637
+ "consulting",
3638
+ "saas",
3639
+ "development",
3640
+ // プロダクト
3641
+ "agent-hub",
3642
+ "ai-feel-chatbot",
3643
+ "knowledge-db",
3644
+ "biz-simulator",
3645
+ "discussion-board",
3646
+ "realtime-ai",
3647
+ // その他
3648
+ "partnership",
3649
+ "media",
3650
+ "recruiting",
3651
+ "other"
3652
+ ];
3653
+ var isFFIDInquiryCategorySite2026 = (value) => FFID_INQUIRY_CATEGORIES_SITE_2026.includes(value);
3567
3654
  var CATEGORY_PLACEHOLDER_LABEL_JA = "\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044";
3568
3655
  var CATEGORY_REQUIRED_ERROR_JA = "\u304A\u554F\u3044\u5408\u308F\u305B\u7A2E\u5225\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
3656
+ var TERMS_REQUIRED_ERROR_JA = "\u5229\u7528\u898F\u7D04\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002";
3657
+ var PRIVACY_REQUIRED_ERROR_JA = "\u30D7\u30E9\u30A4\u30D0\u30B7\u30FC\u30DD\u30EA\u30B7\u30FC\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002";
3658
+ var TURNSTILE_PENDING_ERROR_JA = "\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u30C1\u30A7\u30C3\u30AF\u306E\u5B8C\u4E86\u5F8C\u306B\u9001\u4FE1\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
3659
+ function hasSatisfiedTurnstile(turnstileSlot, turnstileToken) {
3660
+ if (turnstileSlot == null) return true;
3661
+ return (turnstileToken ?? "").trim().length > 0;
3662
+ }
3569
3663
  var SUBMIT_BUTTON_OPACITY_PENDING = 0.6;
3570
3664
  var labelStyle = {
3571
3665
  display: "block",
@@ -3635,6 +3729,23 @@ function defaultCategories() {
3635
3729
  label: DEFAULT_CATEGORY_LABELS_JA[value]
3636
3730
  }));
3637
3731
  }
3732
+ function warnOnGroupLabelConflicts(options) {
3733
+ if (process.env.NODE_ENV === "production") return;
3734
+ const firstExplicitLabel = /* @__PURE__ */ new Map();
3735
+ const warnedGroups = /* @__PURE__ */ new Set();
3736
+ for (const opt of options) {
3737
+ if (!opt.group || opt.groupLabel === void 0) continue;
3738
+ const firstLabel = firstExplicitLabel.get(opt.group);
3739
+ if (firstLabel === void 0) {
3740
+ firstExplicitLabel.set(opt.group, opt.groupLabel);
3741
+ } else if (firstLabel !== opt.groupLabel && !warnedGroups.has(opt.group)) {
3742
+ warnedGroups.add(opt.group);
3743
+ console.warn(
3744
+ `[FFIDInquiryForm] group "${opt.group}" has conflicting groupLabels: "${firstLabel}" (first-wins) vs "${opt.groupLabel}" (ignored). Pass the same groupLabel for every item in the group, or omit it on all but the first.`
3745
+ );
3746
+ }
3747
+ }
3748
+ }
3638
3749
  function renderCategoryOptions(options) {
3639
3750
  const hasAnyGroup = options.some((o) => !!o.group);
3640
3751
  if (!hasAnyGroup) {
@@ -3687,6 +3798,9 @@ function FFIDInquiryForm({
3687
3798
  () => categories && categories.length > 0 ? categories : defaultCategories(),
3688
3799
  [categories]
3689
3800
  );
3801
+ react.useEffect(() => {
3802
+ warnOnGroupLabelConflicts(categoryOptions);
3803
+ }, [categoryOptions]);
3690
3804
  const initialOrgId = react.useMemo(() => {
3691
3805
  if (!isAuth) return null;
3692
3806
  if (preselectedOrganizationId !== void 0) return preselectedOrganizationId;
@@ -3720,6 +3834,8 @@ function FFIDInquiryForm({
3720
3834
  const [agreedLegal, setAgreedLegal] = react.useState(false);
3721
3835
  const [agreedTerms, setAgreedTerms] = react.useState(false);
3722
3836
  const [agreedPrivacy, setAgreedPrivacy] = react.useState(false);
3837
+ const [agreedTermsError, setAgreedTermsError] = react.useState(false);
3838
+ const [agreedPrivacyError, setAgreedPrivacyError] = react.useState(false);
3723
3839
  const [submitting, setSubmitting] = react.useState(false);
3724
3840
  const [formError, setFormError] = react.useState(null);
3725
3841
  const [successMessage, setSuccessMessage] = react.useState(null);
@@ -3737,7 +3853,7 @@ function FFIDInquiryForm({
3737
3853
  onChangeRef.current = onChange;
3738
3854
  }, [onChange]);
3739
3855
  react.useEffect(() => {
3740
- if (category === "") return;
3856
+ if (category === "" || successMessage) return;
3741
3857
  onChangeRef.current?.({
3742
3858
  name: name.trim(),
3743
3859
  email: email.trim(),
@@ -3766,25 +3882,19 @@ function FFIDInquiryForm({
3766
3882
  termsVersion,
3767
3883
  privacyVersion,
3768
3884
  turnstileToken,
3769
- locale
3885
+ locale,
3886
+ successMessage
3770
3887
  ]);
3771
3888
  const s = (style) => styleOrUndefined(style, unstyled);
3772
3889
  async function handleSubmit(e) {
3773
3890
  e.preventDefault();
3774
3891
  setFormError(null);
3775
3892
  if (separateLegalCheckboxes) {
3776
- if (!agreedTerms && !agreedPrivacy) {
3777
- setFormError("\u5229\u7528\u898F\u7D04\u3068\u30D7\u30E9\u30A4\u30D0\u30B7\u30FC\u30DD\u30EA\u30B7\u30FC\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002");
3778
- return;
3779
- }
3780
- if (!agreedTerms) {
3781
- setFormError("\u5229\u7528\u898F\u7D04\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002");
3782
- return;
3783
- }
3784
- if (!agreedPrivacy) {
3785
- setFormError("\u30D7\u30E9\u30A4\u30D0\u30B7\u30FC\u30DD\u30EA\u30B7\u30FC\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002");
3786
- return;
3787
- }
3893
+ const termsInvalid = !agreedTerms;
3894
+ const privacyInvalid = !agreedPrivacy;
3895
+ setAgreedTermsError(termsInvalid);
3896
+ setAgreedPrivacyError(privacyInvalid);
3897
+ if (termsInvalid || privacyInvalid) return;
3788
3898
  } else if (!agreedLegal) {
3789
3899
  setFormError("\u5229\u7528\u898F\u7D04\u3068\u30D7\u30E9\u30A4\u30D0\u30B7\u30FC\u30DD\u30EA\u30B7\u30FC\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002");
3790
3900
  return;
@@ -3801,6 +3911,10 @@ function FFIDInquiryForm({
3801
3911
  setFormError("\u304A\u540D\u524D\u3068\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
3802
3912
  return;
3803
3913
  }
3914
+ if (!isAuth && !hasSatisfiedTurnstile(turnstileSlot, turnstileToken)) {
3915
+ setFormError(TURNSTILE_PENDING_ERROR_JA);
3916
+ return;
3917
+ }
3804
3918
  setSubmitting(true);
3805
3919
  try {
3806
3920
  const result = await onSubmit({
@@ -3878,7 +3992,7 @@ function FFIDInquiryForm({
3878
3992
  onSubmit: handleSubmit,
3879
3993
  className: cx(className, classNames?.form),
3880
3994
  noValidate: true,
3881
- "data-hydrated": hydrated ? "true" : void 0,
3995
+ "data-hydrated": hydrated ? "true" : "false",
3882
3996
  children: [
3883
3997
  showOrgBlock && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: s(fieldsetStyle), className: cx(classNames?.field, classNames?.orgSelectWrapper), children: [
3884
3998
  /* @__PURE__ */ jsxRuntime.jsx("label", { style: s(labelStyle), className: classNames?.label, htmlFor: "ffid-inquiry-org", children: "\u554F\u3044\u5408\u308F\u305B\u308B\u7ACB\u5834" }),
@@ -4045,7 +4159,11 @@ function FFIDInquiryForm({
4045
4159
  type: "checkbox",
4046
4160
  className: classNames?.legalCheckbox,
4047
4161
  checked: agreedTerms,
4048
- onChange: (e) => setAgreedTerms(e.target.checked),
4162
+ onChange: (e) => {
4163
+ const checked = e.target.checked;
4164
+ setAgreedTerms(checked);
4165
+ if (checked) setAgreedTermsError(false);
4166
+ },
4049
4167
  required: true
4050
4168
  }
4051
4169
  ),
@@ -4054,6 +4172,7 @@ function FFIDInquiryForm({
4054
4172
  " \u306B\u540C\u610F\u3057\u307E\u3059\u3002"
4055
4173
  ] })
4056
4174
  ] }),
4175
+ agreedTermsError && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", style: s(errorTextStyle), className: classNames?.errorText, children: TERMS_REQUIRED_ERROR_JA }),
4057
4176
  /* @__PURE__ */ jsxRuntime.jsxs("label", { style: s(legalRowLast), className: classNames?.legalRow, children: [
4058
4177
  /* @__PURE__ */ jsxRuntime.jsx(
4059
4178
  "input",
@@ -4061,7 +4180,11 @@ function FFIDInquiryForm({
4061
4180
  type: "checkbox",
4062
4181
  className: classNames?.legalCheckbox,
4063
4182
  checked: agreedPrivacy,
4064
- onChange: (e) => setAgreedPrivacy(e.target.checked),
4183
+ onChange: (e) => {
4184
+ const checked = e.target.checked;
4185
+ setAgreedPrivacy(checked);
4186
+ if (checked) setAgreedPrivacyError(false);
4187
+ },
4065
4188
  required: true
4066
4189
  }
4067
4190
  ),
@@ -4069,7 +4192,8 @@ function FFIDInquiryForm({
4069
4192
  renderPrivacyLink(),
4070
4193
  " \u306B\u540C\u610F\u3057\u307E\u3059\u3002"
4071
4194
  ] })
4072
- ] })
4195
+ ] }),
4196
+ agreedPrivacyError && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", style: s(errorTextStyle), className: classNames?.errorText, children: PRIVACY_REQUIRED_ERROR_JA })
4073
4197
  ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: s(fieldsetStyle), className: classNames?.legalBlock, children: /* @__PURE__ */ jsxRuntime.jsxs("label", { style: s(legalRowLast), className: classNames?.legalRow, children: [
4074
4198
  /* @__PURE__ */ jsxRuntime.jsx(
4075
4199
  "input",
@@ -4129,11 +4253,14 @@ exports.FFIDSubscriptionBadge = FFIDSubscriptionBadge;
4129
4253
  exports.FFIDUserMenu = FFIDUserMenu;
4130
4254
  exports.FFID_ANNOUNCEMENTS_ERROR_CODES = FFID_ANNOUNCEMENTS_ERROR_CODES;
4131
4255
  exports.FFID_INQUIRY_CATEGORIES = FFID_INQUIRY_CATEGORIES;
4256
+ exports.FFID_INQUIRY_CATEGORIES_SITE_2026 = FFID_INQUIRY_CATEGORIES_SITE_2026;
4132
4257
  exports.createFFIDAnnouncementsClient = createFFIDAnnouncementsClient;
4133
4258
  exports.createFFIDClient = createFFIDClient;
4134
4259
  exports.createTokenStore = createTokenStore;
4135
4260
  exports.generateCodeChallenge = generateCodeChallenge;
4136
4261
  exports.generateCodeVerifier = generateCodeVerifier;
4262
+ exports.isFFIDInquiryCategorySite2026 = isFFIDInquiryCategorySite2026;
4263
+ exports.normalizeRedirectUri = normalizeRedirectUri;
4137
4264
  exports.retrieveCodeVerifier = retrieveCodeVerifier;
4138
4265
  exports.storeCodeVerifier = storeCodeVerifier;
4139
4266
  exports.useFFID = useFFID;