# 客戶端整合

本頁說明瀏覽器與非瀏覽器客戶端如何送出 Token、接收續期後的新值。

## 瀏覽器（cookie）

Token 存在 `httpOnly` cookie，前端程式碼讀不到也不需要處理；只要請求帶上 cookie：

```typescript
async function loadProfile() {
  const res = await fetch("https://api.example.com/me", {
    // 跨網域時必須帶上 cookie
    credentials: "include",
  });
  if (res.status === 401) {
    // 未登入或已登出
    window.location.href = "/login";
    return;
  }
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}
```

續期由伺服器以 `Set-Cookie` 完成，前端無需額外處理。

## 非瀏覽器（header）

行動 App、CLI 或伺服器間呼叫不使用 cookie 時，以 header 送出，並從回應 header 更新本地值：

```typescript
type Session = { accessToken: string; refreshId: string; deviceId: string };

async function callApi(session: Session, url: string) {
  const res = await fetch(url, {
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "X-Refresh-ID": session.refreshId,
      // 納入指紋計算，必須與登入時相同
      "X-Device-ID": session.deviceId,
    },
  });

  const newToken = res.headers.get("X-New-Access-Token");
  const newRefreshId = res.headers.get("X-New-Refresh-ID");
  if (newToken) session.accessToken = newToken;
  if (newRefreshId) session.refreshId = newRefreshId;

  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}
```

登入時 `CreateJWT()` 的回傳值 `{ token, refresh_id }` 即為初始的 `accessToken` 與 `refreshId`。

## 注意事項

| 項目 | 說明 |
|---|---|
| Device ID | 由客戶端產生並持久保存（如安裝時產生的 UUID），每次請求相同 |
| 併發請求 | Refresh ID 換發後舊 ID 只保留 5 秒；長時間離線後的大量併發請求應先以單一請求完成續期 |
| 回應 header | 跨網域時伺服器需在 `Access-Control-Expose-Headers` 列出 `X-New-Access-Token`、`X-New-Refresh-ID` |

相關：[Token 傳遞](/zh/token-transport)、[裝置指紋](/zh/device-fingerprint)
