# 会话管理 (SessionProvider)

`SessionProvider` 是 `@arcblock/did-connect-react` 库的基石。它是一个 React Context Provider，封装了所有与会话相关的逻辑、状态和操作。通过使用 `SessionProvider` 包装您的应用程序，您可以将用户的会话信息和身份验证方法提供给应用程序树中的任何组件，而无需手动向下传递 props。

该组件处理从存储会话令牌和管理用户数据到协调登录/注销流程以及自动刷新会话的所有事务。

## 工作原理

`SessionProvider` 创建一个会话上下文，该上下文持有当前用户的状态（`user` 对象、`loading` 状态等）并提供修改该状态的函数（例如 `login`、`logout`）。嵌套在 `SessionProvider` 中的任何组件都可以订阅此上下文以访问此数据并触​​发身份验证操作。

下图说明了基本架构：

<!-- DIAGRAM_IMAGE_START:architecture:4:3:1765962229 -->
![Session Management (SessionProvider)](assets/diagram/core-components-session-provider-01.zh.jpg)
<!-- DIAGRAM_IMAGE_END -->

## 快速入门

要使用会话管理功能，您首先需要导入 `SessionProvider` 并用它包装您的应用程序。该库导出工厂函数以创建配置好的 provider。

### 基本设置

设置 provider 的最常见方法是使用 `createAuthServiceSessionContext`，它已为在 ArcBlock 生态系统 (Blocklets) 中运行的应用程序预先配置。

```javascript App.js icon=logos:javascript
import React from 'react';
import { createAuthServiceSessionContext } from '@arcblock/did-connect-react/lib/Session';
import AuthButton from './AuthButton';

// 创建 SessionProvider 和一个用于访问上下文的 hook
const { SessionProvider, SessionContext } = createAuthServiceSessionContext();

export const useSession = () => React.useContext(SessionContext);

export default function App() {
  return (
    <SessionProvider>
      <div className="App">
        <h1>Welcome to DID Connect</h1>
        <AuthButton />
      </div>
    </SessionProvider>
  );
}
```

```javascript AuthButton.js icon=logos:javascript
import React from 'react';
import { useSession } from './App';

export default function AuthButton() {
  const { session } = useSession();

  if (session.loading) {
    return <div>Loading...</div>;
  }

  if (session.user) {
    return (
      <div>
        <p>Welcome, {session.user.fullName || session.user.did}</p>
        <button onClick={() => session.logout()}>Logout</button>
      </div>
    );
  }

  return <button onClick={() => session.login()}>Login</button>;
}
```

在此示例中，`createAuthServiceSessionContext` 生成一个 `SessionProvider`。我们用它包装我们的 `App`，现在任何子组件，例如 `AuthButton`，都可以使用 `useSession` hook 来访问会话数据和函数。

## SessionProvider Props

您可以通过向 `SessionProvider` 传递各种 props 来配置其行为。

<x-field-group>
  <x-field data-name="serviceHost" data-type="string" data-desc="身份验证服务后端的基 URL。默认为当前 Blocklet 的前缀或 '/'。"></x-field>
  <x-field data-name="autoConnect" data-type="boolean" data-default="false" data-desc="如果为 true，在页面加载时若未找到用户会话，则 DID Connect 对话框将自动打开。"></x-field>
  <x-field data-name="autoDisconnect" data-type="boolean" data-default="true" data-desc="如果为 true，当应用程序 ID 与会话 cookie 中存储的 ID 不匹配时，会话将自动清除。"></x-field>
  <x-field data-name="protectedRoutes" data-type="string[]" data-default='["*"]' data-desc="用于保护的路由模式数组。如果未经身份验证的用户尝试访问这些路由，他们将看到一个加载指示器，直到会话被解析。支持通配符 (*)。"></x-field>
  <x-field data-name="locale" data-type="string" data-desc="设置 DID Connect UI 的显示语言。默认为浏览器语言。"></x-field>
  <x-field data-name="webWalletUrl" data-type="string" data-desc="用于连接过程的基于 Web 的 DID Wallet 的 URL。"></x-field>
  <x-field data-name="timeout" data-type="number" data-default="120000" data-desc="在连接过程中等待 DID Wallet 响应的超时时间（以毫秒为单位）。"></x-field>
  <x-field data-name="useSocket" data-type="boolean" data-default="true" data-desc="决定是否使用 WebSocket 与钱包进行实时通信，以提供更快的用户体验。"></x-field>
  <x-field data-name="extraParams" data-type="object" data-desc="一个包含额外参数的对象，这些参数将随每次身份验证请求发送到后端。"></x-field>
</x-field-group>

## 会话上下文对象

当您使用 `SessionContext` 时，会得到一个包含会话状态和 API 的对象。主要属性是 `session`，它包含了您将要交互的所有数据和方法。

### 状态属性

这些是描述用户会话当前状态的只读属性。

