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