# @edge-markets/connect-node 中文文档

规范地址：https://npmcn.com/package/%40edge-markets/connect-node/

版本：4\.0\.0
npm 元数据更新时间：2026-11-09T20:36:47.765Z
中文译文更新时间：2026-11-13T06:35:35.608Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40edge-markets/connect-node
原文对照：https://npmcn.com/package/%40edge-markets/connect-node/?lang=original
项目主页：https://github.com/edgeboost/edge-connect-sdk#readme
源码仓库：https://github.com/edgeboost/edge-connect-sdk

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：357 kB，8 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- Node.js 要求：\>=22\.23\.4
- 运行依赖：2 个
- 首次发布：2026-01-20T20:42:03.342Z
- 最新版本发布：2026-10-27T09:26:16.068Z
- 近 30 天下载量：16633（2026-10-13 至 2026-11-11，npm 官方下载统计）
- 已发布版本数：23（近 12 个月 23 个）

## 中文简介

EDGE 连接符号交换和 API 调用服务器 SDK

## 安装

```sh
npm install @edge-markets/connect-node
```

也可以使用：`pnpm add @edge-markets/connect-node`、`yarn add @edge-markets/connect-node`、`bun add @edge-markets/connect-node`

## README

# @ edge- markets/ 连接- 节点

服务器SDK用于EDGE Connect sorders交换和API调用.

## 特征

-   🔐 **安全证券交换** - 与PKCE交换代号
-   🔄 **托肯刷新** - 自动刷新令牌处理
-   📡 **完整的 API 客户端** - 用户、余额、通过 `forUser()` 模式
-   🛡️ **输入错误** - 每种情况的具体错误类别
-   📝 **先输入脚本** - 完整的类型定义
-   🪝 **Webhook 签名验证** - 常时HMAC-SHA256,有回放保护
-   ✅ **Webhook 解析+验证** - 对生体进行核查,对畸形事件有效载荷关闭故障
-   🔁 **Webhook 调节** - 以光标为基础的同步,用于捕获错过的事件
-   📥 **Webhook 收件箱管弦乐** - 与合作伙伴拥有的存储器进行持久的接收/处理/重放控制流程
-   🔐 **托肯金库** - 版本的AES-256-GCM 密码加密,并有密钥旋转
-   🔄 **用户会话** - 有刷新、加密和单飞行刷新保护的托肯存储回调
-   ⚙️ **信封配置帮助器** - 从标准的EDGE env vars 构建 SDK 配置

这些帮助是框架中立的: SDK 拥有可重复的连接力学 例如令牌刷新、 webhook 验证、重放控制流程以及传输 意图检查,而伙伴应用程序则保留对持久性的所有权,钱包 变异、管理授权和商业规则.

## 安装

```bash
npm install @edge-markets/connect-node
# or
pnpm add @edge-markets/connect-node
# or
yarn add @edge-markets/connect-node
```

## 快速启动

```typescript
import { createEdgeConnectServerFromEnv } from '@edge-markets/connect-node'

// Create instance once (reuse for all requests). Reads EDGE_CLIENT_ID,
// EDGE_CLIENT_SECRET, EDGE_ENVIRONMENT, optional mTLS and partner sync env vars.
const { server: edge } = createEdgeConnectServerFromEnv()

// Exchange code from EdgeLink for tokens
const tokens = await edge.exchangeCode(code, codeVerifier)

// Create a user-scoped client and make API calls
const client = edge.forUser(tokens.accessToken)
const user = await client.getUser()
const balance = await client.getBalance()
```

## • 警卫

**这个SDK需要你的客户秘密 只用在后端服务器上!**

永远不要向浏览器或客户端代码透露您的客户端秘密.

## 配置

### 从环境变量

大多数伙伴后端应该使用 `createEdgeConnectServerFromEnv()` 如此的微调 PEM 处理、合作伙伴同步证书、 URL 覆盖和超时解析 在所有整合中保持一致.

```typescript
import { createEdgeConnectServerFromEnv } from '@edge-markets/connect-node'

const { server: edge, capabilities, warnings } = createEdgeConnectServerFromEnv({
  requireMtls: process.env.NODE_ENV === 'production',
})

warnings.forEach((warning) => logger.warn(warning))

if (!capabilities.webhookSyncConfigured) {
  logger.warn('EDGE webhook sync is not configured; set EDGE_PARTNER_CLIENT_ID and EDGE_PARTNER_CLIENT_SECRET')
}
```

