@feelflow/ffid-sdk 1.10.0 → 1.11.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.
@@ -9,6 +9,7 @@ var DEFAULT_API_BASE_URL = "https://id.feelflow.net";
9
9
 
10
10
  // src/auth/token-store.ts
11
11
  var STORAGE_KEY = "ffid_tokens";
12
+ var TOKEN_STORE_LOG_PREFIX = "[FFID SDK] TokenStore:";
12
13
  var EXPIRY_BUFFER_SECONDS = 30;
13
14
  var EXPIRY_BUFFER_MS = EXPIRY_BUFFER_SECONDS * 1e3;
14
15
  function isLocalStorageAvailable() {
@@ -32,22 +33,33 @@ function createLocalStorageStore() {
32
33
  const raw = storage.getItem(STORAGE_KEY);
33
34
  if (!raw) return null;
34
35
  const parsed = JSON.parse(raw);
35
- if (!isTokenData(parsed)) return null;
36
+ if (!isTokenData(parsed)) {
37
+ console.warn(TOKEN_STORE_LOG_PREFIX, "\u30C8\u30FC\u30AF\u30F3\u30C7\u30FC\u30BF\u304C\u4E0D\u6B63\u306A\u5F62\u5F0F\u3067\u3059\u3002\u30B9\u30C8\u30EC\u30FC\u30B8\u3092\u30AF\u30EA\u30A2\u3057\u307E\u3059");
38
+ storage.removeItem(STORAGE_KEY);
39
+ return null;
40
+ }
36
41
  return parsed;
37
- } catch {
42
+ } catch (error) {
43
+ console.warn(TOKEN_STORE_LOG_PREFIX, "\u30C8\u30FC\u30AF\u30F3\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u7834\u640D\u30C7\u30FC\u30BF\u3092\u30AF\u30EA\u30A2\u3057\u307E\u3059", error);
44
+ try {
45
+ storage.removeItem(STORAGE_KEY);
46
+ } catch {
47
+ }
38
48
  return null;
39
49
  }
40
50
  },
41
51
  setTokens(tokens) {
42
52
  try {
43
53
  storage.setItem(STORAGE_KEY, JSON.stringify(tokens));
44
- } catch {
54
+ } catch (error) {
55
+ console.warn(TOKEN_STORE_LOG_PREFIX, "\u30C8\u30FC\u30AF\u30F3\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u30B9\u30C8\u30EC\u30FC\u30B8\u5BB9\u91CF\u8D85\u904E\u306E\u53EF\u80FD\u6027\uFF09", error);
45
56
  }
46
57
  },
47
58
  clearTokens() {
48
59
  try {
49
60
  storage.removeItem(STORAGE_KEY);
50
- } catch {
61
+ } catch (error) {
62
+ console.warn(TOKEN_STORE_LOG_PREFIX, "\u30C8\u30FC\u30AF\u30F3\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F", error);
51
63
  }
52
64
  },
