Frame.io TypeScript SDK — 인증 가이드

이 가이드는 Frame.io TypeScript SDK(frameio)를 사용하여 Frame.io API로 인증하는 방법을 설명합니다. Frame.io V4 API는 Adobe의 OAuth 2.0 ID 플랫폼인 Adobe IMS(Identity Management Service)를 사용합니다. 이는 TypeScript/JavaScript 개발자를 위한 독립형 참조 문서입니다. 아래의 모든 코드 예제와 흐름은 frameio 패키지에만 해당됩니다.


TypeScript SDK의 인증 유형

TypeScript SDK는 4가지 OAuth 인증 클래스와 직접 토큰 사용을 지원합니다.

방법사용 사례사용자 상호 작용?클라이언트 시크릿 필요?
정적 토큰빠른 스크립트, 테스트 또는 이미 토큰이 있는 경우아니요아니요
서버 간 인증백엔드 서비스, cron 작업, 자동화아니요
웹 앱서버측 앱(Express, Fastify, Next.js)
SPA(PKCE)시크릿을 저장할 수 없는 브라우저 앱아니요
Native App(PKCE)사용자 지정 URI 스키마 리디렉션이 있는 데스크탑/모바일 애플리케이션아니요
서버 간 인증을 사용하면 앱이 사용자 상호 작용 없이 서비스 계정으로 작동할 수 있습니다. Adobe Admin Console을 통해 관리되는 Frame.io V4 계정에서만 사용할 수 있습니다. 웹 앱SPA를 사용하면 앱이 특정 사용자 역할을 수행할 수 있습니다. 둘 다 내부적으로 Adobe IMS를 사용합니다. 사용자가 앱을 인증하면 SDK가 결과 코드를 토큰으로 교환합니다. TypeScript SDK는 IMS /authorize/v2/token/v3 흐름을 처리합니다. 웹 앱의 경우 클라이언트 시크릿이 필요하고, SPA의 경우 PKCE를 대신 사용합니다. Native App은 SPA와 동일한 PKCE 흐름을 따르지만 Adobe가 Native App 자격 증명에 할당하는 adobe+<hash>://callback</hash> 리디렉션 URI를 사용합니다. 이를 통해 애플리케이션이 인증 후 OS 레벨에서 리디렉션을 가로막을 수 있습니다.

서비스 계정 사용자

서버 간 인증을 사용할 때 애플리케이션은 서비스 계정 사용자 역할을 합니다. 이는 서비스를 대신하여 작업을 수행할 수 있는 고유한 계정 유형입니다. 이 계정은 Frame.io의 다른 사용자에게 표시됩니다. 서비스 계정이 작업을 수행하면 UI에 해당 이름이 표시됩니다. Adobe Admin ConsoleDeveloper Console을 통해 서비스 계정 액세스 권한을 부여하고 취소할 수 있습니다. 서비스 계정 이름은 Frame.io UI에서 관리됩니다. 기본적으로 첫 번째 S2S 연결의 이름은 Service Account User, 두 번째는 Service Account User 2 등으로 지정됩니다.


빠른 시작

사전 요구 사항

  1. Adobe Developer Console자격 증명:
  • 클라이언트 ID — 모든 OAuth 흐름에 필요 - 클라이언트 시크릿 — 서버 간 및 웹 앱 흐름에 필요 - 리디렉션 URI — 웹 앱, SPA, 네이티브 앱 흐름에 필요하며, Adobe 프로젝트에 등록되어야 함
  1. SDK 설치:
$npm install frameio

방법 선택

  • 사용자가 개입하지 않습니까? 서버 간(ServerToServerAuth) 방식을 사용하세요.
  • 사용자가 개입하며 시크릿을 저장할 수 있습니까? 웹 앱(WebAppAuth) 방식을 사용하세요.
  • 사용자가 개입하지만 시크릿을 저장할 수 없습니까? 브라우저 앱의 경우 SPA(SPAAuth)를 사용하고, 데스크톱/모바일 앱의 경우 네이티브 앱(NativeAppAuth)을 사용하세요.

액세스 토큰

(다른 OAuth 시스템 또는 이전 교환(예: API 탐색기)에서 발급받은) 액세스 토큰이 이미 있는 경우 이를 직접 전달할 수 있습니다.

1import { FrameioClient } from "frameio";
2
3const client = new FrameioClient({ token: "YOUR_ACCESS_TOKEN" });