支持的环境变量 :

| 变量 | 说明 |
| --- | --- |
| `EDGE_CLIENT_ID` | 面向用户的 OAuth 客户端ID |
| `EDGE_CLIENT_SECRET` | OAuth 客户端秘密 |
| `EDGE_ENVIRONMENT` | `production` 或者说 `sandbox` |
| `EDGE_API_BASE_URL` | 可选的连接 API 覆盖 |
| `EDGE_OAUTH_BASE_URL` | 可选 OAuth/ token URL 覆盖 |
| `EDGE_PARTNER_API_BASE_URL` | 可选伙伴仪表板 API 覆盖 |
| `EDGE_PARTNER_CLIENT_ID` | 伙伴机对机客户端 ID 用于 Webhook 同步 |
| `EDGE_PARTNER_CLIENT_SECRET` | 用于Webhook同步的伙伴机对机客户端秘密 |
| `EDGE_MTLS_CERT` 编号 : `EDGE_MTLS_CERT_PATH` | 客户端证书 PEM 或文件路径 |
| `EDGE_MTLS_KEY` 编号 : `EDGE_MTLS_KEY_PATH` | 客户端私有密钥 PEM 或文件路径 |
| `EDGE_SERVER_CA_PEM` 编号 : `EDGE_SERVER_CA_PEM_PATH` | 可选的额外服务器信任根 PEM 或文件路径 |
| `EDGE_MTLS_CA` 编号 : `EDGE_MTLS_CA_PATH` | 可选的额外服务器信任根 PEM 或文件路径的后继别名 |
| `EDGE_REQUIRE_MTLS` | 设定为 `true` 无法启动, 除非客户端证书和密钥已配置 |
| `EDGE_TIMEOUT_MS` | 可选请求超时( 毫秒) |

使用带逃出新行的内置 PEMs(`\n`)或文件路径。 如果两者都安排好了, 内入值获胜,帮助者返回警告.

### 手动配置

```typescript
interface EdgeConnectServerConfig {
  clientId: string              // Your user-facing OAuth client ID
  clientSecret: string          // Your user-facing OAuth client secret (keep secret!)
  environment: EdgeEnvironment  // 'production' | 'sandbox'

  // Optional
  apiBaseUrl?: string           // Custom API URL (dev only)
  partnerApiBaseUrl?: string    // Custom partner dashboard API URL (dev only)
  oauthBaseUrl?: string         // Custom OAuth URL (dev only)
  timeout?: number              // Request timeout (default: 30000ms)
  retry?: RetryConfig           // Retry configuration
  onRequest?: (info) => void    // Hook called before each request
  onResponse?: (info) => void   // Hook called after each response

  // Optional: Partner-level credentials for non-user-scoped endpoints.
  // Required ONLY when calling syncWebhookEvents — the rest of the SDK
  // works without them. These are a DIFFERENT OAuth client than clientId
  // (machine-to-machine, not user-facing).
  partnerClientId?: string
  partnerClientSecret?: string

  // By default the SDK derives partnerApiBaseUrl from apiBaseUrl by replacing
  // /connect/v1 with /v1. For example:
  // https://sandbox.connect.staging.edgeboost.io/connect/v1
  // -> https://sandbox.connect.staging.edgeboost.io/v1

  // Optional: Mutual TLS client authentication
  mtls?: {
    enabled: boolean
    cert: string                // Your client certificate PEM
    key: string                 // Your client private key PEM
    ca?: string | string[]      // Additional server trust roots, if EDGE provides them
  }
}
```

### 信件级别加密 (MLE)

教育、教育与教育部尚未提供。 边缘连接 API 不接受或返回加密的有效载荷,所以通过 `mle: { enabled: true }` 掷出 `EdgeFeatureUnavailableError`。连接API流量由mTLS保护.

### 互通 TLS (mTLS)

使用 `mtls` 当 EDGE 向您的伙伴后端发布了客户端证书和密钥时:

```typescript
const edge = new EdgeConnectServer({
  clientId: process.env.EDGE_CLIENT_ID!,
  clientSecret: process.env.EDGE_CLIENT_SECRET!,
  environment: 'sandbox',
  mtls: {
    enabled: true,
    cert: process.env.EDGE_MTLS_CERT!,
    key: process.env.EDGE_MTLS_KEY!,
  },
})
```

`cert` 财务报告和审定财务报表 `key` 认证您的后端到 EDGE 连接 。 他们是客户 认证材料必须留在您的服务器上 .

`ca` 是可选服务器信任材料。 公开EDGE 连接网关等 `https://connect.edgeboost.io` 财务报告和审定财务报表 `https://sandbox.connect.staging.edgeboost.io` 使用情况 公开证书,所以大多数合伙人应该省略 `ca`如果 EDGE 提供 非公用端点的私用服务器 CA, 通过为 `ca`; SDK 附着 到节点默认的公共信任根 而不是替换它们.

不禁用 TLS 校验, 不设置 `NODE_TLS_REJECT_UNAUTHORIZED=0`。 。 。 .

### Next.js 和 Vercel 路由处理器

`@edge-markets/connect-node` 是一个节点.js服务器 SDK. 在Next.js路处理器, 强制导入或构建 SDK 之前的节点运行时间 :

```typescript
export const runtime = 'nodejs'

import { createEdgeConnectServerFromEnv } from '@edge-markets/connect-node'

const { server: edge } = createEdgeConnectServerFromEnv({
  requireMtls: true,
})
```

不要从客户端组件、浏览器代码、中间软件导入此软件包, 或 边缘运行时处理器。 它只读取服务器机密,并使用节点 TLS API 用于 (原始内容存档于2012-10-12). mTLS.

如果您被绑入更旧的 SDK 版本, 您的 Next. js/ Turbopack 将建立日志 显示 `dynamic usage of require is not supported`,将软件包外部化为 应用级缓解直到您可以升级 :

```typescript
// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  serverExternalPackages: ['@edge-markets/connect-node', 'undici'],
}

export default nextConfig
```

## 托肯交换

EdgeLink 完成后, 将代码交换为令牌 :

```typescript
// In your /api/edge/exchange endpoint
export async function POST(req: Request) {
  const { code, codeVerifier } = await req.json()
  
  try {
    const tokens = await edge.exchangeCode(code, codeVerifier)
    
    // Store tokens securely (encrypted in database)
    await db.edgeConnections.upsert({
      userId: req.user.id,
      accessToken: encrypt(tokens.accessToken),
      refreshToken: encrypt(tokens.refreshToken),
      expiresAt: new Date(tokens.expiresAt),
    })
    
    return Response.json({ success: true })
  } catch (error) {
    if (error instanceof EdgeTokenExchangeError) {
      // Code expired or already used
      return Response.json({ error: 'Please try again' }, { status: 400 })
    }
    throw error
  }
}
```

### 加密存储的令牌

在休息时存储访问和刷新加密的令牌 . `EdgeTokenVault` 给 \* 与一个稳定的AES-256-GCM信封合作,提供关键标识和关键旋转支持; 您仍然选择数据库表格和用户映射.

```typescript
import { createEdgeTokenVault } from '@edge-markets/connect-node'

const tokenVault = createEdgeTokenVault({
  currentKey: {
    id: process.env.EDGE_TOKEN_ENCRYPTION_KEY_ID ?? '2026-06',
    key: process.env.EDGE_TOKEN_ENCRYPTION_KEY!, // 32 bytes as base64, base64url, or hex
  },
  previousKeys: [
    // { id: '2026-05', key: process.env.EDGE_TOKEN_ENCRYPTION_KEY_2026_05! },
  ],
})

const tokens = await edge.exchangeCode(code, codeVerifier)
await db.edgeConnections.upsert({
  userId,
  encryptedTokens: tokenVault.encryptTokens(tokens),
})

const connection = await db.edgeConnections.get(userId)
const decrypted = tokenVault.decryptTokens(connection.encryptedTokens)
const client = edge.forUser(decrypted.accessToken)
```

## 托肯刷新和用户会话

使用 `EdgeUserSession` 当你存储加密的代号,并希望 SDK 处理解密-新鲜-拯救-客户生命周期。 您仍然拥有数据库表 和用户映射.