<x-field-group>
  <x-field data-name="user" data-type="User | null" data-desc="如果存在会话，则为经过身份验证的用户对象，否则为 null。包含 DID、姓名、电子邮件、头像等详细信息。"></x-field>
  <x-field data-name="loading" data-type="boolean" data-desc="在会话初始化、刷新或登录/注销过程中为 True。"></x-field>
  <x-field data-name="initialized" data-type="boolean" data-desc="页面加载时初始会话检查完成后为 True。"></x-field>
  <x-field data-name="open" data-type="boolean" data-desc="如果 DID Connect 对话框当前处于打开状态，则为 True。"></x-field>
  <x-field data-name="error" data-type="string" data-desc="如果上次会话操作失败，则包含错误消息。"></x-field>
  <x-field data-name="provider" data-type="string" data-desc="上次登录使用的提供商（例如 'wallet'、'github'、'google'）。"></x-field>
  <x-field data-name="walletOS" data-type="string" data-desc="已连接的 DID Wallet 的操作系统（例如 'ios'、'android'、'web'）。"></x-field>
  <x-field data-name="unReadCount" data-type="number" data-desc="当前用户的未读通知数量。"></x-field>
</x-field-group>

### 操作方法

这些是您可以调用以启动身份验证流程或管理会话的函数。

<x-field-group>
  <x-field data-name="login()" data-type="function">
    <x-field-desc markdown>通过打开 DID Connect UI 来启动用户登录过程。它接受可选的回调和参数。</x-field-desc>
  </x-field>
  <x-field data-name="logout()" data-type="function">
    <x-field-desc markdown>将当前用户登出，从存储中清除会话。它接受一个可选的回调函数，在完成后执行。</x-field-desc>
  </x-field>
  <x-field data-name="switchDid()" data-type="function">
    <x-field-desc markdown>允许已登录的用户切换到不同的 DID 账户。它会重新打开 DID Connect UI，并提供切换账户的上下文。</x-field-desc>
  </x-field>
  <x-field data-name="bindWallet()" data-type="function">
    <x-field-desc markdown>对于通过社交提供商（OAuth）或 Passkeys 登录的用户，此函数会启动一个流程，将 DID Wallet 连接到他们现有的账户。</x-field-desc>
  </x-field>
  <x-field data-name="refresh()" data-type="function">
    <x-field-desc markdown>手动触发从服务器刷新用户会话数据。可用于获取更新的个人资料信息或权限。</x-field-desc>
  </x-field>
  <x-field data-name="switchProfile()" data-type="function">
    <x-field-desc markdown>打开 DID Connect UI，允许用户在同一 DID 账户内的不同个人资料（例如，个人、工作）之间切换。</x-field-desc>
  </x-field>
  <x-field data-name="switchPassport()" data-type="function">
    <x-field-desc markdown>打开 DID Connect UI，允许用户在不同 passport 之间切换，这些 passport 可能代表不同的凭证或角色集。</x-field-desc>
  </x-field>
  <x-field data-name="connectToDidSpaceForFullAccess()" data-type="function">
    <x-field-desc markdown>启动与用户 DID Space 的连接，请求完全访问权限。这通常是需要在用户个人数据存储中读写数据的应用程序所必需的。</x-field-desc>
  </x-field>
  <x-field data-name="withSecondaryAuth()" data-type="function">
    <x-field-desc markdown>一个高阶函数，用于包装另一个函数，在执行被包装的函数之前，要求用户完成二次身份验证步骤（例如，重新输入密码、使用 passkey）。这是保护敏感操作的理想选择。</x-field-desc>
  </x-field>
</x-field-group>

### 事件订阅

该上下文还提供一个 `events` 对象，它是 `EventEmitter3` 的一个实例。您可以使用它来订阅会话的各种生命周期事件。

```javascript EventListener.js icon=logos:javascript
import React, { useEffect } from 'react';
import { useSession } from './App';

function EventListener() {
  const { events } = useSession();

  useEffect(() => {
    const handleLogin = (result) => {
      console.log('用户已登录：', result.user.did);
    };

    const handleLogout = () => {
      console.log('用户已登出');
    };

    events.on('login', handleLogin);
    events.on('logout', handleLogout);

    // 在组件卸载时清理监听器
    return () => {
      events.off('login', handleLogin);
      events.off('logout', handleLogout);
    };
  }, [events]);

  return null; // 此组件不渲染任何内容
}
```

可用的事件包括 `login`、`login-failed`、`logout`、`change`、`bind-wallet`、`switch-passport` 等。

## 高级定制

对于需要不同存储机制或更精细控制的场景，您可以使用通用的 `createSessionContext` 工厂函数。

```javascript customSession.js icon=logos:javascript
import { createSessionContext } from '@arcblock/did-connect-react/lib/Session';

const { SessionProvider, SessionContext } = createSessionContext(
  'my_app_session_token', // 自定义存储键
  'ls',                   // 使用 localStorage 代替 cookie
  {},
  {
    rolling: false,         // 禁用自动令牌刷新
  }
);

// ... 根据需要导出和使用
```

这允许您微调会话行为，例如将存储引擎更改为 `localStorage` ('ls') 或 `cookie`，或调整令牌刷新策略。