이것이 가장 간단한 방식이지만, 토큰은 결국 만료되며 SDK가 이를 대신 새로 고쳐주지 않습니다.

이전 개발자 토큰

아직 Adobe Admin Console을 통해 관리되지 않는 V4 마이그레이션 계정의 경우, Frame.io 개발자 사이트에서 발급받은 이전 개발자 토큰을 계속 사용할 수 있습니다. 반드시 x-frameio-legacy-token-auth 헤더를 포함하고 이를 true로 설정해야 합니다.

1import { FrameioClient } from "frameio";
2
3const client = new FrameioClient({
4 token: "YOUR_LEGACY_DEVELOPER_TOKEN",
5 headers: { "x-frameio-legacy-token-auth": "true" },
6});

이전 개발자 토큰은 만료되지 않지만 이는 임시적인 전환 메커니즘입니다. 새로운 통합 및 프로덕션 워크로드의 경우 아래의 OAuth 2.0 흐름 중 하나를 사용하는 것이 좋습니다. 자세한 내용은 마이그레이션 가이드를 참조하세요.


서버 간 방식(클라이언트 자격 증명)

사용자 상호 작용 없이 Frame.io 액세스가 필요한 백엔드 서비스 및 스크립트에 이 방식을 사용하세요. 이 흐름은 Adobe Admin Console을 통해 관리되는 Frame.io V4 계정에서만 사용할 수 있습니다. 애플리케이션은 사용자 개입 없이 서비스 계정 사용자로 인증됩니다.

1import { FrameioClient, ServerToServerAuth } from "frameio";
2
3const auth = new ServerToServerAuth({
4 clientId: "YOUR_CLIENT_ID",
5 clientSecret: "YOUR_CLIENT_SECRET",
6});
7
8const client = new FrameioClient({ token: () => auth.getToken() });

이것이 전부입니다. auth.getToken()은 모든 요청에서 SDK가 호출하는 비동기 함수입니다. 현재 토큰이 여전히 유효하면 즉시 반환됩니다. 토큰이 만료될 예정이면 먼저 완벽하게 투명한 방식으로 새 토큰을 가져옵니다.

작동 방식

클라이언트 자격 증명(클라이언트 ID + 시크릿)은 절대 만료되지 않습니다. 보안 관리를 위해 수동으로만 순환시킵니다. S2S는 수동 개입 없이 사실상 영구적이고 중단 없는 API 액세스를 제공합니다.

내부 동작 방식:

  1. 첫 번째 API 호출 시, getToken()client_credentials 권한 부여를 사용하여 Adobe IMS에 새 액세스 토큰을 요청합니다.
  2. 토큰은 메모리에 캐시됩니다. 개별 액세스 토큰은 만료(일반적으로 24시간)되지만 이는 자동으로 처리됩니다.
  3. 캐시된 토큰이 새로 고침 버퍼(기본값: 만료 60초 전) 내에 있으면, SDK는 동일한 클라이언트 자격 증명을 사용하여 새 토큰을 자동으로 가져옵니다.
  4. 새로 고침 토큰은 관여하지 않습니다. 클라이언트 자격 증명 자체가 수명이 긴 시크릿이며, 이를 통해 언제든 새 액세스 토큰을 발행할 수 있습니다.

명시적 인증

(예를 들어 시작 시 잘못된 자격 증명에 대해 빠르게 실패 처리를 하기 위해) 즉시 토큰을 가져오려는 경우는 다음과 같습니다.

1const auth = new ServerToServerAuth({ clientId: "...", clientSecret: "..." });
2await auth.authenticate(); // throws AuthenticationError if credentials are invalid
3const client = new FrameioClient({ token: () => auth.getToken() });

웹 앱(권한 부여 코드)

사용자가 자신의 Adobe ID로 로그인하는 서버 측 애플리케이션에 이 방식을 사용하세요. 이 흐름에는 클라이언트 시크릿이 필요하며, 이는 서버에 안전하게 저장되어야 합니다.

1

사용자를 Adobe IMS로 리디렉션

1 import { WebAppAuth } from "frameio";
2 import crypto from "crypto";
3
4 const auth = new WebAppAuth({
5 clientId: "YOUR_CLIENT_ID",
6 clientSecret: "YOUR_CLIENT_SECRET",
7 redirectUri: "https://yourapp.com/callback",
8 });
9
10 // Generate a cryptographically random state value to prevent CSRF attacks
11 const state = crypto.randomBytes(32).toString("hex");
12
13 const authorizationUrl = auth.getAuthorizationUrl({ state });
14 // Store `state` in the user's session, then redirect them to `authorizationUrl`
2