```typescript
const session = edge.createUserSession({
  subjectId: userId,
  tokenVault,
  tokenStore: {
    load: async (subjectId) => {
      const row = await db.edgeConnections.get(subjectId)
      return row?.encryptedTokens
    },
    save: async (subjectId, encryptedTokens, { tokens }) => {
      await db.edgeConnections.update(subjectId, {
        encryptedTokens,
        expiresAt: new Date(tokens.expiresAt),
      })
    },
  },
  refreshSkewMs: 60_000,
})

const balance = await session.withClient((client) => client.getBalance())
```

`EdgeUserSession` 刷新已近过期的符号, 当 符号端点不旋转, 保存已刷新的符号信封 在返回客户端之前,单飞行同时刷新同一客户端 进程。 在横向规模的服务中,增加每个链接的数据库租赁和 乐观的符号版本 。 坚持完整返回的符号 设置解剖,因为持久不透明的刷新证书在成功时旋转.

刷新失败抛出 `EdgeRefreshError`启用 `actionRequired` (单位:千美元)`retry`, (中文(简体) ), `partner_admin`,或 `relink`和 `retryable` 连接状态决定; 不将每个符号端点失败都翻译为用户再连接提示 .

对于从分割符列迁移,请使用 `createSessionTokenRecord`, (中文(简体) ), `parseSessionTokenRecord`, (中文(简体) ), `normalizeSessionExpiresAt`,以及 `encryptLegacySessionTokens`. 普通文字迁移要求明确 `allowPlaintext: true` 选项,所以生产请求路径不会默默接受 原始标志.

## API 方法

所有用户扫描的 API 方法都使用 `EdgeUserClient`,通过 `edge.forUser(accessToken)`数字 :

```typescript
const client = edge.forUser(accessToken)
```

### 用户余额( B)

```typescript
const user = await client.getUser()
// Returns: { id, email, firstName, lastName, address?, city?, state?, zip?, phone?, dateOfBirth?, verified, createdAt }

const balance = await client.getBalance()
// Returns: { userId, currentBalance, availableBalance, currency, asOf }
```

### 转账

```typescript
// 1. Initiate the transfer — returns a pending transfer that requires verification.
const transfer = await client.initiateTransfer({
  type: 'debit',           // 'debit' = pull from user, 'credit' = push to user
  amount: '100.00',
  idempotencyKey: `txn_${userId}_${Date.now()}`,
})
// Returns: { transferId, status: 'pending_verification', otpMethod }

// 2. Create an EDGE-hosted verification session. The returned `verificationUrl`
//    is a single-use, short-lived URL you embed in an iframe or popup on your
//    frontend. The user enters their OTP inside the EDGE-hosted UI — OTP secrets
//    never touch your infrastructure.
const session = await client.createVerificationSession(transfer.transferId, {
  origin: 'https://your-site.example.com',
})
// Returns: { sessionId, verificationUrl, expiresAt }

// 3. Listen for the postMessage event from the iframe (or poll
//    getVerificationSessionStatus) to learn when verification completes.
//    A `transfer.completed` webhook will be delivered to your configured
//    webhook URL once the transfer settles. For browser-crash recovery,
//    your backend should also reconcile against
//    GET /v1/partner/webhooks/events/sync using a dashboard
//    client_credentials token from POST /connect/oauth/token.

// Get transfer status
const status = await client.getTransfer(transfer.transferId)

// List transfers
const { transfers, total } = await client.listTransfers({
  status: 'completed',
  limit: 10,
  offset: 0,
})
```

### 同意

```typescript
// Revoke consent (disconnect user)
await client.revokeConsent()

// Clean up stored tokens
await db.edgeConnections.delete(userId)
```

## 网络呼号

EDGE Connect在传输完成后将webhook事件发送给您的服务器, 失败、过期或用户取消同意。 SDK提供两个帮助: **签名验证** 用于 HTTP 直播频道和 a **调节同步方法** 以克龙驱动的安全网.

### 解析和校验webhook 交付

