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