콜백 처리

Adobe IMS가 사용자를 redirectUri로 리디렉션하면 codestate 매개변수를 추출합니다. 저장한 내용과 state가 일치하는지 확인한 다음, 코드를 토큰으로 교환합니다.

1 // In your callback handler (e.g. an Express route):
2 await auth.exchangeCode(req.query.code as string);

이는 권한 부여 코드를 액세스 토큰 및 새로 고침 토큰으로 교환하고 두 가지를 내부적으로 저장합니다.

3

클라이언트 사용

1 import { FrameioClient } from "frameio";
2
3 const client = new FrameioClient({ token: () => auth.getToken() });

이것이 전부입니다. 이 시점부터 getToken()이 토큰 수명 주기를 자동으로 관리합니다. 액세스 토큰의 만료가 임박하면 SDK는 새로 고침 토큰을 사용하여 새 토큰을 가져옵니다. 사용자 상호 작용이 필요하지 않습니다.

전체 Express 예제

1import crypto from "crypto";
2import express from "express";
3import session from "express-session";
4import { FrameioClient, WebAppAuth } from "frameio";
5
6const auth = new WebAppAuth({
7 clientId: "YOUR_CLIENT_ID",
8 clientSecret: "YOUR_CLIENT_SECRET",
9 redirectUri: "http://localhost:3000/callback",
10});
11
12const app = express();
13app.use(session({ secret: crypto.randomBytes(32).toString("hex"), resave: false, saveUninitialized: false }));
14
15app.get("/login", (req, res) => {
16 const state = crypto.randomBytes(32).toString("hex");
17 (req.session as any).oauthState = state;
18 res.redirect(auth.getAuthorizationUrl({ state }));
19});
20
21app.get("/callback", async (req, res) => {
22 if (req.query.state !== (req.session as any).oauthState) {
23 return res.status(403).send("Invalid state parameter");
24 }
25
26 await auth.exchangeCode(req.query.code as string);
27
28 const client = new FrameioClient({ token: () => auth.getToken() });
29 const accounts = await client.accounts.index();
30 res.json(accounts);
31});
32
33app.listen(3000);

단일 페이지 앱 / PKCE(권한 부여 코드 + PKCE)

클라이언트 시크릿을 안전하게 저장할 수 없는 브라우저 기반 애플리케이션, 데스크톱 앱, 또는 CLI 도구에 이 방식을 사용하세요. 이 흐름은 PKCE(RFC 7636)를 사용하여 권한 부여 코드 교환을 보호합니다.

1

권한 부여 URL 생성

1 import { SPAAuth } from "frameio";
2
3 const auth = new SPAAuth({
4 clientId: "YOUR_CLIENT_ID",
5 redirectUri: "https://yourapp.com/callback",
6 });
7
8 const state = crypto.randomUUID();
9 const result = await auth.getAuthorizationUrl({ state });
10 // result.url -> redirect the user here
11 // result.codeVerifier -> store this securely until the callback

getAuthorizationUrl은 (PKCE code_challenge가 임베드된) 전체 URL 및 다음 단계에서 필요한 codeVerifier가 포함된 AuthorizationUrlResult를 반환합니다.

2

검증자를 사용하여 코드 교환

사용자가 다시 리디렉션된 경우:

1 await auth.exchangeCode({
2 code: "CODE_FROM_CALLBACK",
3 codeVerifier: result.codeVerifier,
4 });
3

클라이언트 사용

1 import { FrameioClient } from "frameio";
2
3 const client = new FrameioClient({ token: () => auth.getToken() });

이것이 전부입니다. 새로 고침은 웹 앱 방식과 동일하게 작동하며, SDK가 새로 고침 토큰을 자동으로 사용합니다. 차이점은 SPA 흐름이 공개 클라이언트용으로 설계되었기 때문에 새로 고침 중에 클라이언트 시크릿이 전송되지 않는다는 점입니다.

codeVerifier는 권한 부여 요청과 코드 교환 사이에 클라이언트 측에 안전하게 저장되어야 합니다. 브라우저 앱에서는 sessionStorage 또는 동등한 기능을 사용하세요.


네이티브 앱(권한 부여 코드 + PKCE)

