> This page is for プラットフォーム, version V4 (default).
> For other versions, use one of these documentation indexes:
> - V4 (default): https://next.developer.frame.io/platform/v4/llms.txt
> - V4 Experimental: https://next.developer.frame.io/platform/v4-experimental/llms.txt
> - レガシー: https://next.developer.frame.io/platform/v2/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://next.developer.frame.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://next.developer.frame.io/_mcp/server.

# Frame.io TypeScript SDK — 認証ガイド

このガイドでは、**Frame.io TypeScript SDK**（`frameio`）を使用してFrame.io APIで認証する方法について説明します。 Frame.io V4 APIは[Adobe Identity Management Service（IMS）](https://developer.adobe.com/developer-console/docs/guides/authentication/)、アドビのOAuth 2.0アイデンティティプラットフォームを使用します。 これはTypeScript/JavaScript開発者向けのスタンドアロンリファレンスです。 以下のすべてのコード例と流量は`frameio`パッケージのみに対応しています。

---

## TypeScript SDKの認証タイプ

TypeScript SDKは4つのOAuth認証クラスに加えて、直接トークン使用をサポートしています：

| メソッド                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | 使用事例                                   | ユーザーとのやり取りが必要？ | クライアントシークレットが必要？ |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | -------------- | ---------------- |
| **静的トークン**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | クイックスクリプト、テスト、または既にトークンを持っている場合        | －              | －                |
| **サーバー間**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | バックエンドサービス、cronジョブ、自動化                 | －              | はい               |
| **web アプリ**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | サーバーサイドアプリ（Express、Fastify、Next.js）    | はい             | はい               |
| **SPA（PKCE）**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | シークレットを保存できないブラウザーアプリ                  | はい             | －                |
| **ネイティブアプリ（PKCE）**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | カスタム URI スキームリダイレクトを使用するデスクトップ/モバイルアプリ | はい             | －                |
| **Server-to-Server** では、ユーザーの操作なしでアプリをサービスアカウントとして動作させることができます。 これは、[Adobe Admin Console](https://adminconsole.adobe.com/) で管理される Frame.io V4 アカウントでのみ利用できます。 **Web アプリ** と **SPA** では、アプリを特定のユーザーとして動作させることができます。 どちらも内部的に Adobe IMS を使用します。ユーザーがアプリを認証し、SDK が結果のコードをトークンと交換します。 TypeScript SDK が IMS `/authorize/v2` および `/token/v3` 流量を処理します。 Web アプリにはクライアントシークレットが必要です。SPA では代わりに [PKCE](https://datatracker.ietf.org/doc/html/rfc7636) を使用します。 **ネイティブアプリ** は SPA と同じ PKCE 流量に従いますが、`adobe+<hash>://callback の形式のリダイレクト URI を割り当てます</hash>` リダイレクト URI を使用します。これは、アドビがネイティブアプリ資格情報に割り当てるものです。 これにより、認証後に OS レベルでアプリケーションがリダイレクトをインターセプトできます。 |                                        |                |                  |

---

## サービスアカウントユーザー

Server-to-Server認証を使用する場合、アプリケーションは**サービスアカウントユーザー**として動作します。これは、サービスに代わってアクションを実行できる独特なアカウントタイプです。 これらはFrame.ioの他のユーザーに表示されます。サービスアカウントがアクションを実行すると、その名前がUIに表示されます。 [Adobe Admin Console](https://adminconsole.adobe.com/)と[Developer Console](https://developer.adobe.com/console)を通じてサービスアカウントアクセスを付与および取り消すことができます。 サービスアカウント名はFrame.io UIから管理されます。 最初に作成する S2S 接続は、デフォルトで **Service Account User**\*\* と呼ばれ、2 番目は デフォルトで \*\*Service Account User 2 と呼ばれます。

> **Info**
>
> 詳細については、[Frame.io サーバー間サポートを使用した設定の自動化](https://helpx.adobe.com/enterprise/using/automate-using-frame-io.html)を参照してください。

---

## クイックスタート

### 前提条件

1. **Adobe Developer Console**からの[資格情報](https://developer.adobe.com/console)：

* **Client ID** — すべてのOAuthフローに必要 - **Client Secret** — Server-to-ServerおよびWeb Appフローに必要 - **Redirect URI** — Web App、SPA、およびNative Appフローに必要。Adobeプロジェクトに登録する必要があります

2. **SDKをインストール：**

```bash
npm install frameio
```

### 方法の選択

* **ユーザーが関与しない場合** **Server-to-Server**（`ServerToServerAuth`）を使用します。
* **ユーザーが関与し、シークレットを保存できる場合** **Web App**（`WebAppAuth`）を使用します。
* **ユーザーが関与するが、シークレットを保存できない場合** ブラウザーアプリには**SPA**（`SPAAuth`）を、デスクトップ/モバイルアプリには**Native App**（`NativeAppAuth`）を使用します。

---

## アクセストークン

既にアクセストークンがある場合（他のOAuthシステムまたは以前の交換から、例えば[API Explorer](/platform/api-reference/accounts/index?explorer=true)経由）、直接渡すことができます：

```typescript
import { FrameioClient } from "frameio";

const client = new FrameioClient({ token: "YOUR_ACCESS_TOKEN" });
```

これは最もシンプルなアプローチですが、トークンは最終的に期限切れになり、SDKは自動的に更新しません。

### レガシーデベロッパートークン

[Adobe Admin Console](https://adminconsole.adobe.com/)でまだ管理されていないV4移行アカウントの場合、[Frame.io developer site](https://developer.frame.io/app/tokens)からのレガシーデベロッパートークンを引き続き使用できます。 `x-frameio-legacy-token-auth`ヘッダーを含め、`true`に設定する必要があります：

```typescript
import { FrameioClient } from "frameio";

const client = new FrameioClient({
    token: "YOUR_LEGACY_DEVELOPER_TOKEN",
    headers: { "x-frameio-legacy-token-auth": "true" },
});
```

レガシーデベロッパートークンは期限切れになりませんが、移行メカニズムです。 新しい統合と本番ワークロードには、以下のOAuth 2.0フローのいずれかを使用することをお勧めします。 詳細については、[移行ガイド](/platform/docs/resources/migration#authentication)を参照してください。

---

## Server-to-Server（クライアント資格情報）

ユーザーの操作なしにFrame.ioアクセスが必要なバックエンドサービスやスクリプトに使用します。 S2S 認証は、[Adobe Admin Console](https://adminconsole.adobe.com/) で管理される Frame.io V4 アカウントでのみ利用できます。 アプリケーションは、人間のループなしで[サービスアカウントユーザー](#service-account-users)として認証されます。

```typescript
import { FrameioClient, ServerToServerAuth } from "frameio";

const auth = new ServerToServerAuth({
    clientId: "YOUR_CLIENT_ID",
    clientSecret: "YOUR_CLIENT_SECRET",
});

const 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. リフレッシュトークンは関与しません。 クライアント資格情報自体が長期間有効なシークレットであり、常に新しいアクセストークンを作成するために使用できます。

### 明示的な認証

トークンを積極的に取得したい場合（例えば、起動時に不正な資格情報で高速に失敗させるため）：

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

---

## web アプリ（認証コード）

ユーザーがAdobe IDでログインするサーバーサイドアプリケーションに使用します。 このフローにはクライアントシークレットが必要で、サーバーに安全に保存する必要があります。

#### ユーザーをAdobe IMSにリダイレクトします

```typescript
    import { WebAppAuth } from "frameio";
    import crypto from "crypto";

    const auth = new WebAppAuth({
        clientId: "YOUR_CLIENT_ID",
        clientSecret: "YOUR_CLIENT_SECRET",
        redirectUri: "https://yourapp.com/callback",
    });

    // Generate a cryptographically random state value to prevent CSRF attacks
    const state = crypto.randomBytes(32).toString("hex");

    const authorizationUrl = auth.getAuthorizationUrl({ state });
    // Store `state` in the user's session, then redirect them to `authorizationUrl`
```

#### コールバックを処理

Adobe IMS がユーザーを `redirectUri` にリダイレクトした際、`code` と `state` パラメーターを抽出します。 state が保存したものと一致することを確認してから、code をトークンと交換します：

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

これにより認証コードをアクセストークンとリフレッシュトークンに交換し、両方を内部に保存します。

#### クライアントを使用

```typescript
    import { FrameioClient } from "frameio";

    const client = new FrameioClient({ token: () => auth.getToken() });
```

これで完了です。 この時点から、`getToken()` がトークンのライフサイクルを自動的に管理します。 アクセストークンの有効期限が近づくと、SDK はリフレッシュトークンを使用して新しいトークンを取得します。 ユーザーの操作は不要です。

### 完全な Express の例

```typescript
import crypto from "crypto";
import express from "express";
import session from "express-session";
import { FrameioClient, WebAppAuth } from "frameio";

const auth = new WebAppAuth({
    clientId: "YOUR_CLIENT_ID",
    clientSecret: "YOUR_CLIENT_SECRET",
    redirectUri: "http://localhost:3000/callback",
});

const app = express();
app.use(session({ secret: crypto.randomBytes(32).toString("hex"), resave: false, saveUninitialized: false }));

app.get("/login", (req, res) => {
    const state = crypto.randomBytes(32).toString("hex");
    (req.session as any).oauthState = state;
    res.redirect(auth.getAuthorizationUrl({ state }));
});

app.get("/callback", async (req, res) => {
    if (req.query.state !== (req.session as any).oauthState) {
        return res.status(403).send("Invalid state parameter");
    }

    await auth.exchangeCode(req.query.code as string);

    const client = new FrameioClient({ token: () => auth.getToken() });
    const accounts = await client.accounts.index();
    res.json(accounts);
});

app.listen(3000);
```

---

## シングルページアプリケーション / PKCE（認証コード + PKCE）

ブラウザーベースのアプリケーション、デスクトップアプリケーション、またはクライアントシークレットを安全に保存できない CLI ツールに使用します。 この流量は [PKCE (RFC 7636)](https://datatracker.ietf.org/doc/html/rfc7636) を使用して認証コード交換を保護します。

#### 認証 URL を生成

```typescript
    import { SPAAuth } from "frameio";

    const auth = new SPAAuth({
        clientId: "YOUR_CLIENT_ID",
        redirectUri: "https://yourapp.com/callback",
    });

    const state = crypto.randomUUID();
    const result = await auth.getAuthorizationUrl({ state });
    // result.url            -> redirect the user here
    // result.codeVerifier   -> store this securely until the callback
```

`getAuthorizationUrl` は完全な URL（PKCE `code_challenge` が埋め込まれた）と次のステップで必要な `codeVerifier` を含む `AuthorizationUrlResult` を返します。

#### 検証機能付きでコードを交換

ユーザーがリダイレクトされた際：

```typescript
    await auth.exchangeCode({
        code: "CODE_FROM_CALLBACK",
        codeVerifier: result.codeVerifier,
    });
```

#### クライアントを使用

```typescript
    import { FrameioClient } from "frameio";

    const client = new FrameioClient({ token: () => auth.getToken() });
```

これで完了です。 リフレッシュは web アプリと同じように動作します — SDK がリフレッシュトークンを自動的に使用します。 違いは、SPA 流量がパブリッククライアント向けに設計されているため、リフレッシュ時にクライアントシークレットが送信されないことです。

> **Warning**
>
> `codeVerifier` は認証リクエストとコード交換の間、クライアント側で安全に保存する必要があります。 ブラウザーアプリでは `sessionStorage` または同等のものを使用します。

---

## ネイティブアプリ（認証コード + PKCE）

デスクトップおよびモバイルアプリケーションに使用します。 [Adobe Developer Console](https://developer.adobe.com/console) でネイティブアプリ資格情報を作成すると、アドビは `adobe+<hash>://callback の形式のリダイレクト URI を割り当てます</hash>` — OS レベルでそのカスタム URI スキームを処理するようにアプリケーションを登録します。 ループバックリダイレクト（`http://127.0.0.1:<port>/callback</port>`）もローカル開発でサポートされています。 フローは SPA と同じです — クライアントシークレットなしで PKCE を使用します。

```typescript
import { NativeAppAuth } from "frameio";

const auth = new NativeAppAuth({
    clientId: "YOUR_CLIENT_ID",
    redirectUri: "adobe+abc123def456://callback", // from your Adobe Developer Console Native App credential
    // Also supports loopback: "http://127.0.0.1:8080/callback"
});

const { url, codeVerifier } = await auth.getAuthorizationUrl({
    state: crypto.randomUUID(),
});

// Open system browser to `url`
// Listen for redirect on your custom URI scheme or loopback server

await auth.exchangeCode({ code: "CODE_FROM_REDIRECT", codeVerifier });
const client = new FrameioClient({ token: () => auth.getToken() });
```

### リダイレクト URI ルール

アドビは 2 つのポイントでリダイレクト URI ルールを適用します：[Adobe Developer Console](https://developer.adobe.com/console) で資格情報を登録するときと、`redirect_uri` パラメーターが `/authorize/v2` エンドポイントにヒットするときです。 この SDK で `redirectUri` に渡す値は、資格情報に登録した「リダイレクト URI パターン」のいずれかと一致する必要があります — そうでなければ、アドビは代わりに資格情報のデフォルトリダイレクト URI にリダイレクトします。

* **Web App** と **SPA** 資格情報には HTTPS が必要です。
* **Native App** 資格情報は非 HTTPS リダイレクトを使用します — 通常は `adobe+<hash>://callback</hash>` URI で、資格情報の Developer Console に表示されます。

資格情報で受け入れられる正確なパターンについては、[Adobe Developer Console](https://developer.adobe.com/console) を参照してください。

> **Note**
>
> Python SDK には Native App 資格情報クラスは含まれていません。Python にはカスタム
>
> URI スキームハンドラーを登録する標準的な方法がないためです。 TypeScript SDK は Native App を含むすべての 4 つの資格情報タイプをサポートしています。

---

## 手動トークン更新

Web App、SPA、および Native App フローでは、SDK は `getToken()` を介してトークンを自動的に更新します。 明示的な制御が必要な場合は、`refresh()` を直接呼び出すことができます：

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

これは、自動更新バッファーに依存するのではなく、重要な操作の前に強制的に更新したい場合に便利です。

`refresh()` は `WebAppAuth`、`SPAAuth`、および `NativeAppAuth` で利用できます。 更新トークンが利用できない場合（つまり、最初に `exchangeCode()` を呼び出す必要がある場合）、`ConfigurationError` をスローします。 `ServerToServerAuth` には `refresh()` メソッドがありません — 代わりにクライアント資格情報を介して新しいトークンを取得するために `authenticate()` を使用します。

---

## トークンの永続化

すべての認証クラスは、再起動間でトークン状態を永続化するために `exportTokens()` と `importTokens()` をサポートしています。 web アプリ、SPA、ネイティブアプリの流量では、アクセストークンとリフレッシュトークンがデフォルトでメモリに保存されるため、これは特に重要です。アプリケーションが再起動すると、トークンを永続化しない限り、ユーザーは再認証する必要があります。 サーバー間では、永続化はオプションです（クライアント資格情報は常に新しいトークンを作成できます）が、キャッシュされたトークンを読み込むことで、起動時の余分なラウンドトリップを回避できます。

### マーカーの読み込みと書き出し

```typescript
// After exchangeCode(), save the token state
const tokenData = auth.exportTokens();
// tokenData is: { access_token: "...", refresh_token: "...", expires_at: 1234567890.0 }
// Save it to your database, file, or secret store

// On next startup, restore it
auth.importTokens(tokenData);
const client = new FrameioClient({ token: () => auth.getToken() });
// The SDK will automatically refresh if the token is near expiry
```

> **Warning**
>
> 書き出したトークンは安全に保存してください。 これらにはAPIアクセスを付与するアクセストークンとリフレッシュトークンが含まれています。 トークンの書き込みは避けてください
>
> 本番環境でプレーンテキストファイルに保存することは避けてください。

### `onTokenRefreshed`による自動永続化

トークンが更新されるたびに自動的に保持するには、`onTokenRefreshed` コールバックを使用します：

```typescript
import fs from "fs/promises";

const TOKEN_FILE = "tokens.json";

const auth = new WebAppAuth({
    clientId: "...",
    clientSecret: "...",
    redirectUri: "...",
    onTokenRefreshed: (tokens) => {
        fs.writeFile(TOKEN_FILE, JSON.stringify(tokens));
    },
});

// On startup, restore if available
try {
    const saved = JSON.parse(await fs.readFile(TOKEN_FILE, "utf-8"));
    auth.importTokens(saved);
} catch {
    // No saved tokens — user will need to authenticate
}
```

コールバックは `exportTokens()` と同じ形式を受け取り、トークンの更新が成功するたびに実行されます。

---

## トークンの取り消し

ユーザーをログアウトし、Adobe IMS でトークンを無効にするには：

```typescript
await auth.revoke();
```

これにより、Adobe IMS に対して 2 つのベストエフォート取り消しリクエストが並行して実行されます（アクセストークン用とリフレッシュトークン用）。そして、すべてのローカルトークン状態がクリアされます。機密クライアント（`WebAppAuth`）の場合、取り消しリクエストは HTTP Basic Auth を使用します。パブリッククライアント（`SPAAuth`、`NativeAppAuth`）の場合、`client_id` はクエリパラメータとして送信されます。 取り消しエラーはログに記録されますが、スローされません。 取り消し後、ユーザーは再認証する必要があります。

---

## エラーハンドリング

すべての認証エラーは `FrameioAuthError` を継承するため、広範囲でキャッチするか特定のケースを処理できます：

```typescript
import {
    FrameioAuthError,
    AuthenticationError,
    TokenExpiredError,
    ConfigurationError,
    NetworkError,
    RateLimitError,
} from "frameio";

try {
    await auth.exchangeCode("...");
} catch (error) {
    if (error instanceof TokenExpiredError) {
        // The refresh token has expired; redirect the user to sign in again
    } else if (error instanceof AuthenticationError) {
        // Token exchange failed
        console.error(`Error: ${error.errorCode} - ${error.errorDescription}`);
    } else if (error instanceof NetworkError) {
        // Timeout or connection failure (after retries)
    } else if (error instanceof RateLimitError) {
        // 429 from Adobe IMS; retry after error.retryAfter seconds
    } else if (error instanceof FrameioAuthError) {
        // Catch-all for any other auth error
    }
}
```

### エラーリファレンス

| 例外                    | 発生するタイミング                                                                   |
| --------------------- | --------------------------------------------------------------------------- |
| `ConfigurationError`  | 設定が見つからないか無効（例：空の `clientId`、非 HTTPS リダイレクト URI、非 HTTPS `imsBaseUrl`）       |
| `AuthenticationError` | Adobe IMS によってトークン交換または更新が却下されました（`.errorCode` と `.errorDescription` があります） |
| `TokenExpiredError`   | リフレッシュトークン自体が期限切れ。ユーザーは再認証が必要                                               |
| `NetworkError`        | すべての再試行後の HTTP タイムアウトまたは接続失敗                                                |
| `RateLimitError`      | Adobe IMS が 429 を返しました。バックオフガイダンスについては `.retryAfter` を確認してください              |
| `PKCEError`           | PKCE フローでの消費者使用に利用可能。SDK 内部では投げられない                                         |

### 本番環境での期限切れリフレッシュトークンの処理

> **Warning**
>
> web アプリ、SPA、およびネイティブアプリフローの場合、リフレッシュトークンは最終的に期限切れになります。その場合、`getToken()` は `TokenExpiredError` を発生させます。 これをキャッチして、ユーザーを再度認証フローにリダイレクトする必要があります。

```typescript
import { TokenExpiredError } from "frameio";

try {
    const client = new FrameioClient({ token: () => auth.getToken() });
    const assets = await client.files.list({ projectId: "..." });
} catch (error) {
    if (error instanceof TokenExpiredError) {
        // Clear persisted tokens and redirect user to login
        await auth.revoke();
        return res.redirect("/login");
    }
}
```

---

## 設定リファレンス

これらのパラメーターには適切なデフォルト値があり、設定が必要になることはほとんどありません。 動作をカスタマイズする必要がある場合（ステージング IMSを指定する、カスタム`fetch`を挿入する、タイムアウトを調整する、ロガーを接続するなど）は、認証クラスを構築する際にオプションパラメーターとして渡してください：

| パラメーター             | Default                          | 説明                                                                                                                                                |
| ------------------ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `scopes`           | フロー固有のデフォルト                      | スペース区切りの OAuth 範囲。 S2S のデフォルトは `openid AdobeID frame.s2s.all` です。ユーザー向けフローのデフォルトは `openid email profile offline_access additional_info.roles` です。 |
| `imsBaseUrl`       | `https://ims-na1.adobelogin.com` | Adobe IMS ベース URL。 ステージングまたは本番環境以外の環境での上書き。 HTTPS を使用する必要があります。                                                                                   |
| `fetch`            | `globalThis.fetch`               | プロキシ、mTLS、またはカスタム HTTP 処理用のカスタム `fetch` 実装。                                                                                                       |
| `timeout`          | `30000`                          | トークンエンドポイント呼び出しの HTTP リクエストタイムアウト（ミリ秒）。                                                                                                           |
| `maxRetries`       | `2`                              | 一時的な障害（5xx、タイムアウト）の最大再試行回数。 レート制限の再試行（429）は別途追跡されます。                                                                                              |
| `refreshBuffer`    | `60`                             | トークンの有効期限前に事前更新をトリガーする秒数。                                                                                                                         |
| `onTokenRefreshed` | `undefined`                      | トークンの更新が成功するたびに実行されるコールバック。 `access_token`、`refresh_token`、および`expires_at`を含むオブジェクトを受け取ります。                                                       |
| `logger`           | 何もしない（サイレント）                     | `debug`、`info`、`warn`、`error`メソッドを持つLoggerインスタンス（例：`console`、`pino`、`winston`）。                                                                   |

### ステージング環境

`imsBaseUrl`を上書きしてステージングのAdobe IMSインスタンスを指定します。 SDKは、プログラムで本番環境の値を参照する必要がある場合に備えて、`DEFAULT_IMS_BASE_URL`（`https://ims-na1.adobelogin.com`）も書き出します。

```typescript
const auth = new ServerToServerAuth({
    clientId: "...",
    clientSecret: "...",
    imsBaseUrl: "https://ims-na1-stg1.adobelogin.com",
});
```

### カスタムfetch

プロキシサポートやカスタムTLS設定の場合：

```typescript
import { ProxyAgent } from "undici";

const proxyAgent = new ProxyAgent("http://corporate-proxy:8080");

const auth = new ServerToServerAuth({
    clientId: "...",
    clientSecret: "...",
    fetch: (url, init) => fetch(url, { ...init, dispatcher: proxyAgent }),
});
```

---

## 同時実行の安全性

TypeScript SDK は同時使用に対して安全です。 複数の `getToken()` 呼び出しが同時に発生し、更新が必要な場合、更新リクエストは 1 つだけ実行されます。 他の呼び出しは同じプロミスを待機し、同じ結果を受け取ります。 外部ロックは必要ありません。 この重複の除外は JavaScript のシングルスレッドイベントループと共有 `Promise` を使用します — 更新が既に実行中の場合、同時呼び出し元は 2 番目のリクエストを開始する代わりにそれに参加します。 リフレッシュ中に`revoke()`が呼び出された場合、リフレッシュは`AuthenticationError`で却下され、トークンはクリアされたままになります。取り消しが常に優先されます。