npm SDK
@ttqsoft/verify 是甜甜圈网络验证官方 JavaScript / TypeScript SDK。SDK 会自动处理公共参数、随机 nonce、时间戳和 HMAC-SHA256 签名,适合 Node.js、Electron、桌面客户端本地服务、服务端等环境。
appSecret会参与签名,请勿放在浏览器前端代码中。纯网页应用应通过自己的服务端转发调用。
安装
npm install @ttqsoft/verify
初始化
import { TtqVerifyClient } from "@ttqsoft/verify";
const client = new TtqVerifyClient({
baseUrl: "https://ttqsoft.com",
appKey: "YOUR_APP_KEY",
appSecret: "YOUR_APP_SECRET",
});
| 参数 | 类型 | 说明 |
|---|---|---|
baseUrl | string | 验证服务器地址,例如 https://ttqsoft.com 或自部署地址 |
appKey | string | 软件 App Key |
appSecret | string | 软件 App Secret,仅应在可信环境使用 |
卡密模式
const login = await client.card.login({
card: "XXXX-XXXX-XXXX-XXXX",
deviceId: "device-001",
});
console.log(login.token);
console.log(login.expires);
console.log(login.hg);
await client.card.heartbeat({
card: "XXXX-XXXX-XXXX-XXXX",
token: login.token,
});
await client.card.logout({
card: "XXXX-XXXX-XXXX-XXXX",
token: login.token,
});
常用方法:
| 方法 | 说明 |
|---|---|
client.card.login(params) | 卡密登录,首次使用时激活卡密并绑定设备 |
client.card.heartbeat(params) | 会话心跳,刷新 token 有效期 |
client.card.logout(params) | 登出并释放多开名额 |
client.card.recharge(params) | 使用未激活卡密为当前卡密续期 |
client.card.unbindDevice(params) | 解绑设备,需软件开启解绑功能 |
client.card.getConfig(params) | 获取卡密配置 |
client.card.setConfig(params) | 更新卡密配置,最大 512 字符 |
用户模式
await client.user.register({
username: "alice",
password: "123456",
});
const login = await client.user.login({
username: "alice",
password: "123456",
deviceId: "device-001",
});
await client.user.heartbeat({ token: login.token });
await client.user.logout({ token: login.token });
用户登录后可以读写自己的云端数据,适合保存软件配置、偏好、进度和云存档。单个用户在同一软件下最多保存 5MB 云端数据,单个 key 的 value 最多 1MB:
await client.user.setData({
token: login.token,
key: "settings",
value: JSON.stringify({ theme: "dark", volume: 80 }),
});
const data = await client.user.getData({
token: login.token,
key: "settings",
});
const settings = JSON.parse(data.value);
await client.user.deleteData({
token: login.token,
key: "settings",
});
常用方法:
| 方法 | 说明 |
|---|---|
client.user.register(params) | 终端用户注册 |
client.user.login(params) | 用户名密码登录 |
client.user.heartbeat(params) | 用户会话心跳 |
client.user.logout(params) | 用户登出 |
client.user.unbindDevice(params) | 解绑用户设备 |
client.user.getData(params) | 读取当前用户自己的云端数据 |
client.user.setData(params) | 保存当前用户自己的云端数据 |
client.user.deleteData(params) | 删除当前用户自己的云端数据 |
软件接口
const config = await client.software.getConfig();
const notice = await client.software.getNotice();
try {
const latest = await client.software.getLatestVersion({ version: "1.0.0" });
console.log(latest.version, latest.url);
} catch (err) {
// VerifyErrorCode.AlreadyLatestVersion 表示已是最新版
}
| 方法 | 说明 |
|---|---|
client.software.getConfig() | 获取软件全局配置 |
client.software.getNotice() | 获取软件公告 |
client.software.getLatestVersion(params) | 检查新版本 |
远程功能
const remoteVar = await client.af.getRemoteVar({ key: "announcement" });
const remoteData = await client.af.getRemoteData({ key: "myKey" });
await client.af.setRemoteData({ action: "create", key: "myKey", value: "hello" });
await client.af.setRemoteData({ action: "update", key: "myKey", value: "world" });
await client.af.setRemoteData({ action: "delete", key: "myKey" });
const result = await client.af.callRemoteFunc<{ ok: boolean }>({
funcName: "checkAccess",
params: { level: 3 },
card: "XXXX-XXXX-XXXX-XXXX",
token: login.token,
});
console.log(result.value);
| 方法 | 说明 |
|---|---|
client.af.getRemoteVar(params) | 获取远程变量 |
client.af.getRemoteData(params) | 获取远程数据 |
client.af.setRemoteData(params) | 新建、修改或删除远程数据 |
client.af.callRemoteFunc(params) | 调用远程函数 |
错误处理
所有接口失败时会抛出 VerifyError:
import { VerifyError, VerifyErrorCode } from "@ttqsoft/verify";
try {
await client.card.login({ card: "XXXX", deviceId: "device-001" });
} catch (err) {
if (err instanceof VerifyError) {
if (err.code === VerifyErrorCode.CardExpired) {
console.log("卡密已过期");
} else if (err.code === VerifyErrorCode.TokenExpired) {
console.log("登录状态已失效,请重新登录");
} else {
console.log(err.code, err.message);
}
}
}
完整卡密生命周期示例
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { TtqVerifyClient, VerifyError, VerifyErrorCode } from "@ttqsoft/verify";
const client = new TtqVerifyClient({
baseUrl: "https://ttqsoft.com",
appKey: "YOUR_APP_KEY",
appSecret: "YOUR_APP_SECRET",
});
const CARD = "XXXX-XXXX-XXXX-XXXX";
const DEVICE_ID = "device-001";
const SESSION_FILE = "./session.json";
interface Session { token: string; hg: number }
function loadSession(): Session | null {
if (!existsSync(SESSION_FILE)) return null;
try { return JSON.parse(readFileSync(SESSION_FILE, "utf8")) as Session; }
catch { return null; }
}
function saveSession(session: Session) {
writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
}
async function authenticate() {
const saved = loadSession();
if (saved?.token) {
try {
await client.card.heartbeat({ card: CARD, token: saved.token });
return saved;
} catch (err) {
if (!(err instanceof VerifyError && err.code === VerifyErrorCode.TokenExpired)) throw err;
}
}
const login = await client.card.login({ card: CARD, deviceId: DEVICE_ID });
const session = { token: login.token, hg: login.hg };
saveSession(session);
return session;
}
const session = await authenticate();
setInterval(() => {
void client.card.heartbeat({ card: CARD, token: session.token });
}, session.hg * 1000);