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