데스크톱 및 모바일 애플리케이션에 이 방식을 사용하세요. Adobe Developer Console에서 네이티브 앱 자격 증명을 생성하면 Adobe에서 adobe+<hash>://callback</hash> 형태의 리디렉션 URI를 할당합니다. 귀하는 OS 수준에서 해당 사용자 지정 URI 스킴(scheme)을 처리하도록 애플리케이션을 등록하게 됩니다. 루프백 리디렉션(http://127.0.0.1:<port>/callback</port>) 역시 로컬 개발을 위해 지원됩니다. 이 흐름은 SPA와 동일하며, 클라이언트 시크릿 없이 PKCE를 사용합니다.

1import { NativeAppAuth } from "frameio";
2
3const auth = new NativeAppAuth({
4 clientId: "YOUR_CLIENT_ID",
5 redirectUri: "adobe+abc123def456://callback", // from your Adobe Developer Console Native App credential
6 // Also supports loopback: "http://127.0.0.1:8080/callback"
7});
8
9const { url, codeVerifier } = await auth.getAuthorizationUrl({
10 state: crypto.randomUUID(),
11});
12
13// Open system browser to `url`
14// Listen for redirect on your custom URI scheme or loopback server
15
16await auth.exchangeCode({ code: "CODE_FROM_REDIRECT", codeVerifier });
17const client = new FrameioClient({ token: () => auth.getToken() });

리디렉션 URI 규칙

Adobe는 Adobe Developer Console에서 자격 증명을 등록할 때와 redirect_uri 매개변수가 /authorize/v2 엔드포인트에 도달할 때, 총 두 번에 걸쳐 리디렉션 URI 규칙을 시행합니다. 이 SDK에서 redirectUri에 전달하는 값은 귀하가 자격 증명에 등록한 “리디렉션 URI 패턴” 중 하나와 일치해야 합니다. 그렇지 않으면 Adobe는 이를 무시하고 자격 증명에 등록된 기본 리디렉션 URI로 대신 리디렉션합니다.

  • 웹 앱SPA 자격 증명에는 HTTPS가 필요합니다.
  • 네이티브 앱 자격 증명은 비-HTTPS 리디렉션을 사용합니다 — 일반적으로 adobe+<hash>://callback</hash>과 같이 자격 증명을 위해 개발자 콘솔에 표시된 URI를 사용합니다.

귀하의 자격 증명에 허용되는 정확한 패턴은 Adobe Developer Console에서 확인하세요.

Python SDK에는 네이티브 앱 자격 증명 클래스가 포함되어 있지 않습니다. Python에는 사용자 지정

URI 체계 핸들러를 등록하는 표준화된 방법이 없기 때문입니다. TypeScript SDK는 네이티브 앱을 포함한 4가지 자격 증명 유형을 모두 지원합니다.


수동 토큰 새로 고침

웹 앱, SPA 및 네이티브 앱 흐름의 경우, SDK는 getToken()을 통해 토큰을 자동으로 새로 고칩니다. 명시적인 제어가 필요한 경우 refresh()를 직접 호출할 수 있습니다.

1await auth.refresh(); // fetches a new access token using the refresh token

이 기능은 자동 새로 고침 버퍼에 의존하는 대신 중요한 작업을 앞두고 강제로 새로 고치고자 할 때 유용합니다.

refresh()WebAppAuth, SPAAuth, NativeAppAuth에서 사용할 수 있습니다. 새로 고침 토큰을 사용할 수 없는 경우 ConfigurationError를 발생시킵니다(즉, 먼저 exchangeCode()를 호출해야 함). ServerToServerAuth에는 refresh() 메서드가 없습니다 — 대신 authenticate()를 사용하여 클라이언트 자격 증명을 통해 새 토큰을 가져옵니다.


토큰 지속성

모든 인증 클래스는 재시작 시에도 토큰 상태를 유지할 수 있도록 exportTokens()importTokens()를 지원합니다. 웹 앱, SPA, 네이티브 앱 흐름의 경우 액세스 토큰과 새로 고침 토큰이 기본적으로 메모리에 상주하므로 이는 특히 중요합니다. 이를 유지하지 않으면 애플리케이션이 다시 시작될 때 사용자가 재인증을 해야 합니다. 서버 간(S2S) 방식의 경우 지속성은 선택 사항이지만(클라이언트 자격 증명으로 항상 새 토큰을 발행할 수 있음), 캐시된 토큰을 가져오면 시작 시 한 번의 추가 왕복을 피할 수 있습니다.

내보내기 및 가져오기