每个网友都带着 `X-Edge-Signature` 页眉在 `t=...,v1=...` 格式 - HMAC-SHA256 超过 `${timestamp}.${rawBody}` 键入了您的网页 暗号 使用 `parseAndVerifyWebhook` 核查签名,分析JSON 运行时间 - 将输入的事件信封逐一验证 。 帮手拒绝 默认超过5分钟的事件,以防止再播放攻击.

> ⚠️ **这个 `body` 参数必须为原始请求字节**,没有解析 对象,您重新字符串. `JSON.stringify(req.body)` 重排序密钥 并且无声无息地打破了没有诊断的核查. 总是抓住 原始身体通过你框架的生体中间软件.

```typescript
import { parseAndVerifyWebhook } from '@edge-markets/connect-node'
import express from 'express'

const app = express()

app.post(
  '/webhooks/edge',
  // 👇 Critical: this gives you req.body as a Buffer, not a parsed object
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const { event, eventId, signatureTimestamp } = parseAndVerifyWebhook({
      rawBody: req.body,
      signatureHeader: req.headers['x-edge-signature'],
      secret: process.env.EDGE_WEBHOOK_SECRET!,
    })

    // Store eventId + signatureTimestamp in your durable inbox if you use one.
    // Process asynchronously and return 200 immediately — see "Best Practices"
    void processEdgeEvent(event)
    res.status(200).send('ok')
  },
)
```

#### 网点JS

```typescript
// main.ts
const app = await NestFactory.create(AppModule, { rawBody: true })

// edge-webhook.controller.ts
import { Controller, Post, Req, HttpCode, UnauthorizedException, type RawBodyRequest } from '@nestjs/common'
import type { Request } from 'express'
import { EdgeAuthenticationError, parseAndVerifyWebhook } from '@edge-markets/connect-node'

@Controller('webhooks')
export class EdgeWebhookController {
  @Post('edge')
  @HttpCode(200)
  handle(@Req() req: RawBodyRequest<Request>) {
    try {
      const { event } = parseAndVerifyWebhook({
        rawBody: req.rawBody ?? Buffer.from(''),
        signatureHeader: req.headers['x-edge-signature'],
        secret: process.env.EDGE_WEBHOOK_SECRET!,
      })
      void processEdgeEvent(event)
    } catch (error) {
      if (error instanceof EdgeAuthenticationError) {
        throw new UnauthorizedException('invalid signature')
      }
      throw error
    }
  }
}
```

`verifyWebhookSignature` 仍为高级集成出口,但 `parseAndVerifyWebhook` 是建议默认的,因为它没有关闭 未知的事件类型,错误的有效载荷, 和解析的身体错误.

### 类型安全事件处理 `EdgeWebhookEvent`

`EdgeWebhookEvent` 歧视的结合 `event.type`切换 缩进 `event.data` 自动——不需要铸造.

```typescript
import type { EdgeWebhookEvent } from '@edge-markets/connect-node'

function processEdgeEvent(event: EdgeWebhookEvent) {
  switch (event.type) {
    case 'transfer.completed':
      // event.data is { transferId, status: 'completed', type, amount }
      creditWallet(event.data.transferId, event.data.amount)
      break
    case 'transfer.failed':
      // event.data.reason is `string | undefined`
      logFailure(event.data.transferId, event.data.reason ?? 'unknown')
      break
    case 'transfer.expired':
      cancelPending(event.data.transferId)
      break
    case 'transfer.processing':
      // @experimental — reserved for ledger dual-write hand-off
      break
    case 'consent.revoked':
      deactivateLink(event.data.userId, event.data.clientId)
      break
    default: {
      const _exhaustive: never = event
      return _exhaustive
    }
  }
}
```

`event.data.amount` 故意为: `string` (例如:. `'100.00'`页:1 数字,以保持小数精确度——用小数认识来分析 图书馆在你那边.

### 校正错过的事件 `createWebhookReconciler`

即使是复试和死字排队,网络或伙伴断线也能 放弃 Webhook 事件。 运行 `syncWebhookEvents` 从头到尾(每~5分钟) 是典型的)以捕捉任何您的主要接收器错过的.

