---
title: TypeScript SDK
description: 安装并使用官方 @sudomimus TypeScript 包。
editUrl: true
head: []
template: doc
sidebar:
  order: 2
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

import { CardGrid, LinkCard } from "@astrojs/starlight/components";

TypeScript SDK 拆分为 `@sudomimus/*` scope 下的小包。只要具体流程适合相应运行时，这些包可用于 Node.js 和浏览器能力足够的运行时。

## 包

| 包 | 用途 |
| --- | --- |
| `@sudomimus/connect` | Connect inquiry 生命周期：`establish`、`statusPoll`、`redeem`、`info`，以及通过应用元数据验 token。 |
| `@sudomimus/session` | Refresh token 轮换、introspection、logout、revoke-all，以及 token store 辅助。 |
| `@sudomimus/device` | Device authorization：`deviceAuthorize`、`deviceToken`，以及自动轮询辅助。 |
| `@sudomimus/native` | Steam ticket 与 AccessKey direct-issue。 |
| `jose` | 基于标准的 JWT 解析与密码学验证。 |

只安装当前集成需要的包：

```bash
pnpm add @sudomimus/connect @sudomimus/session
npm install @sudomimus/connect @sudomimus/session
yarn add @sudomimus/connect @sudomimus/session
```

## Connect

```ts
import { ConnectClient, RETURN_METHOD } from "@sudomimus/connect";

const client = new ConnectClient({
    clientAuth: {
        applicationAnchor: "your-app-anchor",
        privateKeyPem: process.env.SUDOMIMUS_CLIENT_AUTH_PRIVATE_KEY!,
    },
});

const inquiry = await client.establish({
    applicationAnchor: "your-app-anchor",
    returnMethods: [{ type: RETURN_METHOD.STATUS_POLL, payload: {} }],
});

const status = await client.statusPoll({
    exposureKey: inquiry.exposureKey,
    hiddenKey: inquiry.hiddenKey,
});

if (status.status === "REALIZED") {
    const tokens = await client.redeem({
        exposureKey: inquiry.exposureKey,
        hiddenKey: inquiry.hiddenKey,
        confirmationKey: status.confirmationKey,
    });
}
```

`/establish` 要求 audience 为 `sudomimus-connect` 的 client-auth JWT。传入 `clientAuth` 后，SDK 会在内部完成签名。

## Sessions

```ts
import {
    InMemoryTokenStore,
    RotatingSessionClient,
    SessionClient,
} from "@sudomimus/session";

const store = new InMemoryTokenStore();
const session = new RotatingSessionClient(new SessionClient(), store);

await session.seed({
    accessToken: tokens.accessToken,
    refreshToken: tokens.refreshToken,
});

const accessToken = await session.refresh();
await session.logout();
```

`revokeAll` 是应用后端操作，需要 audience 为 `sudomimus-session` 的 client-auth 签名。

## Device Authorization

```ts
import { DeviceClient, DeviceTokenApiError } from "@sudomimus/device";

const device = new DeviceClient({ baseUrl: "https://device-api.sudomimus.com" });
const auth = await device.deviceAuthorize({ applicationAnchor: "your-app-anchor" });

console.log(auth.userCode, auth.verificationUriComplete);

while (true) {
    try {
        const tokens = await device.deviceToken({ deviceCode: auth.deviceCode });
        break;
    } catch (error) {
        if (
            error instanceof DeviceTokenApiError
            && (error.error === "authorization_pending" || error.error === "slow_down")
        ) {
            await new Promise((resolve) => setTimeout(resolve, (error.interval ?? auth.interval) * 1000));
            continue;
        }
        throw error;
    }
}
```

Device authorization 成功后继续使用 `@sudomimus/session`；Device API 返回的是普通 Sudomimus access/refresh token。

## Token 验证

令牌验证与 Connect 解耦。从 Session API 的 `GET /applications/{applicationAnchor}/jwks.json` 获取应用 JWK Set，按 `Cache-Control` 缓存，并精确选择 token `kid` 指定的密钥；遇到未知 `kid` 时刷新一次。使用 `jose` 等标准 JOSE 实现解析 JWT 并执行密码学验证，不要再把 Connect `/info` 当作密钥来源。

验签成功后，从 payload `sub` 读取应用可见用户键，从 payload `sid` 读取逻辑会话，从 payload `jti` 读取 bearer 实例。Access token 不携带个人资料；refresh token 不携带用户标识，并额外包含 `rotationVersion`。当前资料从 Session `/userinfo` 获取。离线验证无法观察后续 logout 或权威变化；要求实时会话状态的操作应调用 Session `/introspect`。

## 源码

<CardGrid>
<LinkCard
    title="TypeScript packages"
    description="源码与包 README。"
    href="https://github.com/sudomimus/sudomimus/tree/master/sdks/typescript/packages"
/>
<LinkCard
    title="选择 SDK"
    description="官方 SDK 与 Sudomimus API 的对应关系。"
    href="/zh-cn/sdk/overview/"
/>
</CardGrid>