1// After exchangeCode(), save the token state
2const tokenData = auth.exportTokens();
3// tokenData is: { access_token: "...", refresh_token: "...", expires_at: 1234567890.0 }
4// Save it to your database, file, or secret store
5
6// On next startup, restore it
7auth.importTokens(tokenData);
8const client = new FrameioClient({ token: () => auth.getToken() });
9// The SDK will automatically refresh if the token is near expiry

내보낸 토큰을 안전하게 보관하세요. 이 토큰에는 API 액세스 권한을 부여하는 액세스 토큰과 새로 고침 토큰이 포함되어 있습니다. 프로덕션 환경에서는 토큰을

일반 텍스트 파일에 저장하지 마세요.

onTokenRefreshed를 활용한 자동 지속성

토큰이 새로 고쳐질 때마다 자동으로 유지하려면 onTokenRefreshed 콜백을 사용합니다.

1import fs from "fs/promises";
2
3const TOKEN_FILE = "tokens.json";
4
5const auth = new WebAppAuth({
6 clientId: "...",
7 clientSecret: "...",
8 redirectUri: "...",
9 onTokenRefreshed: (tokens) => {
10 fs.writeFile(TOKEN_FILE, JSON.stringify(tokens));
11 },
12});
13
14// On startup, restore if available
15try {
16 const saved = JSON.parse(await fs.readFile(TOKEN_FILE, "utf-8"));
17 auth.importTokens(saved);
18} catch {
19 // No saved tokens — user will need to authenticate
20}

콜백은 exportTokens()와 동일한 형태를 수신하며, 토큰 새로 고침이 성공할 때마다 실행됩니다.


토큰 해지

사용자를 로그아웃하고 Adobe IMS에서 해당 토큰을 무효화하려면 다음을 실행합니다.

1await auth.revoke();

이는 액세스 토큰 및 새로 고침 토큰 모두에 대해 최선의 노력으로 Adobe IMS에 해지 요청을 보낸 다음 모든 로컬 토큰 상태를 지웁니다. 기밀 클라이언트(WebAppAuth)의 경우 해지 요청 시 HTTP Basic Auth를 사용하며, 공개 클라이언트(SPAAuth, NativeAppAuth)의 경우 client_id가 쿼리 매개변수로 전송됩니다. 해지 과정의 오류는 로그로 기록되지만 에러를 발생시키지는 않습니다. 해지 후에는 사용자가 다시 인증을 거쳐야 합니다.


오류 처리

모든 인증 오류는 FrameioAuthError에서 상속되므로, 이를 포괄적으로 잡거나 특정 케이스를 분리하여 처리할 수 있습니다.

1import {
2 FrameioAuthError,
3 AuthenticationError,
4 TokenExpiredError,
5 ConfigurationError,
6 NetworkError,
7 RateLimitError,
8} from "frameio";
9
10try {
11 await auth.exchangeCode("...");
12} catch (error) {
13 if (error instanceof TokenExpiredError) {
14 // The refresh token has expired; redirect the user to sign in again
15 } else if (error instanceof AuthenticationError) {
16 // Token exchange failed
17 console.error(`Error: ${error.errorCode} - ${error.errorDescription}`);
18 } else if (error instanceof NetworkError) {
19 // Timeout or connection failure (after retries)
20 } else if (error instanceof RateLimitError) {
21 // 429 from Adobe IMS; retry after error.retryAfter seconds
22 } else if (error instanceof FrameioAuthError) {
23 // Catch-all for any other auth error
24 }
25}

오류 참조

예외발생 조건
ConfigurationError누락되었거나 유효하지 않은 구성(예: 비어 있는 clientId, HTTPS가 아닌 리디렉션 URI, HTTPS가 아닌 imsBaseUrl)
AuthenticationErrorAdobe IMS에서 토큰 교환 또는 새로 고침 거절(.errorCode.errorDescription 속성 포함)
TokenExpiredError새로 고침 토큰 자체가 만료됨. 사용자가 다시 인증해야 함
NetworkError모든 재시도 후에도 HTTP 시간 초과 또는 연결 실패 발생
RateLimitErrorAdobe IMS에서 429 반환. 백오프 지침을 위해 .retryAfter 확인 필요
PKCEErrorPKCE 흐름에서 소비자가 사용할 수 있도록 제공됨. SDK 내부적으로 발생하지는 않음

프로덕션 환경에서 만료된 새로 고침 토큰 처리하기