这种方法需要合作伙伴的证书(`partnerClientId` 编号 : `partnerClientSecret`继续 `EdgeConnectServer`它使用 OAuth `client_credentials` 赠款,在内部缓存伙伴代号 处理一个 401 ,方法是刷新一次符牌并重试.

```typescript
import { createEdgeConnectServerFromEnv } from '@edge-markets/connect-node'

const { server: edge } = createEdgeConnectServerFromEnv()

const reconciler = edge.createWebhookReconciler({
  cursorStore: {
    load: () => db.cursors.get('edge_webhook_sync'),
    save: (cursor) => db.cursors.set('edge_webhook_sync', cursor),
  },
  processEvent: processEdgeEvent, // same idempotent handler as live webhooks
  limit: 100,
  maxPages: 10,
})

async function reconcile() {
  const result = await reconciler.run()
  logger.info('EDGE webhook reconciliation complete', result)
}

setInterval(reconcile, 5 * 60 * 1000)
```

校正器只在光标后保存光标 `processEvent(event)` 成功并 跳过重叠运行在同一实例上。 您提供了光标存储和 事件处理器; SDK 拥有页码和光标推进.

如果你需要全权控制, `edge.syncWebhookEvents()` 现保留.

服务器执行 a **30天的回顾**。如果光标大于 30 你们将会收到30天前的事件 状态良好,通过 `getTransfer` 如果你已经下线的时间更长.

### 最佳做法

1.  **先验证签名** - 拒绝在401号事件前签名错误的事件 从事任何工作.
2.  **立即返回200号** ——过程事件同步. EDGE 重试 在非-2xx和反应慢于10s.
3.  **发自内心** - 同一活动可以不止一次进行 (一一一一送来). 按键您的钱包信用/ 状态更新 `event.id` 或者说 `event.data.transferId`。 。 。 .
4.  **运行调节作为后台** - 网络呼喊是快速路径, `syncWebhookEvents` 这是安全网。 他们有相同的处理器.

`syncWebhookEvents` 使用与用户扫描的 API 相同的 SDK mTLS 传输 呼叫、获取和缓存合伙人 `client_credentials` 内部标志, 与 SDK 重试策略重试瞬时同步失败, 并编辑符号 材质来自可观察钩.

### 持久网点收件箱 `createWebhookInbox`

对于生产集成,接受经过验证的网络钩入耐用存储器 返回之前 `200`,然后同步处理。 SDK可以拥有 当您的应用程序拥有数据库召回和钱包突变时, 状态机器 .

```typescript
import {
  createWebhookInbox,
  parseWebhookHttpRequest,
} from '@edge-markets/connect-node'

const inbox = createWebhookInbox({
  store: {
    insert: db.webhookInbox.insertIdempotently,
    find: db.webhookInbox.find,
    markProcessing: db.webhookInbox.markProcessing,
    markProcessed: db.webhookInbox.markProcessed,
    markFailed: db.webhookInbox.markFailed,
    markPending: db.webhookInbox.markPending,
    list: db.webhookInbox.list,
  },
  processEvent: async (event) => {
    // Partner-owned business logic. Keep wallet mutation idempotent.
    await processEdgeEvent(event)
  },
})

app.post('/webhooks/edge', express.raw({ type: 'application/json' }), async (req, res) => {
  const parsed = parseWebhookHttpRequest({
    request: { headers: req.headers, rawBody: req.body },
    secret: process.env.EDGE_WEBHOOK_SECRET!,
  })

  const accepted = await inbox.acceptRaw(
    parsed,
    Buffer.from(req.body).toString('utf8'),
    String(req.headers['x-edge-signature'] ?? ''),
  )

  res.status(200).json({
    received: true,
    duplicate: accepted.duplicate,
    status: accepted.status,
  })

  void inbox.process(accepted.eventId).catch((error) => {
    logger.error('EDGE webhook processing failed', { eventId: accepted.eventId, error })
  })
})
```

收件箱状态为 `pending`, (中文(简体) ), `processing`, (中文(简体) ), `processed`,以及 `failed`。 。 。 . 复制 `event.id` 如果有效载荷指纹,交货即为同位素 火柴。 比赛。 不同有效载荷的重复事件ID失败关闭 .

同步调节可以使用相同的收件箱路径 :

```typescript
const reconciler = edge.createWebhookReconciler({
  cursorStore,
  inbox,
  limit: 100,
})
```

调和器接受每个同步事件进入收件箱,处理并保存 光标仅在处理成功或事件已经处理之后. 使用 `inbox.replay(eventId)` 用于明确的管理员重放和 `inbox.recoverStaleProcessing()` 将崩溃 `processing` 行可回放.

### 转移安全助手

使用传输助手来避免小数和方向错误 :

```typescript
import {
  assertWebhookMatchesTransfer,
  createTransferIdempotencyKey,
  getConnectTransferDirection,
  mapTransferStatusToPartnerState,
  normalizeMoneyAmount,
  validateTransferAmount,
} from '@edge-markets/connect-node'

const amount = validateTransferAmount(input.amount, { min: '1.00', max: '10000.00' }, { allowNumber: false })
const idempotencyKey = createTransferIdempotencyKey({
  partnerUserId: user.id,
  type: 'debit',
  amount,
  category: 'sportsbook',
  externalId: bet.id,
})

// debit = EDGE user to partner; credit = partner to EDGE user
const direction = getConnectTransferDirection('debit')

assertWebhookMatchesTransfer(event, {
  transferId: localTransfer.edgeTransferId,
  type: 'debit',
  amount: localTransfer.amount,
})
```

## 处理错误

```typescript
import {
  EdgeError,
  EdgeAuthenticationError,
  EdgeTokenExchangeError,
  EdgeConsentRequiredError,
  isEdgeError,
} from '@edge-markets/connect-node'

try {
  const client = edge.forUser(accessToken)
  const balance = await client.getBalance()
} catch (error) {
  if (error instanceof EdgeAuthenticationError) {
    // Token expired - try refresh or reconnect
    return { error: 'session_expired' }
  }
  
  if (error instanceof EdgeConsentRequiredError) {
    // User revoked consent - need to reconnect
    return { error: 'reconnect_required' }
  }
  
  if (isEdgeError(error)) {
    // Some other SDK error
    console.error(`Edge Error [${error.code}]: ${error.message}`)
    return { error: error.code }
  }
  
  // Unknown error
  throw error
}
```

### 错误类型

| 错误 | 何时 | 怎么办 |
| --- | --- | --- |
| `EdgeAuthenticationError` | 键入无效/ 过期 | 刷新符号或重新连接 |
| `EdgeTokenExchangeError` | 代码交换失败 | 请用户再次尝试 |
| `EdgeConsentRequiredError` | 用户没有同意 | 打开边缘链接 |
| `EdgeInsufficientScopeError` | 缺少所需范围 | 请求更多范围 |
| `EdgeNotFoundError` | 未找到资源 | 检查身份 |
| `EdgeApiError` | 其他 API 错误 | 检查错误. code |
| `EdgeNetworkError` | 网络失败 | 重试请求 |

## NestJS 示例

```typescript
import { Injectable, Logger } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { EdgeConnectServer, EdgeConsentRequiredError } from '@edge-markets/connect-node'

@Injectable()
export class EdgeService {
  private readonly edge: EdgeConnectServer
  private readonly logger = new Logger(EdgeService.name)

  constructor(private config: ConfigService) {
    this.edge = new EdgeConnectServer({
      clientId: this.config.getOrThrow('EDGE_CLIENT_ID'),
      clientSecret: this.config.getOrThrow('EDGE_CLIENT_SECRET'),
      environment: this.config.get('EDGE_ENVIRONMENT', 'sandbox'),
    })
  }

  async exchangeCode(code: string, codeVerifier: string) {
    return this.edge.exchangeCode(code, codeVerifier)
  }

  async getBalance(accessToken: string) {
    try {
      const client = this.edge.forUser(accessToken)
      return await client.getBalance()
    } catch (error) {
      if (error instanceof EdgeConsentRequiredError) {
        this.logger.warn('User consent required')
        throw error
      }
      this.logger.error('Failed to get balance', error)
      throw error
    }
  }
}
```

## 快捷示例

```typescript
import express from 'express'
import { EdgeConnectServer, isEdgeError } from '@edge-markets/connect-node'

const edge = new EdgeConnectServer({
  clientId: process.env.EDGE_CLIENT_ID!,
  clientSecret: process.env.EDGE_CLIENT_SECRET!,
  environment: 'sandbox',
})

const app = express()
app.use(express.json())

// Exchange code for tokens
app.post('/api/edge/exchange', async (req, res) => {
  try {
    const { code, codeVerifier } = req.body
    const tokens = await edge.exchangeCode(code, codeVerifier)
    
    // Store tokens for user...
    
    res.json({ success: true })
  } catch (error) {
    if (isEdgeError(error)) {
      res.status(400).json({ error: error.code, message: error.message })
    } else {
      res.status(500).json({ error: 'internal_error' })
    }
  }
})

// Get balance
app.get('/api/edge/balance', async (req, res) => {
  try {
    const accessToken = await getAccessTokenForUser(req.user.id)
    const client = edge.forUser(accessToken)
    const balance = await client.getBalance()
    res.json(balance)
  } catch (error) {
    // Handle errors...
  }
})
```

## 相关软件包

-   `@edge-markets/connect` - 核心类型和公用事业
-   `@edge-markets/connect-link` - 用于弹出认证的浏览器 SDK

## 许可证

麻省理工学院

## 只读的 Webhooks v2 (2026-09-06) (中文(简体) )

有门的释放:这些出口船,下一次释放含有这一变化; 现有3.0.0装置尚未提供。 发布 - 请拥有版本. `parseAndVerifyWebhookV2` 用5分钟的时间戳验证准确的生字节 窗口、显式单元格和活性签名密钥(或边框密钥环). 它区分了经认证的未来类型和无效的已知类型。 唯一一个 商务活动是 `consent.revoked`; 保留其实际 `grantId` 发件人 在更改链接前匹配单元格/客户端/用户/赠款。 忽略 `test: true`。 。 。 .

`WebhookInboxV2` 需要一个具有独特特性的应用程序拥有的可持久适配器 租赁和围栏。 2xx 之前提交接受; 运行 a 单独的重启安全工人. `reconcileWebhooksV2` 需要分配租赁 ,将每个事件与光标一起插入一页。 从不 部分失败后提前. `WebhookCursorExpiredError` 所需赠款 从快照上取出水印.

`EdgeConnectServer.syncWebhookEventsV2`, (中文(简体) ), `getWebhookGrantSnapshot` 财务报告和审定财务报表 `getWebhookCapabilities` 使用具有 mTLS 的单独伙伴机证书. `partnerApiBaseUrl` 一定是犬科细胞网关+ `/v1` (SDK游戏机) 附录 `/partner/webhooks`细胞在中转 沙盒被粘住, 沙盒生产和生产。 没有用户端或平正文本传输回落 .

正常旋转在24小时重叠期间验证任何活性的v1签名. 过期 明示前一个接收键; 绝不记录原始签名、 证书或密钥. 离线 `connect-webhook-inspect --cell sandbox-staging --signature-file /protected/signature.txt < /protected/body.json` 命令从 `EDGE_WEBHOOK_SECRET` 并打印只验证安全元数据。 没有HTTP 请求。 使用仪表板进行真正的沙盒测试,尝试和确认再发.

见文件处网站的Webhooks、签名核查和回收指南和 亚历山大港的PostgreSQL接收器用于一个完整的应用程序适配器. 遗产 出口仍然可用;现在,遗留的调节工作停止于未处理的工作.

## 常见问题

### @edge-markets/connect-node 是什么？

EDGE 连接符号交换和 API 调用服务器 SDK

### 如何安装 @edge-markets/connect-node？

在项目目录运行 npm install @edge-markets/connect-node，或使用 pnpm add / yarn add / bun add。它还提供命令行工具 connect-webhook-inspect，可以用 npx connect-webhook-inspect 直接运行。

### @edge-markets/connect-node 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @edge-markets/connect-node 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @edge-markets/connect-node 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=22\.23\.4。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @edge-markets/connect-node 可以商用吗？

@edge-markets/connect-node 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @edge-markets/connect-node 的下载量有多少？

根据 npm 官方下载统计，@edge-markets/connect-node 在 2026/10/13 – 2026/11/11 这 30 天内被下载 16,633 次，日均约 554 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @edge-markets/connect-node 还在维护吗？

最新版本 4\.0\.0 发布于 2026/10/27（17 天前）；至今共发布 23 个版本，其中过去 12 个月发布了 23 个；首次发布于 2026/01/21。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