53
65
  isAccessTokenExpired() {
@@ -90,54 +102,6 @@ function createTokenStore(storageType) {
90
102
  return createMemoryStore();
91
103
  }
92
104
 
93
- // src/auth/pkce.ts
94
- var VERIFIER_STORAGE_KEY = "ffid_code_verifier";
95
- var CODE_VERIFIER_MIN_LENGTH = 43;
96
- var UNRESERVED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
97
- function generateCodeVerifier() {
98
- const length = CODE_VERIFIER_MIN_LENGTH;
99
- const randomValues = new Uint8Array(length);
100
- crypto.getRandomValues(randomValues);
101
- let verifier = "";
102
- for (let i = 0; i < length; i++) {
103
- verifier += UNRESERVED_CHARS[randomValues[i] % UNRESERVED_CHARS.length];
104
- }
105
- return verifier;
106
- }
107
- async function generateCodeChallenge(verifier) {
108
- const encoder = new TextEncoder();
109
- const data = encoder.encode(verifier);
110
- const digest = await crypto.subtle.digest("SHA-256", data);
111
- return base64UrlEncode(digest);
112
- }
113
- function storeCodeVerifier(verifier) {
114
- try {
115
- if (typeof window === "undefined") return;
116
- window.sessionStorage.setItem(VERIFIER_STORAGE_KEY, verifier);
117
- } catch {
118
- }
119
- }
120
- function retrieveCodeVerifier() {
121
- try {
122
- if (typeof window === "undefined") return null;
123
- const verifier = window.sessionStorage.getItem(VERIFIER_STORAGE_KEY);
124
- if (verifier) {
125
- window.sessionStorage.removeItem(VERIFIER_STORAGE_KEY);
126
- }
127
- return verifier;
128
- } catch {
129
- return null;
130
- }
131
- }
132
- function base64UrlEncode(buffer) {
133
- const bytes = new Uint8Array(buffer);
134
- let binary = "";
135
- for (let i = 0; i < bytes.length; i++) {
136
- binary += String.fromCharCode(bytes[i]);
137
- }
138
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
139
- }
140
-
141
105
  // src/client/oauth-userinfo.ts
142
106
  var VALID_SUBSCRIPTION_STATUSES = ["trialing", "active", "past_due", "canceled", "paused"];
143
107
  function isValidSubscriptionStatus(value) {
@@ -241,6 +205,7 @@ var OAUTH_INTROSPECT_ENDPOINT = "/api/v1/oauth/introspect";
241
205
  var CACHE_KEY_PREFIX = "ffid:introspect:";
242
206
  var HEX_BASE = 16;
243
207
  var HEX_BYTE_WIDTH = 2;
208
+ var TOKEN_LOG_PREFIX_LENGTH = 8;
244
209
  function createVerifyAccessToken(deps) {
245
210
  const { authMode, baseUrl, serviceCode, serviceApiKey, verifyStrategy, logger, createError, errorCodes, cache, timeout } = deps;
246
211
  let jwtVerify2 = null;
@@ -256,7 +221,7 @@ function createVerifyAccessToken(deps) {
256
221
  }
257
222
  return jwtVerify2;
258
223
  }
259
- async function verifyAccessToken(accessToken) {
224
+ async function verifyAccessToken(accessToken, options) {
260
225
  if (authMode !== "service-key") {
261
226
  return {
262
227
  error: createError(
@@ -273,12 +238,24 @@ function createVerifyAccessToken(deps) {
273
238
  )
274
239
  };
275
240
  }
241
+ if (options?.includeProfile && verifyStrategy === "jwt" && !serviceApiKey) {
242
+ return {
243
+ error: createError(
244
+ errorCodes.TOKEN_VERIFICATION_ERROR,
245
+ "includeProfile: true \u3092\u4F7F\u7528\u3059\u308B\u306B\u306F serviceApiKey \u306E\u8A2D\u5B9A\u304C\u5FC5\u8981\u3067\u3059"
246
+ )
247
+ };
248
+ }
276
249
  if (verifyStrategy === "jwt") {
277
- return getJwtVerifier()(accessToken);
250
+ const jwtResult = await getJwtVerifier()(accessToken);
251
+ if (!options?.includeProfile || jwtResult.error) {
252
+ return jwtResult;
253
+ }
254
+ return verifyViaIntrospect(accessToken, "profile");
278
255
  }
279
256
  return verifyViaIntrospect(accessToken);
280
257
  }
281
- async function verifyViaIntrospect(accessToken) {
258
+ async function verifyViaIntrospect(accessToken, cacheKeySuffix) {
282
259
  if (!serviceApiKey) {
283
260
  return {
284
261
  error: createError(
@@ -289,7 +266,7 @@ function createVerifyAccessToken(deps) {
289
266
  }
290
267
  const url = `${baseUrl}${OAUTH_INTROSPECT_ENDPOINT}`;
291
268
  logger.debug("Verifying access token:", url);
292
- const cacheKey = await buildCacheKey(accessToken);
269
+ const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) : "";
293
270
  if (cache) {
294
271
  try {
295
272
  const cached = await cache.adapter.get(cacheKey);
@@ -391,16 +368,23 @@ function createVerifyAccessToken(deps) {
391
368
  }
392
369
  return { data: userinfo };
393
370
  }
394
- async function buildCacheKey(token) {
371
+ async function buildCacheKey(token, suffix) {
372
+ let key;
395
373
  if (typeof globalThis.crypto?.subtle?.digest === "function") {
396
374
  const encoder = new TextEncoder();
397
375
  const data = encoder.encode(token);
398
376
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
399
377
  const hashArray = Array.from(new Uint8Array(hashBuffer));
400
378
  const hashHex = hashArray.map((b) => b.toString(HEX_BASE).padStart(HEX_BYTE_WIDTH, "0")).join("");
401
- return CACHE_KEY_PREFIX + hashHex;
379
+ key = CACHE_KEY_PREFIX + hashHex;
380
+ } else {
381
+ const tokenPrefix = token.length > TOKEN_LOG_PREFIX_LENGTH ? token.substring(0, TOKEN_LOG_PREFIX_LENGTH) + "..." : "***";
382
+ logger.warn(
383
+ `crypto.subtle \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u305F\u3081\u3001\u30AD\u30E3\u30C3\u30B7\u30E5\u30AD\u30FC\u306B\u30CF\u30C3\u30B7\u30E5\u5316\u3055\u308C\u3066\u3044\u306A\u3044\u30C8\u30FC\u30AF\u30F3\u3092\u4F7F\u7528\u3057\u307E\u3059 (token prefix: ${tokenPrefix})`
384
+ );
385
+ key = CACHE_KEY_PREFIX + token;
402
386
  }
403
- return CACHE_KEY_PREFIX + token;
387
+ return suffix ? `${key}:${suffix}` : key;
404
388
  }
405
389
  return verifyAccessToken;
406
390
  }
@@ -453,9 +437,15 @@ function createBillingMethods(deps) {
453
437
  }
454
438
 
455
439
  // src/client/version-check.ts
456
- var SDK_VERSION = "1.10.0";
440
+ var SDK_VERSION = "1.11.0";
457
441
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
458
442
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
443
+ function sdkHeaders() {
444
+ return {
445
+ "User-Agent": SDK_USER_AGENT,
446
+ [SDK_VERSION_HEADER]: SDK_VERSION
447
+ };
448
+ }
459
449
  var LATEST_VERSION_HEADER = "X-FFID-SDK-Latest-Version";
460
450
  var SEMVER_PATTERN = /^\d+(\.\d+)*$/;
461
451
  var _versionWarningShown = false;
@@ -489,178 +479,213 @@ npm install @feelflow/ffid-sdk@latest \u3067\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8
489
479
  }
490
480
  }
491
481
 
492
- // src/client/ffid-client.ts
493
- var NO_CONTENT_STATUS = 204;
494
- var SESSION_ENDPOINT = "/api/v1/auth/session";
495
- var LOGOUT_ENDPOINT = "/api/v1/auth/signout";
482
+ // src/client/oauth-token.ts
496
483
  var OAUTH_TOKEN_ENDPOINT = "/api/v1/oauth/token";
497
- var OAUTH_USERINFO_ENDPOINT = "/api/v1/oauth/userinfo";
498
- var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
499
484
  var OAUTH_REVOKE_ENDPOINT = "/api/v1/oauth/revoke";
500
- var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
501
- var SDK_LOG_PREFIX = "[FFID SDK]";
502
485
  var MS_PER_SECOND = 1e3;
503
- var UNAUTHORIZED_STATUS = 401;
504
- var STATE_RANDOM_BYTES = 16;
505
- var HEX_BASE2 = 16;
506
- var noopLogger = {
507
- debug: () => {
508
- },
509
- info: () => {
510
- },
511
- warn: () => {
512
- },
513
- error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
514
- };
515
- var consoleLogger = {
516
- debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
517
- info: (...args) => console.info(SDK_LOG_PREFIX, ...args),
518
- warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
519
- error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
520
- };
521
- var FFID_ERROR_CODES = {
522
- NETWORK_ERROR: "NETWORK_ERROR",
523
- PARSE_ERROR: "PARSE_ERROR",
524
- UNKNOWN_ERROR: "UNKNOWN_ERROR",
525
- TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
526
- TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
527
- NO_TOKENS: "NO_TOKENS",
528
- TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
529
- };
530
- function createFFIDClient(config) {
531
- if (!config.serviceCode || !config.serviceCode.trim()) {
532
- throw new Error("FFID Client: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
533
- }
534
- const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
535
- const authMode = config.authMode ?? "cookie";
536
- const clientId = config.clientId ?? config.serviceCode;
537
- const resolvedRedirectUri = config.redirectUri ?? null;
538
- const serviceApiKey = config.serviceApiKey?.trim();
539
- const verifyStrategy = config.verifyStrategy ?? "jwt";
540
- const cache = config.cache;
541
- const timeout = config.timeout;
542
- if (authMode === "service-key" && !serviceApiKey) {
543
- throw new Error("FFID Client: service-key \u30E2\u30FC\u30C9\u3067\u306F serviceApiKey \u304C\u5FC5\u9808\u3067\u3059");
544
- }
545
- if (cache && cache.ttl <= 0) {
546
- throw new Error("FFID Client: cache.ttl \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
547
- }
548
- if (timeout !== void 0 && timeout <= 0) {
549
- throw new Error("FFID Client: timeout \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
550
- }
551
- const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
552
- const tokenStore = authMode === "token" ? createTokenStore() : createTokenStore("memory");
553
- async function fetchWithAuth(endpoint, options = {}) {
554
- const url = `${baseUrl}${endpoint}`;
555
- logger.debug("Fetching:", url);
556
- const fetchOptions = buildFetchOptions(options);
486
+ function createOAuthTokenMethods(deps) {
487
+ const {
488
+ baseUrl,
489
+ clientId,
490
+ resolvedRedirectUri,
491
+ tokenStore,
492
+ logger,
493
+ errorCodes
494
+ } = deps;
495
+ async function exchangeCodeForTokens(code, codeVerifier) {
496
+ const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
497
+ logger.debug("Exchanging code for tokens:", url);
498
+ const effectiveRedirectUri = resolvedRedirectUri ?? (typeof window !== "undefined" ? window.location.origin + window.location.pathname : null);
499
+ if (!effectiveRedirectUri) {
500
+ logger.error("redirectUri is required for token exchange in SSR environments. Set config.redirectUri explicitly.");
501
+ return {
502
+ error: {
503
+ code: errorCodes.TOKEN_EXCHANGE_ERROR,
504
+ message: "redirectUri \u304C\u672A\u8A2D\u5B9A\u3067\u3059\u3002SSR\u74B0\u5883\u3067\u306F config.redirectUri \u3092\u660E\u793A\u7684\u306B\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
505
+ }
506
+ };
507
+ }
508
+ const body = {
509
+ grant_type: "authorization_code",
510
+ code,
511
+ client_id: clientId,
512
+ redirect_uri: effectiveRedirectUri
513
+ };
514
+ if (codeVerifier) {
515
+ body.code_verifier = codeVerifier;
516
+ }
557
517
  let response;
558
518
  try {
559
- response = await fetch(url, fetchOptions);
519
+ response = await fetch(url, {
520
+ method: "POST",
521
+ credentials: "omit",
522
+ headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
523
+ body: new URLSearchParams(body).toString()
524
+ });
560
525
  } catch (error) {
561
- logger.error("Network error:", error);
526
+ logger.error("Network error during token exchange:", error);
562
527
  return {
563
528
  error: {
564
- code: FFID_ERROR_CODES.NETWORK_ERROR,
529
+ code: errorCodes.NETWORK_ERROR,
565
530
  message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
566
531
  }
567
532
  };
568
533
  }
569
- if (authMode === "token" && response.status === UNAUTHORIZED_STATUS) {
570
- const refreshResult = await refreshAccessToken();
571
- if (!refreshResult.error) {
572
- logger.debug("Token refreshed, retrying request");
573
- const retryOptions = buildFetchOptions(options);
574
- try {
575
- response = await fetch(url, retryOptions);
576
- } catch (retryError) {
577
- logger.error("Network error on retry:", retryError);
578
- return {
579
- error: {
580
- code: FFID_ERROR_CODES.NETWORK_ERROR,
581
- message: retryError instanceof Error ? retryError.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
582
- }
583
- };
584
- }
585
- }
586
- }
587
- let raw;
534
+ let tokenResponse;
588
535
  try {
589
- raw = await response.json();
536
+ tokenResponse = await response.json();
590
537
  } catch (parseError) {
591
- logger.error("Parse error:", parseError, "Status:", response.status);
538
+ logger.error("Parse error during token exchange:", parseError);
592
539
  return {
593
540
  error: {
594
- code: FFID_ERROR_CODES.PARSE_ERROR,
595
- 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})`
541
+ code: errorCodes.PARSE_ERROR,
542
+ message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F (status: ${response.status})`
596
543
  }
597
544
  };
598
545
  }
599
- logger.debug("Response:", response.status, raw);
600
- checkVersionHeader(response, logger);
601
546
  if (!response.ok) {
547
+ const errorBody = tokenResponse;
602
548
  return {
603
- error: raw.error ?? {
604
- code: FFID_ERROR_CODES.UNKNOWN_ERROR,
605
- message: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
549
+ error: {
550
+ code: errorBody.error ?? errorCodes.TOKEN_EXCHANGE_ERROR,
551
+ message: errorBody.error_description ?? "\u30C8\u30FC\u30AF\u30F3\u4EA4\u63DB\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
606
552
  }
607
553
  };
608
554
  }
609
- if (raw.data === void 0) {
555
+ tokenStore.setTokens({
556
+ accessToken: tokenResponse.access_token,
557
+ refreshToken: tokenResponse.refresh_token,
558
+ expiresAt: Date.now() + tokenResponse.expires_in * MS_PER_SECOND
559
+ });
560
+ logger.debug("Token exchange successful");
561
+ return { data: void 0 };
562
+ }
563
+ async function refreshAccessToken() {
564
+ const tokens = tokenStore.getTokens();
565
+ if (!tokens) {
610
566
  return {
611
567
  error: {
612
- code: FFID_ERROR_CODES.UNKNOWN_ERROR,
613
- message: "\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u30C7\u30FC\u30BF\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F"
568
+ code: errorCodes.NO_TOKENS,
569
+ message: "\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u304C\u3042\u308A\u307E\u305B\u3093"
614
570
  }
615
571
  };
616
572
  }
617
- return { data: raw.data };
618
- }
619
- function sdkHeaders() {
620
- return {
621
- "User-Agent": SDK_USER_AGENT,
622
- [SDK_VERSION_HEADER]: SDK_VERSION
623
- };
624
- }
625
- function buildFetchOptions(options) {
626
- if (authMode === "service-key") {
627
- return {
628
- ...options,
573
+ const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
574
+ logger.debug("Refreshing access token:", url);
575
+ let response;
576
+ try {
577
+ response = await fetch(url, {
578
+ method: "POST",
629
579
  credentials: "omit",
630
- headers: {
631
- "Content-Type": "application/json",
632
- ...sdkHeaders(),
633
- "X-Service-Api-Key": serviceApiKey,
634
- ...options.headers
580
+ headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
581
+ body: new URLSearchParams({
582
+ grant_type: "refresh_token",
583
+ refresh_token: tokens.refreshToken,
584
+ client_id: clientId
585
+ }).toString()
586
+ });
587
+ } catch (error) {
588
+ logger.error("Network error during token refresh:", error);
589
+ return {
590
+ error: {
591
+ code: errorCodes.NETWORK_ERROR,
592
+ message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
635
593
  }
636
594
  };
637
595
  }
638
- if (authMode === "token") {
639
- const tokens = tokenStore.getTokens();
640
- const headers = {
641
- "Content-Type": "application/json",
642
- ...sdkHeaders(),
643
- ...options.headers
644
- };
645
- if (tokens) {
646
- headers["Authorization"] = `Bearer ${tokens.accessToken}`;
647
- }
648
- return {
649
- ...options,
650
- credentials: "omit",
651
- headers
596
+ let tokenResponse;
597
+ try {
598
+ tokenResponse = await response.json();
599
+ } catch (parseError) {
600
+ logger.error("Parse error during token refresh:", parseError);
601
+ return {
602
+ error: {
603
+ code: errorCodes.PARSE_ERROR,
604
+ message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F (status: ${response.status})`
605
+ }
652
606
  };
653
607
  }
654
- return {
655
- ...options,
656
- credentials: "include",
657
- headers: {
658
- "Content-Type": "application/json",
659
- ...sdkHeaders(),
660
- ...options.headers
608
+ if (!response.ok) {
609
+ const errorBody = tokenResponse;
610
+ logger.error("Token refresh failed:", errorBody);
611
+ const irrecoverableErrors = ["token_revoked", "invalid_grant"];
612
+ if (errorBody.error && irrecoverableErrors.includes(errorBody.error)) {
613
+ tokenStore.clearTokens();
614
+ logger.debug("Cleared tokens due to irrecoverable refresh error:", errorBody.error);
661
615
  }
662
- };
616
+ return {
617
+ error: {
618
+ code: errorBody.error ?? errorCodes.TOKEN_REFRESH_ERROR,
619
+ message: errorBody.error_description ?? "\u30C8\u30FC\u30AF\u30F3\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
620
+ }
621
+ };
622
+ }
623
+ tokenStore.setTokens({
624
+ accessToken: tokenResponse.access_token,
625
+ refreshToken: tokenResponse.refresh_token,
626
+ expiresAt: Date.now() + tokenResponse.expires_in * MS_PER_SECOND
627
+ });
628
+ logger.debug("Token refresh successful");
629
+ return { data: void 0 };
663
630
  }
631
+ async function signOutToken() {
632
+ const tokens = tokenStore.getTokens();
633
+ tokenStore.clearTokens();
634
+ if (!tokens) {
635
+ logger.debug("No tokens to revoke");
636
+ return { data: void 0 };
637
+ }
638
+ const url = `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`;
639
+ logger.debug("Revoking token:", url);
640
+ try {
641
+ const response = await fetch(url, {
642
+ method: "POST",
643
+ credentials: "omit",
644
+ headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
645
+ body: new URLSearchParams({
646
+ token: tokens.accessToken,
647
+ client_id: clientId
648
+ }).toString()
649
+ });
650
+ if (!response.ok) {
651
+ logger.warn(
652
+ "\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u5931\u6557\u3057\u307E\u3057\u305F:",
653
+ `status=${response.status}`
654
+ );
655
+ }
656
+ } catch (error) {
657
+ logger.warn("\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u306E\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
658
+ }
659
+ logger.debug("Token sign-out completed");
660
+ return { data: void 0 };
661
+ }
662
+ return { exchangeCodeForTokens, refreshAccessToken, signOutToken };
663
+ }
664
+
665
+ // src/client/session.ts
666
+ var SESSION_ENDPOINT = "/api/v1/auth/session";
667
+ var LOGOUT_ENDPOINT = "/api/v1/auth/signout";
668
+ var OAUTH_USERINFO_ENDPOINT = "/api/v1/oauth/userinfo";
669
+ var NO_CONTENT_STATUS = 204;
670
+ var UNAUTHORIZED_STATUS = 401;
671
+ function normalizeFFIDUser(user) {
672
+ return {
673
+ ...user,
674
+ locale: user.locale ?? null,
675
+ timezone: user.timezone ?? null
676
+ };
677
+ }
678
+ function createSessionMethods(deps) {
679
+ const {
680
+ authMode,
681
+ baseUrl,
682
+ serviceCode,
683
+ tokenStore,
684
+ logger,
685
+ fetchWithAuth,
686
+ refreshAccessToken,
687
+ errorCodes
688
+ } = deps;
664
689
  async function getSession() {
665
690
  if (authMode === "token") {
666
691
  return getSessionFromUserinfo();
@@ -677,19 +702,12 @@ function createFFIDClient(config) {
677
702
  }
678
703
  return result;
679
704
  }
680
- function normalizeFFIDUser(user) {
681
- return {
682
- ...user,
683
- locale: user.locale ?? null,
684
- timezone: user.timezone ?? null
685
- };
686
- }
687
705
  async function getSessionFromUserinfo() {
688
706
  const tokens = tokenStore.getTokens();
689
707
  if (!tokens) {
690
708
  return {
691
709
  error: {
692
- code: FFID_ERROR_CODES.NO_TOKENS,
710
+ code: errorCodes.NO_TOKENS,
693
711
  message: "\u30C8\u30FC\u30AF\u30F3\u304C\u4FDD\u5B58\u3055\u308C\u3066\u3044\u307E\u305B\u3093"
694
712
  }
695
713
  };
@@ -710,7 +728,7 @@ function createFFIDClient(config) {
710
728
  logger.error("Network error:", error);
711
729
  return {
712
730
  error: {
713
- code: FFID_ERROR_CODES.NETWORK_ERROR,
731
+ code: errorCodes.NETWORK_ERROR,
714
732
  message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
715
733
  }
716
734
  };
@@ -733,7 +751,7 @@ function createFFIDClient(config) {
733
751
  logger.error("Network error on retry:", retryError);
734
752
  return {
735
753
  error: {
736
- code: FFID_ERROR_CODES.NETWORK_ERROR,
754
+ code: errorCodes.NETWORK_ERROR,
737
755
  message: retryError instanceof Error ? retryError.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
738
756
  }
739
757
  };
@@ -750,7 +768,7 @@ function createFFIDClient(config) {
750
768
  logger.error("Parse error:", parseError, "Status:", response.status);
751
769
  return {
752
770
  error: {
753
- code: FFID_ERROR_CODES.PARSE_ERROR,
771
+ code: errorCodes.PARSE_ERROR,
754
772
  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})`
755
773
  }
756
774
  };
@@ -759,7 +777,7 @@ function createFFIDClient(config) {
759
777
  const errorBody = rawUserinfo;
760
778
  return {
761
779
  error: {
762
- code: errorBody.code ?? FFID_ERROR_CODES.UNKNOWN_ERROR,
780
+ code: errorBody.code ?? errorCodes.UNKNOWN_ERROR,
763
781
  message: errorBody.message ?? "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
764
782
  }
765
783
  };
@@ -778,16 +796,10 @@ function createFFIDClient(config) {
778
796
  data: {
779
797
  user,
780
798
  organizations: [],
781
- subscriptions: mapUserinfoSubscriptionToSession(userinfo, config.serviceCode)
799
+ subscriptions: mapUserinfoSubscriptionToSession(userinfo, serviceCode)
782
800
  }
783
801
  };
784
802
  }
785
- async function signOut() {
786
- if (authMode === "token") {
787
- return signOutToken();
788
- }
789
- return signOutCookie();
790
- }
791
803
  async function signOutCookie() {
792
804
  const url = `${baseUrl}${LOGOUT_ENDPOINT}`;
793
805
  logger.debug("Fetching:", url);
@@ -802,7 +814,7 @@ function createFFIDClient(config) {
802
814
  logger.error("Network error:", error);
803
815
  return {
804
816
  error: {
805
- code: FFID_ERROR_CODES.NETWORK_ERROR,
817
+ code: errorCodes.NETWORK_ERROR,
806
818
  message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
807
819
  }
808
820
  };
@@ -816,14 +828,20 @@ function createFFIDClient(config) {
816
828
  const raw = await response.json();
817
829
  return {
818
830
  error: raw.error ?? {
819
- code: FFID_ERROR_CODES.UNKNOWN_ERROR,
831
+ code: errorCodes.UNKNOWN_ERROR,
820
832
  message: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
821
833
  }
822
834
  };
823
- } catch {
835
+ } catch (parseError) {
836
+ logger.error(
837
+ "\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F:",
838
+ parseError,
839
+ "Status:",
840
+ response.status
841
+ );
824
842
  return {
825
843
  error: {
826
- code: FFID_ERROR_CODES.PARSE_ERROR,
844
+ code: errorCodes.PARSE_ERROR,
827
845
  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})`
828
846
  }
829
847
  };
@@ -832,125 +850,233 @@ function createFFIDClient(config) {
832
850
  logger.debug("Response:", response.status);
833
851
  return { data: void 0 };
834
852
  }
835
- async function signOutToken() {
836
- const tokens = tokenStore.getTokens();
837
- tokenStore.clearTokens();
838
- if (!tokens) {
839
- logger.debug("No tokens to revoke");
840
- return { data: void 0 };
853
+ return { getSession, signOutCookie };
854
+ }
855
+
856
+ // src/auth/pkce.ts
857
+ var VERIFIER_STORAGE_KEY = "ffid_code_verifier";
858
+ var CODE_VERIFIER_MIN_LENGTH = 43;
859
+ var UNRESERVED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
860
+ function generateCodeVerifier() {
861
+ const length = CODE_VERIFIER_MIN_LENGTH;
862
+ const randomValues = new Uint8Array(length);
863
+ crypto.getRandomValues(randomValues);
864
+ let verifier = "";
865
+ for (let i = 0; i < length; i++) {
866
+ verifier += UNRESERVED_CHARS[randomValues[i] % UNRESERVED_CHARS.length];
867
+ }
868
+ return verifier;
869
+ }
870
+ async function generateCodeChallenge(verifier) {
871
+ const encoder = new TextEncoder();
872
+ const data = encoder.encode(verifier);
873
+ const digest = await crypto.subtle.digest("SHA-256", data);
874
+ return base64UrlEncode(digest);
875
+ }
876
+ function storeCodeVerifier(verifier) {
877
+ try {
878
+ if (typeof window === "undefined") return;
879
+ window.sessionStorage.setItem(VERIFIER_STORAGE_KEY, verifier);
880
+ } catch {
881
+ }
882
+ }
883
+ function retrieveCodeVerifier() {
884
+ try {
885
+ if (typeof window === "undefined") return null;
886
+ const verifier = window.sessionStorage.getItem(VERIFIER_STORAGE_KEY);
887
+ if (verifier) {
888
+ window.sessionStorage.removeItem(VERIFIER_STORAGE_KEY);
841
889
  }
842
- const url = `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`;
843
- logger.debug("Revoking token:", url);
890
+ return verifier;
891
+ } catch {
892
+ return null;
893
+ }
894
+ }
895
+ function base64UrlEncode(buffer) {
896
+ const bytes = new Uint8Array(buffer);
897
+ let binary = "";
898
+ for (let i = 0; i < bytes.length; i++) {
899
+ binary += String.fromCharCode(bytes[i]);
900
+ }
901
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
902
+ }
903
+
904
+ // src/client/redirect.ts
905
+ var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
906
+ var STATE_RANDOM_BYTES = 16;
907
+ var HEX_BASE2 = 16;
908
+ function generateRandomState() {
909
+ const array = new Uint8Array(STATE_RANDOM_BYTES);
910
+ crypto.getRandomValues(array);
911
+ return Array.from(array, (byte) => byte.toString(HEX_BASE2).padStart(2, "0")).join("");
912
+ }
913
+ function createRedirectMethods(deps) {
914
+ const {
915
+ authMode,
916
+ baseUrl,
917
+ clientId,
918
+ serviceCode,
919
+ resolvedRedirectUri,
920
+ logger
921
+ } = deps;
922
+ async function redirectToAuthorize() {
923
+ const verifier = generateCodeVerifier();
924
+ storeCodeVerifier(verifier);
844
925
  try {
845
- await fetch(url, {
846
- method: "POST",
847
- credentials: "omit",
848
- headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
849
- body: new URLSearchParams({
850
- token: tokens.accessToken,
851
- client_id: clientId
852
- }).toString()
926
+ const challenge = await generateCodeChallenge(verifier);
927
+ const state = generateRandomState();
928
+ const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
929
+ const params = new URLSearchParams({
930
+ response_type: "code",
931
+ client_id: clientId,
932
+ redirect_uri: redirectUri,
933
+ state,
934
+ code_challenge: challenge,
935
+ code_challenge_method: "S256"
853
936
  });
937
+ const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
938
+ logger.debug("Redirecting to authorize:", authorizeUrl);
939
+ window.location.href = authorizeUrl;
940
+ return true;
854
941
  } catch (error) {
855
- logger.warn("Token revocation failed:", error);
942
+ logger.error("PKCE \u30B3\u30FC\u30C9\u30C1\u30E3\u30EC\u30F3\u30B8\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
943
+ return false;
856
944
  }
857
- logger.debug("Token sign-out completed");
858
- return { data: void 0 };
859
945
  }
860
- async function exchangeCodeForTokens(code, codeVerifier) {
861
- const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
862
- logger.debug("Exchanging code for tokens:", url);
863
- const effectiveRedirectUri = resolvedRedirectUri ?? (typeof window !== "undefined" ? window.location.origin + window.location.pathname : null);
864
- if (!effectiveRedirectUri) {
865
- logger.error("redirectUri is required for token exchange in SSR environments. Set config.redirectUri explicitly.");
866
- return {
867
- error: {
868
- code: FFID_ERROR_CODES.TOKEN_EXCHANGE_ERROR,
869
- message: "redirectUri \u304C\u672A\u8A2D\u5B9A\u3067\u3059\u3002SSR\u74B0\u5883\u3067\u306F config.redirectUri \u3092\u660E\u793A\u7684\u306B\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
870
- }
871
- };
946
+ async function redirectToLogin() {
947
+ if (typeof window === "undefined") {
948
+ logger.debug("Cannot redirect in SSR context");
949
+ return false;
872
950
  }
873
- const body = {
874
- grant_type: "authorization_code",
875
- code,
876
- client_id: clientId,
877
- redirect_uri: effectiveRedirectUri
878
- };
879
- if (codeVerifier) {
880
- body.code_verifier = codeVerifier;
951
+ if (authMode === "token") {
952
+ return redirectToAuthorize();
881
953
  }
882
- let response;
883
- try {
884
- response = await fetch(url, {
885
- method: "POST",
886
- credentials: "omit",
887
- headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
888
- body: new URLSearchParams(body).toString()
889
- });
890
- } catch (error) {
891
- logger.error("Network error during token exchange:", error);
954
+ const currentUrl = window.location.href;
955
+ const loginUrl = `${baseUrl}/login?redirect=${encodeURIComponent(currentUrl)}&service=${encodeURIComponent(serviceCode)}`;
956
+ logger.debug("Redirecting to login:", loginUrl);
957
+ window.location.href = loginUrl;
958
+ return true;
959
+ }
960
+ function getLoginUrl(redirectUrl) {
961
+ const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
962
+ return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
963
+ }
964
+ function getSignupUrl(redirectUrl) {
965
+ const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
966
+ return `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
967
+ }
968
+ return { redirectToLogin, redirectToAuthorize, getLoginUrl, getSignupUrl };
969
+ }
970
+
971
+ // src/client/ffid-client.ts
972
+ var UNAUTHORIZED_STATUS2 = 401;
973
+ var SDK_LOG_PREFIX = "[FFID SDK]";
974
+ var noopLogger = {
975
+ debug: () => {
976
+ },
977
+ info: () => {
978
+ },
979
+ warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
980
+ error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
981
+ };
982
+ var consoleLogger = {
983
+ debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
984
+ info: (...args) => console.info(SDK_LOG_PREFIX, ...args),
985
+ warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
986
+ error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
987
+ };
988
+ var FFID_ERROR_CODES = {
989
+ NETWORK_ERROR: "NETWORK_ERROR",
990
+ PARSE_ERROR: "PARSE_ERROR",
991
+ UNKNOWN_ERROR: "UNKNOWN_ERROR",
992
+ TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
993
+ TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
994
+ NO_TOKENS: "NO_TOKENS",
995
+ TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
996
+ };
997
+ var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
998
+ function createFFIDClient(config) {
999
+ if (!config.serviceCode || !config.serviceCode.trim()) {
1000
+ throw new Error("FFID Client: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
1001
+ }
1002
+ const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
1003
+ const authMode = config.authMode ?? "cookie";
1004
+ const clientId = config.clientId ?? config.serviceCode;
1005
+ const resolvedRedirectUri = config.redirectUri ?? null;
1006
+ const serviceApiKey = config.serviceApiKey?.trim();
1007
+ const verifyStrategy = config.verifyStrategy ?? "jwt";
1008
+ const cache = config.cache;
1009
+ const timeout = config.timeout;
1010
+ if (authMode === "service-key" && !serviceApiKey) {
1011
+ throw new Error("FFID Client: service-key \u30E2\u30FC\u30C9\u3067\u306F serviceApiKey \u304C\u5FC5\u9808\u3067\u3059");
1012
+ }
1013
+ if (cache && cache.ttl <= 0) {
1014
+ throw new Error("FFID Client: cache.ttl \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
1015
+ }
1016
+ if (timeout !== void 0 && timeout <= 0) {
1017
+ throw new Error("FFID Client: timeout \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
1018
+ }
1019
+ const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
1020
+ const tokenStore = authMode === "token" ? createTokenStore() : createTokenStore("memory");
1021
+ function createError(code, message) {
1022
+ return { code, message };
1023
+ }
1024
+ function buildFetchOptions(options) {
1025
+ if (authMode === "service-key") {
892
1026
  return {
893
- error: {
894
- code: FFID_ERROR_CODES.NETWORK_ERROR,
895
- message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
1027
+ ...options,
1028
+ credentials: "omit",
1029
+ headers: {
1030
+ "Content-Type": "application/json",
1031
+ ...sdkHeaders(),
1032
+ "X-Service-Api-Key": serviceApiKey,
1033
+ ...options.headers
896
1034
  }
897
1035
  };
898
1036
  }
899
- let tokenResponse;
900
- try {
901
- tokenResponse = await response.json();
902
- } catch (parseError) {
903
- logger.error("Parse error during token exchange:", parseError);
904
- return {
905
- error: {
906
- code: FFID_ERROR_CODES.PARSE_ERROR,
907
- message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F (status: ${response.status})`
908
- }
1037
+ if (authMode === "token") {
1038
+ const tokens = tokenStore.getTokens();
1039
+ const headers = {
1040
+ "Content-Type": "application/json",
1041
+ ...sdkHeaders(),
1042
+ ...options.headers
909
1043
  };
910
- }
911
- if (!response.ok) {
912
- const errorBody = tokenResponse;
1044
+ if (tokens) {
1045
+ headers["Authorization"] = `Bearer ${tokens.accessToken}`;
1046
+ }
913
1047
  return {
914
- error: {
915
- code: errorBody.error ?? FFID_ERROR_CODES.TOKEN_EXCHANGE_ERROR,
916
- message: errorBody.error_description ?? "\u30C8\u30FC\u30AF\u30F3\u4EA4\u63DB\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
917
- }
1048
+ ...options,
1049
+ credentials: "omit",
1050
+ headers
918
1051
  };
919
1052
  }
920
- tokenStore.setTokens({
921
- accessToken: tokenResponse.access_token,
922
- refreshToken: tokenResponse.refresh_token,
923
- expiresAt: Date.now() + tokenResponse.expires_in * MS_PER_SECOND
924
- });
925
- logger.debug("Token exchange successful");
926
- return { data: void 0 };
1053
+ return {
1054
+ ...options,
1055
+ credentials: "include",
1056
+ headers: {
1057
+ "Content-Type": "application/json",
1058
+ ...sdkHeaders(),
1059
+ ...options.headers
1060
+ }
1061
+ };
927
1062
  }
928
- async function refreshAccessToken() {
929
- const tokens = tokenStore.getTokens();
930
- if (!tokens) {
931
- return {
932
- error: {
933
- code: FFID_ERROR_CODES.NO_TOKENS,
934
- message: "\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u304C\u3042\u308A\u307E\u305B\u3093"
935
- }
936
- };
937
- }
938
- const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
939
- logger.debug("Refreshing access token:", url);
1063
+ const { exchangeCodeForTokens, refreshAccessToken, signOutToken } = createOAuthTokenMethods({
1064
+ baseUrl,
1065
+ clientId,
1066
+ resolvedRedirectUri,
1067
+ tokenStore,
1068
+ logger,
1069
+ errorCodes: FFID_ERROR_CODES
1070
+ });
1071
+ async function fetchWithAuth(endpoint, options = {}) {
1072
+ const url = `${baseUrl}${endpoint}`;
1073
+ logger.debug("Fetching:", url);
1074
+ const fetchOptions = buildFetchOptions(options);
940
1075
  let response;
941
1076
  try {
942
- response = await fetch(url, {
943
- method: "POST",
944
- credentials: "omit",
945
- headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
946
- body: new URLSearchParams({
947
- grant_type: "refresh_token",
948
- refresh_token: tokens.refreshToken,
949
- client_id: clientId
950
- }).toString()
951
- });
1077
+ response = await fetch(url, fetchOptions);
952
1078
  } catch (error) {
953
- logger.error("Network error during token refresh:", error);
1079
+ logger.error("Network error:", error);
954
1080
  return {
955
1081
  error: {
956
1082
  code: FFID_ERROR_CODES.NETWORK_ERROR,
@@ -958,88 +1084,80 @@ function createFFIDClient(config) {
958
1084
  }
959
1085
  };
960
1086
  }
961
- let tokenResponse;
1087
+ if (authMode === "token" && response.status === UNAUTHORIZED_STATUS2) {
1088
+ const refreshResult = await refreshAccessToken();
1089
+ if (!refreshResult.error) {
1090
+ logger.debug("Token refreshed, retrying request");
1091
+ const retryOptions = buildFetchOptions(options);
1092
+ try {
1093
+ response = await fetch(url, retryOptions);
1094
+ } catch (retryError) {
1095
+ logger.error("Network error on retry:", retryError);
1096
+ return {
1097
+ error: {
1098
+ code: FFID_ERROR_CODES.NETWORK_ERROR,
1099
+ message: retryError instanceof Error ? retryError.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
1100
+ }
1101
+ };
1102
+ }
1103
+ }
1104
+ }
1105
+ let raw;
962
1106
  try {
963
- tokenResponse = await response.json();
1107
+ raw = await response.json();
964
1108
  } catch (parseError) {
965
- logger.error("Parse error during token refresh:", parseError);
1109
+ logger.error("Parse error:", parseError, "Status:", response.status);
966
1110
  return {
967
1111
  error: {
968
1112
  code: FFID_ERROR_CODES.PARSE_ERROR,
969
- message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F (status: ${response.status})`
1113
+ 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})`
970
1114
  }
971
1115
  };
972
1116
  }
1117
+ logger.debug("Response:", response.status, raw);
1118
+ checkVersionHeader(response, logger);
973
1119
  if (!response.ok) {
974
- const errorBody = tokenResponse;
975
- logger.error("Token refresh failed:", errorBody);
976
- const irrecoverableErrors = ["token_revoked", "invalid_grant"];
977
- if (errorBody.error && irrecoverableErrors.includes(errorBody.error)) {
978
- tokenStore.clearTokens();
979
- logger.debug("Cleared tokens due to irrecoverable refresh error:", errorBody.error);
980
- }
1120
+ return {
1121
+ error: raw.error ?? {
1122
+ code: FFID_ERROR_CODES.UNKNOWN_ERROR,
1123
+ message: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
1124
+ }
1125
+ };
1126
+ }
1127
+ if (raw.data === void 0) {
981
1128
  return {
982
1129
  error: {
983
- code: errorBody.error ?? FFID_ERROR_CODES.TOKEN_REFRESH_ERROR,
984
- message: errorBody.error_description ?? "\u30C8\u30FC\u30AF\u30F3\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
1130
+ code: FFID_ERROR_CODES.UNKNOWN_ERROR,
1131
+ message: "\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u30C7\u30FC\u30BF\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F"
985
1132
  }
986
1133
  };
987
1134
  }
988
- tokenStore.setTokens({
989
- accessToken: tokenResponse.access_token,
990
- refreshToken: tokenResponse.refresh_token,
991
- expiresAt: Date.now() + tokenResponse.expires_in * MS_PER_SECOND
992
- });
993
- logger.debug("Token refresh successful");
994
- return { data: void 0 };
1135
+ return { data: raw.data };
995
1136
  }
996
- function redirectToLogin() {
997
- if (typeof window === "undefined") {
998
- logger.debug("Cannot redirect in SSR context");
999
- return false;
1000
- }
1137
+ const { getSession, signOutCookie } = createSessionMethods({
1138
+ authMode,
1139
+ baseUrl,
1140
+ serviceCode: config.serviceCode,
1141
+ tokenStore,
1142
+ logger,
1143
+ fetchWithAuth,
1144
+ refreshAccessToken,
1145
+ errorCodes: FFID_ERROR_CODES
1146
+ });
1147
+ async function signOut() {
1001
1148
  if (authMode === "token") {
1002
- return redirectToAuthorize();
1149
+ return signOutToken();
1003
1150
  }
1004
- const currentUrl = window.location.href;
1005
- const loginUrl = `${baseUrl}/login?redirect=${encodeURIComponent(currentUrl)}&service=${encodeURIComponent(config.serviceCode)}`;
1006
- logger.debug("Redirecting to login:", loginUrl);
1007
- window.location.href = loginUrl;
1008
- return true;
1009
- }
1010
- function redirectToAuthorize() {
1011
- const verifier = generateCodeVerifier();
1012
- storeCodeVerifier(verifier);
1013
- generateCodeChallenge(verifier).then((challenge) => {
1014
- const state = generateRandomState();
1015
- const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
1016
- const params = new URLSearchParams({
1017
- response_type: "code",
1018
- client_id: clientId,
1019
- redirect_uri: redirectUri,
1020
- state,
1021
- code_challenge: challenge,
1022
- code_challenge_method: "S256"
1023
- });
1024
- const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
1025
- logger.debug("Redirecting to authorize:", authorizeUrl);
1026
- window.location.href = authorizeUrl;
1027
- }).catch((error) => {
1028
- logger.error("Failed to generate code challenge:", error);
1029
- });
1030
- return true;
1031
- }
1032
- function getLoginUrl(redirectUrl) {
1033
- const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
1034
- return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(config.serviceCode)}`;
1035
- }
1036
- function getSignupUrl(redirectUrl) {
1037
- const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
1038
- return `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(config.serviceCode)}`;
1039
- }
1040
- function createError(code, message) {
1041
- return { code, message };
1151
+ return signOutCookie();
1042
1152
  }
1153
+ const { redirectToLogin, getLoginUrl, getSignupUrl } = createRedirectMethods({
1154
+ authMode,
1155
+ baseUrl,
1156
+ clientId,
1157
+ serviceCode: config.serviceCode,
1158
+ resolvedRedirectUri,
1159
+ logger
1160
+ });
1043
1161
  async function checkSubscription(params) {
1044
1162
  if (!params.userId || !params.organizationId) {
1045
1163
  return {
@@ -1096,11 +1214,6 @@ function createFFIDClient(config) {
1096
1214
  redirectUri: resolvedRedirectUri
1097
1215
  };
1098
1216
  }
1099
- function generateRandomState() {
1100
- const array = new Uint8Array(STATE_RANDOM_BYTES);
1101
- crypto.getRandomValues(array);
1102
- return Array.from(array, (byte) => byte.toString(HEX_BASE2).padStart(2, "0")).join("");
1103
- }
1104
1217
  var DEFAULT_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
1105
1218
  var TOKEN_REFRESH_RATIO = 0.8;
1106
1219
  var FFIDContext = react.createContext(null);
@@ -1186,7 +1299,9 @@ function FFIDProvider({
1186
1299
  }
1187
1300
  }, [client]);
1188
1301
  const login = react.useCallback(() => {
1189
- client.redirectToLogin();
1302
+ client.redirectToLogin().catch((err) => {
1303
+ client.logger.error("\u30ED\u30B0\u30A4\u30F3\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", err);
1304
+ });
1190
1305
  }, [client]);
1191
1306
  const logout = react.useCallback(async () => {
1192
1307
  client.logger.debug("Signing out...");