웹 앱, SPA, 네이티브 앱 흐름의 경우 새로 고침 토큰은 결국 만료됩니다. 이 경우 getToken()TokenExpiredError를 발생시킵니다. 이를 잡아내어 사용자를 권한 부여 흐름을 통해 다시 리디렉션해야 합니다.

1import { TokenExpiredError } from "frameio";
2
3try {
4 const client = new FrameioClient({ token: () => auth.getToken() });
5 const assets = await client.files.list({ projectId: "..." });
6} catch (error) {
7 if (error instanceof TokenExpiredError) {
8 // Clear persisted tokens and redirect user to login
9 await auth.revoke();
10 return res.redirect("/login");
11 }
12}

구성 참조

이러한 매개변수들은 합리적인 기본값을 가지고 있어 변경해야 할 일이 드뭅니다. 스테이징 IMS를 가리키거나, 사용자 지정 fetch를 주입하거나, 시간 초과를 조정하거나, 또는 로거를 연결하는 등 동작을 사용자 지정해야 하는 경우, 인증 클래스를 구성할 때 선택적 매개변수로 전달할 수 있습니다.

매개 변수Default설명
범위흐름별 기본값공백으로 구분된 OAuth 권한입니다. S2S의 기본값은 openid AdobeID frame.s2s.all이며, 사용자 대면 흐름의 기본값은 openid email profile offline_access additional_info.roles입니다.
imsBaseUrlhttps://ims-na1.adobelogin.comAdobe IMS 기본 URL입니다. 스테이징 또는 비프로덕션 환경을 위해 이를 재정의할 수 있습니다. HTTPS를 사용해야 합니다.
fetchglobalThis.fetch프록시, mTLS, 또는 사용자 지정 HTTP 처리를 위한 사용자 지정 fetch 구현입니다.
timeout30000토큰 엔드포인트 호출에 대한 HTTP 요청 시간 초과(밀리초 단위)입니다.
maxRetries2일시적인 오류(5xx, 시간 초과) 발생 시 최대 재시도 횟수입니다. 속도 제한 재시도(429)는 별도로 추적됩니다.
refreshBuffer60사전 새로 고침을 트리거하기 위해 토큰이 만료되기 전까지 남은 시간(초 단위)입니다.
onTokenRefreshedundefined토큰 새로 고침이 성공할 때마다 실행되는 콜백입니다. access_token, refresh_token, expires_at이 포함된 오브젝트를 수신합니다.
loggerNo-op(무음)debug,, info, warn, error 메서드(예: console, pino, winston)를 포함하는 로거 인스턴스입니다.

스테이징 환경

imsBaseUrl을 재정의하여 스테이징 Adobe IMS 인스턴스를 지정합니다. 또한 프로그래밍 방식으로 프로덕션 값을 참조해야 하는 경우 SDK는 DEFAULT_IMS_BASE_URL(https://ims-na1.adobelogin.com)을 내보냅니다.

1const auth = new ServerToServerAuth({
2 clientId: "...",
3 clientSecret: "...",
4 imsBaseUrl: "https://ims-na1-stg1.adobelogin.com",
5});

사용자 지정 가져오기

프록시 지원 또는 사용자 지정 TLS 구성에 사용됩니다.

1import { ProxyAgent } from "undici";
2
3const proxyAgent = new ProxyAgent("http://corporate-proxy:8080");
4
5const auth = new ServerToServerAuth({
6 clientId: "...",
7 clientSecret: "...",
8 fetch: (url, init) => fetch(url, { ...init, dispatcher: proxyAgent }),
9});

동시성 안전

TypeScript SDK는 동시에 사용해도 안전합니다. 여러 개의 getToken() 호출이 동시에 발생하고 새로 고침이 필요한 경우 새로 고침 요청은 하나만 발생합니다. 나머지 호출은 동일한 프로미스를 대기하다가 동일한 결과를 받게 됩니다. 외부 잠금이 필요하지 않습니다. 이러한 중복 제거는 JavaScript의 단일 스레드 이벤트 루프와 공유 Promise를 사용합니다. 이미 새로 고침이 진행 중인 경우, 동시 호출자들은 두 번째 요청을 시작하는 대신 기존 요청에 합류합니다. 새로 고침이 진행 중일 때 revoke()가 호출되면, 해당 새로 고침은 AuthenticationError와 함께 거부되고 토큰은 지워진 상태로 유지됩니다. 즉, 해지가 항상 우선시됩니다.