Chaindoc SDK 开发指南
Chaindoc 提供两款 TypeScript SDK:Server SDK 专注于后端开发(Node.js),Embed SDK 则让你轻松将签名界面嵌入 Web 应用。说实话,两款 SDK 都具备完整的类型定义,零运行时依赖,而且与任何前端框架都能完美配合。
可用 SDK 一览
关于 npm 安装和框架特定配置(环境变量、providers 等),请参考 安装指南。
- Server SDK (@chaindoc_io/server-sdk) - 适用于 Node.js 18+ 的后端集成方案
- Embed SDK (@chaindoc_io/embed-sdk) - 为 Web 应用打造的前端签名界面
- Python SDK - 即将推出
- PHP SDK - 即将推出
Server SDK
Server SDK 将 REST API 封装成类型安全的 Node.js 接口。通过它可以管理 文档、创建 签名请求、处理文件上传,还能触发区块链存证。
安装
npm install @chaindoc_io/server-sdk环境要求
Node.js 18 或更高版本(使用原生 fetch API) Secret API key(sk_live_* 或 sk_test_*)
快速开始
server.ts
import { Chaindoc } from '@chaindoc_io/server-sdk';
import { readFile } from 'fs/promises';
// 1. 初始化 SDK
const chaindoc = new Chaindoc({
secretKey: process.env.CHAINDOC_SECRET_KEY!,
});
// 2. 上传文档文件
const buffer = await readFile('./contract.pdf');
const file = new Blob([buffer], { type: 'application/pdf' });
const { media } = await chaindoc.media.upload([file]);
// 3. 创建文档
const doc = await chaindoc.documents.create({
name: 'Service Agreement',
description: 'Contract for consulting services',
media: media[0],
status: 'published', // 触发区块链存证
hashtags: ['#contract', '#2024'],
meta: [{ key: 'client', value: 'Acme Corp' }],
});
// 4. 创建签名请求
const sigRequest = await chaindoc.signatures.createRequest({
versionId: doc.document.versions[0].uuid,
recipients: [{ email: 'signer@example.com' }],
deadline: new Date('2024-12-31'),
embeddedFlow: true,
});
// 5. 为前端 SDK 创建会话
const session = await chaindoc.embedded.createSession({
email: 'signer@example.com',
metadata: {
documentId: doc.documentId,
signatureRequestId: sigRequest.signatureRequest.uuid,
},
});
console.log('Session ID:', session.sessionId);Express.js 集成
server.ts
import express from 'express';
import { Chaindoc, ChaindocError } from '@chaindoc_io/server-sdk';
import multer from 'multer';
const app = express();
const upload = multer({ storage: multer.memoryStorage() });
const chaindoc = new Chaindoc({
secretKey: process.env.CHAINDOC_SECRET_KEY!,
});
// 上传并创建文档
app.post('/api/documents', upload.single('file'), async (req, res) => {
try {
const file = new Blob([req.file!.buffer], { type: req.file!.mimetype });
const { media } = await chaindoc.media.upload([file]);
const doc = await chaindoc.documents.create({
name: req.body.name,
description: req.body.description || '',
media: media[0],
status: 'published',
hashtags: req.body.hashtags || [],
meta: req.body.meta || [],
});
res.json({ documentId: doc.documentId });
} catch (error) {
if (error instanceof ChaindocError) {
res.status(error.statusCode || 500).json({ error: error.message });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
// 为签署人创建嵌入式会话
app.post('/api/signing/session', async (req, res) => {
try {
const { email, documentId, signatureRequestId } = req.body;
const session = await chaindoc.embedded.createSession({
email,
metadata: { documentId, signatureRequestId },
});
res.json({ sessionId: session.sessionId });
} catch (error) {
if (error instanceof ChaindocError) {
res.status(error.statusCode || 500).json({ error: error.message });
}
}
});
app.listen(3000);Next.js API Routes
app/api/signing/create-session/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Chaindoc, ChaindocError } from '@chaindoc_io/server-sdk';
const chaindoc = new Chaindoc({
secretKey: process.env.CHAINDOC_SECRET_KEY!,
});
export async function POST(request: NextRequest) {
try {
const { email, documentId, signatureRequestId } = await request.json();
const session = await chaindoc.embedded.createSession({
email,
metadata: { documentId, signatureRequestId },
});
return NextResponse.json({
sessionId: session.sessionId,
expiresAt: session.expiresAt,
});
} catch (error) {
if (error instanceof ChaindocError) {
return NextResponse.json(
{ error: error.message },
{ status: error.statusCode || 500 }
);
}
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}错误处理
建议始终用 try-catch 包裹 API 调用,并针对 ChaindocError 做特殊处理:
terminal
import { ChaindocError } from '@chaindoc_io/server-sdk';
try {
const doc = await chaindoc.documents.create({ /* ... */ });
} catch (error) {
if (error instanceof ChaindocError) {
console.error('API Error:', error.message);
console.error('Status Code:', error.statusCode);
switch (error.statusCode) {
case 400:
// 请求参数有误 - 请检查参数
break;
case 401:
// 未授权 - 请检查 API key
break;
case 404:
// 资源不存在
break;
case 429:
// 触发限流 - SDK 会自动重试
break;
}
}
}Embed SDK
Embed SDK 让你能在自己的 Web 应用中展示 Chaindoc 签名界面。它负责处理 iframe、OTP 验证,以及应用与 Chaindoc 之间的通信。关键是,用户无需离开你的网站就能完成文档签署。
安装
npm install @chaindoc_io/embed-sdk包含特性
零运行时依赖 完整的 TypeScript 支持 框架无关(支持 React、Vue、Angular、Svelte) 轻量级:gzip 后小于 10KB 安全可靠:源验证 + iframe 沙箱隔离
基本用法
terminal
import { ChaindocEmbed } from '@chaindoc_io/embed-sdk';
// 1. 初始化 SDK(每页只需一次)
const chaindoc = new ChaindocEmbed({
publicKey: 'pk_live_xxxxxxxxxxxxx',
environment: 'production',
});
// 2. 从后端获取会话
const response = await fetch('/api/signing/create-session', {
method: 'POST',
body: JSON.stringify({ documentId, signerEmail }),
});
const { sessionId } = await response.json();
// 3. 打开签名流程
const instance = chaindoc.openSignatureFlow({
sessionId,
onReady: () => {
console.log('Signing interface loaded');
},
onSuccess: (data) => {
console.log('Document signed:', data.signatureId);
instance.close();
},
onError: (error) => {
console.error('Signing failed:', error.code, error.message);
},
onCancel: () => {
console.log('User cancelled');
instance.close();
},
});React 集成
components/SignButton.tsx
import { useCallback, useRef, useEffect } from 'react';
import { ChaindocEmbed, EmbedInstance } from '@chaindoc_io/embed-sdk';
function SignButton({ sessionId }: { sessionId: string }) {
const sdkRef = useRef<ChaindocEmbed | null>(null);
const instanceRef = useRef<EmbedInstance | null>(null);
useEffect(() => {
sdkRef.current = new ChaindocEmbed({
publicKey: process.env.REACT_APP_CHAINDOC_PUBLIC_KEY!,
});
return () => {
sdkRef.current?.destroy();
};
}, []);
const handleSign = useCallback(() => {
if (!sdkRef.current) return;
instanceRef.current = sdkRef.current.openSignatureFlow({
sessionId,
onSuccess: (data) => {
console.log('Signed!', data.signatureId);
instanceRef.current?.close();
},
onCancel: () => {
instanceRef.current?.close();
},
});
}, [sessionId]);
return <button onClick={handleSign}>Sign Document</button>;
}Vue 3 集成
SignButton.vue
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { ChaindocEmbed, type EmbedInstance } from '@chaindoc_io/embed-sdk';
const props = defineProps<{ sessionId: string }>();
let sdk: ChaindocEmbed | null = null;
let instance: EmbedInstance | null = null;
onMounted(() => {
sdk = new ChaindocEmbed({
publicKey: import.meta.env.VITE_CHAINDOC_PUBLIC_KEY,
});
});
onUnmounted(() => {
sdk?.destroy();
});
function openSignature() {
if (!sdk) return;
instance = sdk.openSignatureFlow({
sessionId: props.sessionId,
onSuccess: (data) => {
console.log('Signed!', data.signatureId);
instance?.close();
},
onCancel: () => {
instance?.close();
},
});
}
</script>
<template>
<button @click="openSignature">Sign Document</button>
</template>内联模式
如果不想要弹窗,可以直接将签名界面嵌入页面指定位置:
terminal
const instance = chaindoc.openSignatureFlow({
sessionId,
mode: 'inline',
container: document.getElementById('signature-container'),
onSuccess: (data) => {
console.log('Signed!');
},
});主题定制
通过浅色或深色主题自定义外观:
terminal
const instance = chaindoc.openSignatureFlow({
sessionId,
theme: 'dark',
// ... 其他选项
});
// 动态切换主题
instance.changeTheme('light');完整工作流示例
下面演示如何结合两款 SDK:后端使用 Server SDK 创建文档和会话,前端使用 Embed SDK 展示签名界面。
1后端:上传文档使用 Server SDK 上传文件并创建文档
2后端:创建签名请求启用嵌入式流程创建签名请求
3后端:生成会话为每位签署人创建嵌入式会话
4前端:初始化 Embed SDK使用 public key 初始化 SDK
5前端:打开签名流程用 session ID 打开签名界面
6前端:处理成功回调处理已签名文档并更新界面
// server.ts
import { Chaindoc } from '@chaindoc_io/server-sdk';
const chaindoc = new Chaindoc({
secretKey: process.env.CHAINDOC_SECRET_KEY!,
});
// 上传并创建文档
const { media } = await chaindoc.media.upload([pdfFile]);
const doc = await chaindoc.documents.create({
name: 'Contract',
description: 'Service agreement',
media: media[0],
status: 'published',
hashtags: ['#contract'],
meta: [],
});
// 创建签名请求
const sigRequest = await chaindoc.signatures.createRequest({
versionId: doc.document.versions[0].uuid,
recipients: [{ email: 'signer@example.com' }],
deadline: new Date('2024-12-31'),
embeddedFlow: true,
});
// 创建会话
const session = await chaindoc.embedded.createSession({
email: 'signer@example.com',
metadata: {
documentId: doc.documentId,
signatureRequestId: sigRequest.signatureRequest.uuid,
},
});
// 返回 sessionId 给前端
res.json({ sessionId: session.sessionId });最佳实践
- 每个页面或组件生命周期内只初始化 SDK 一次
- 组件卸载时务必销毁 SDK 实例
- 处理所有回调事件(onSuccess、onError、onCancel)
- 将 API key 存储在环境变量中
- 使用 TypeScript 获得更好的类型安全
- 用 ChaindocError 实现完善的错误处理
- 生产部署前先用 sandbox key 进行测试
环境配置
// Backend
const chaindoc = new Chaindoc({
secretKey: 'sk_live_xxxxx',
});
// Frontend
const embed = new ChaindocEmbed({
publicKey: 'pk_live_xxxxx',
environment: 'production',
});