@edge-markets/connect-node

中文文档
4.0.0发布于 2026/10/27MITTypeScriptESM + CJS近 30 天下载 1.7万

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

中文使用文档
文档目录31 节
  1. 特征
  2. 安装
  3. 快速启动
  4. • 警卫
  5. 配置
  6. 从环境变量
  7. 手动配置
  8. 信件级别加密 (MLE)
  9. 互通 TLS (mTLS)
  10. Next.js 和 Vercel 路由处理器
  11. 托肯交换
  12. 加密存储的令牌
  13. 托肯刷新和用户会话
  14. API 方法
  15. 用户余额( B)
  16. 转账
  17. 同意
  18. 网络呼号
  19. 解析和校验webhook 交付
  20. 类型安全事件处理 EdgeWebhookEvent
  21. 校正错过的事件 createWebhookReconciler
  22. 最佳做法
  23. 持久网点收件箱 createWebhookInbox
  24. 转移安全助手
  25. 处理错误
  26. 错误类型
  27. NestJS 示例
  28. 快捷示例
  29. 相关软件包
  30. 许可证
  31. 只读的 Webhooks v2 (2026-09-06) (中文(简体) )

@ 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 验证、重放控制流程以及传输 意图检查,而伙伴应用程序则保留对持久性的所有权,钱包 变异、管理授权和商业规则.

安装

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

快速启动

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 覆盖和超时解析 在所有整合中保持一致.

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_SECRETOAuth 客户端秘密
EDGE_ENVIRONMENTproduction 或者说 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)或文件路径。 如果两者都安排好了, 内入值获胜,帮助者返回警告.

手动配置

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 向您的伙伴后端发布了客户端证书和密钥时:

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 之前的节点运行时间 :

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,将软件包外部化为 应用级缓解直到您可以升级 :

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

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

export default nextConfig

托肯交换

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

// 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信封合作,提供关键标识和关键旋转支持; 您仍然选择数据库表格和用户映射.

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 处理解密-新鲜-拯救-客户生命周期。 您仍然拥有数据库表 和用户映射.

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)数字 :

const client = edge.forUser(accessToken)

用户余额( B)

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 }

转账

// 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,
})
// 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) 重排序密钥 并且无声无息地打破了没有诊断的核查. 总是抓住 原始身体通过你框架的生体中间软件.

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

// 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 自动——不需要铸造.

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 ,方法是刷新一次符牌并重试.

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可以拥有 当您的应用程序拥有数据库召回和钱包突变时, 状态机器 .

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失败关闭 .

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

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

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

转移安全助手

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

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,
})

处理错误

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 示例

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
    }
  }
}

快捷示例

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 共发布 23 个版本,首次发布于 2026/01/21;过去 12 个月发布了 23 个版本。

版本发布日期距今
4.0.0latest2026/10/2717 天前
3.1.02026/09/281 个月前
3.0.02026/08/033 个月前
2.0.02026/07/293 个月前
1.12.12026/07/273 个月前
1.12.02026/07/273 个月前

关于 @edge-markets/connect-node 的常见问题

@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 和提交记录判断。