dnp-client
Version:
数据基础设施轻量连接器,帮助用户管理身份凭证、区域节点、业务节点,完成身份集成。
582 lines (523 loc) • 16.4 kB
JavaScript
import { v7 } from 'uuid';
/**
* DNP 统一身份登录 SDK
*/
class DNP {
constructor(config = {}) {
// 从 config 中提取 options 和 callbacks
const { options = {}, callbacks = {} } = config;
this.options = {
// 轮询超时时间(毫秒),默认10分钟
pollingTimeout: 600 * 1000,
// 轮询间隔(毫秒),默认1秒
pollingInterval: 1000,
// API基础URL
dnpServerUrl: "http://localhost:3521",
// API端点配置
apiEndpoints: {
getToken: "/get/token", // 登录-获取token
getCert: "/get/cert", // 注册-获取证书
importCert: "/import/cert", // 获取身份凭证-导入证书
},
// 域名配置(用于桌面应用调用)
baseUrl:
typeof window !== "undefined" ? `${window.location.origin}/api/v1` : "",
// 类型参数,默认值为1 1-区域,2-业务
type: 1,
// 客户端类型检测
clientType: DNP.detectClientType(),
...options, // 展开用户传入的 options
};
this.uuidString = "";
this.startTime = 0;
this.isPolling = false;
this.pollingTimer = null;
// 回调函数
this.callbacks = {
onLoading: (loading) => {},
onSuccess: (token) => {},
onError: (error) => {},
onMessage: (message, type = "info") => {},
...callbacks, // 展开用户传入的 callbacks
};
// 绑定方法
this.login = this.login.bind(this);
this.stopPolling = this.stopPolling.bind(this);
this.clickAuthLogin = this.clickAuthLogin.bind(this);
this.registerIdentity = this.registerIdentity.bind(this);
this.importCert = this.importCert.bind(this);
}
/**
* 检测客户端类型
*/
static detectClientType() {
if (typeof window === "undefined") return "server";
if (typeof window.sendToMain === "function") return "electron";
return "browser";
}
/**
* 开始统一身份登录流程
*/
async login() {
try {
// 清除之前的定时任务
this.stopPolling();
this.callbacks.onLoading(true);
await this.clickAuthLogin();
} catch (error) {
this.callbacks.onError(error);
this.callbacks.onLoading(false);
}
}
/**
* 统一身份登录点击处理
*/
async clickAuthLogin() {
this.uuidString = v7().slice(0, 32);
// 当前页面打开环境在桌面端内部
if (this.options.clientType === "electron") {
if (typeof window.sendToMain === "function") {
window.sendToMain({
type: "login",
data: {
uuid: this.uuidString,
},
});
this.invokeDesktopAppLogin();
} else {
this.callbacks.onMessage(
"统一身份登录暂时不可用,请使用其他登录方式",
"error"
);
this.callbacks.onLoading(false);
}
return;
}
// 浏览器环境
this.invokeDesktopApp(
`dnp://getToken?json=${JSON.stringify({
uuid: this.uuidString,
baseUrl: this.options.baseUrl,
type: this.options.type,
})}`,
() => this.invokeDesktopAppLogin()
);
}
/**
* 调用桌面应用程序
*/
invokeDesktopApp(url, callback) {
try {
// 通过iframe调用自定义协议
const iframe = document.createElement("iframe");
iframe.style.display = "none";
iframe.src = url;
document.body.appendChild(iframe);
// 清理iframe
setTimeout(() => {
document.body.removeChild(iframe);
}, 1000);
// 执行回调
if (callback && typeof callback === "function") {
callback();
}
} catch (error) {
this.callbacks.onError(error);
this.callbacks.onLoading(false);
}
}
/**
* 启动桌面应用登录流程
*/
invokeDesktopAppLogin() {
// 清除之前的定时任务
this.stopPolling();
this.startTime = Date.now();
this.isPolling = true;
this.waitForVP();
}
/**
* 轮询获取VP - 根据原始 Vue 组件的逻辑
*/
async waitForVP() {
if (!this.isPolling) {
return;
}
// 检查是否超时(10分钟)
if (Date.now() - this.startTime >= this.options.pollingTimeout) {
this.stopPolling();
this.callbacks.onMessage("获取签名失败", "error");
this.callbacks.onLoading(false);
return;
}
try {
const res = await this.apiGetToken(this.uuidString);
if (res) {
if (res === "refuse") {
this.stopPolling();
this.callbacks.onMessage("客户端正忙,请稍后再试", "error");
this.callbacks.onLoading(false);
return;
}
// 客户端返回了token,直接登录
if (res.data?.tokenValue) {
this.stopPolling();
this.callbacks.onSuccess(res.data.tokenValue);
this.callbacks.onLoading(false);
return;
}
// 如果有响应但没有tokenValue,继续轮询
this.pollingTimer = setTimeout(() => {
this.waitForVP();
}, this.options.pollingInterval);
return;
}
// 没有响应,继续轮询
this.pollingTimer = setTimeout(() => {
this.waitForVP();
}, this.options.pollingInterval);
} catch (error) {
// 出错后继续轮询
this.pollingTimer = setTimeout(() => {
this.waitForVP();
}, this.options.pollingInterval);
}
}
/**
* 停止轮询
*/
stopPolling() {
// console.log('===stopPolling called===', this.isPolling, this.pollingTimer);
this.isPolling = false;
if (this.pollingTimer) {
clearTimeout(this.pollingTimer);
this.pollingTimer = null;
// console.log('===pollingTimer cleared===');
}
}
/**
* 请求token接口
*/
async apiGetToken(uuid) {
const url = `${this.options.dnpServerUrl}${this.options.apiEndpoints.getToken}?uuid=${uuid}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
/**
* 注册身份
* @param {Object} [params={}] - 注册参数对象
* @param {string} [params.didMethod] - DID方法,可选参数
* @param {string} [params.subject] - 主体信息,可选参数
*/
async registerIdentity({ didMethod, subject } = {}) {
try {
// 清除之前的定时任务
this.stopPolling();
this.callbacks.onLoading(true);
await this.invokeDesktopAppRegister(didMethod, subject);
} catch (error) {
this.callbacks.onError(error);
this.callbacks.onLoading(false);
}
}
/**
* 调用桌面应用注册身份
*/
async invokeDesktopAppRegister(didMethod, subject) {
this.uuidString = v7().slice(0, 32);
// 构建数据对象,只包含有值的参数
const data = { uuid: this.uuidString };
if (didMethod) data.didMethod = didMethod;
if (subject) data.subject = subject;
// 当前页面打开环境在桌面端内部
if (this.options.clientType === "electron") {
if (typeof window.sendToMain === "function") {
window.sendToMain({
type: "register",
data,
});
this.invokeDesktopAppRegisterFlow();
} else {
this.callbacks.onMessage("身份注册暂时不可用,请使用其他方式", "error");
this.callbacks.onLoading(false);
}
return;
}
// 浏览器环境
this.invokeDesktopApp(`dnp://genKeys?json=${JSON.stringify(data)}`, () =>
this.invokeDesktopAppRegisterFlow()
);
}
/**
* 启动桌面应用注册流程
*/
invokeDesktopAppRegisterFlow() {
// 清除之前的定时任务
this.stopPolling();
this.startTime = Date.now();
this.isPolling = true;
// console.log('开始轮询DID,UUID:', this.uuidString);
this.waitForDid();
}
/**
* 轮询获取DID - 根据原始 Vue 组件的逻辑
*/
async waitForDid() {
if (!this.isPolling) {
return;
}
// 检查是否超时(10分钟)
if (Date.now() - this.startTime >= this.options.pollingTimeout) {
this.stopPolling();
this.callbacks.onMessage("身份注册超时", "error");
this.callbacks.onLoading(false);
return;
}
try {
const res = await this.apiGetCert("uuid", this.uuidString);
// console.log('waitForDid response:', res);
if (res) {
if (res === "refuse") {
this.stopPolling();
this.callbacks.onMessage("客户端正忙,请稍后再试", "error");
this.callbacks.onLoading(false);
return;
}
if (res.data) {
this.stopPolling();
// 如果响应有data字段,返回data,否则返回整个响应
const resultData = res.data;
this.callbacks.onSuccess(resultData);
this.callbacks.onLoading(false);
return;
}
}
// 没有有效响应或响应无效,继续轮询
if (Date.now() - this.startTime < this.options.pollingTimeout) {
// console.log('继续轮询DID...');
this.pollingTimer = setTimeout(() => {
this.waitForDid();
}, this.options.pollingInterval);
} else {
// console.log('轮询DID超时');
this.stopPolling();
this.callbacks.onMessage("身份注册超时", "error");
this.callbacks.onLoading(false);
}
} catch (error) {
// console.log('waitForDid error:', error);
// 出错后继续轮询,直到超时
if (Date.now() - this.startTime < this.options.pollingTimeout) {
// console.log('请求出错,继续轮询DID...');
this.pollingTimer = setTimeout(() => {
this.waitForDid();
}, this.options.pollingInterval);
} else {
// console.log('轮询DID超时(错误后)');
this.stopPolling();
this.callbacks.onMessage("身份注册超时", "error");
this.callbacks.onLoading(false);
}
}
}
/**
* 请求证书接口
*/
async apiGetCert(type, uuid) {
const url = `${this.options.dnpServerUrl}${this.options.apiEndpoints.getCert}?key=${type}&code=${uuid}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
/**
* 导入身份凭证
* @param {Object} certData - 身份凭证数据
* @param {string} certData.vcJson - VC JSON数据
* @param {string} certData.didHash - DID哈希
* @param {string} certData.didBlockTime - DID区块时间
* @param {Object} certData.caJson - CA证书JSON数据
* @param {string} certData.authType - 认证类型
* @param {string} certData.crt - 证书内容
* @param {string} certData.certType - 证书类型 1-主体身份 2-连接器身份 3-业务节点身份
*/
async importCert(certData) {
// console.log('===importCert===', certData);
try {
// 清除之前的定时任务
this.stopPolling();
this.callbacks.onLoading(true);
await this.invokeDesktopAppImportVc(certData);
} catch (error) {
this.callbacks.onError(error);
this.callbacks.onLoading(false);
}
}
/**
* 调用桌面应用导入VC
*/
async invokeDesktopAppImportVc(certData) {
// 当前页面打开环境在桌面端内部
if (this.options.clientType === "electron") {
if (typeof window.sendToMain === "function") {
window.sendToMain({
type: "importCert",
data: certData,
});
this.invokeDesktopAppImportVcFlow(certData);
} else {
this.callbacks.onMessage(
"身份凭证导入暂时不可用,请使用其他方式",
"error"
);
this.callbacks.onLoading(false);
}
return;
}
// 浏览器环境
this.invokeDesktopApp("dnp://import-vc", () =>
this.invokeDesktopAppImportVcFlow(certData)
);
}
/**
* 启动桌面应用导入VC流程
*/
invokeDesktopAppImportVcFlow(certData) {
// 清除之前的定时任务
this.stopPolling();
this.startTime = Date.now();
this.isPolling = true;
// console.log('开始推送VC到客户端...');
this.pushVCToClient(certData);
}
/**
* 推送VC到客户端
*/
async pushVCToClient(certData) {
// console.log('===pushVCToClient执行===', 'isPolling:', this.isPolling);
if (!this.isPolling) {
// console.log('轮询已停止,直接返回');
return;
}
// 检查是否超时(10分钟)
if (Date.now() - this.startTime >= this.options.pollingTimeout) {
this.stopPolling();
this.callbacks.onMessage("VC导入超时", "error");
this.callbacks.onLoading(false);
return;
}
try {
const res = await this.apiPushCert(certData);
// console.log('pushVCToClient response:', res);
if (res) {
if (res === "refuse") {
this.stopPolling();
this.callbacks.onMessage("客户端正忙,请稍后再试", "error");
this.callbacks.onLoading(false);
return;
}
if (res.data) {
// console.log('VC导入成功,准备停止轮询', res.data);
this.stopPolling();
// console.log('stopPolling执行后,isPolling:', this.isPolling);
this.callbacks.onMessage("VC导入成功", "success");
this.callbacks.onSuccess(res);
this.callbacks.onLoading(false);
return;
}
}
// 没有有效响应,继续轮询
if (Date.now() - this.startTime < this.options.pollingTimeout) {
// console.log('继续推送VC...', 'isPolling:', this.isPolling);
this.pollingTimer = setTimeout(() => {
// console.log('setTimeout回调执行,isPolling:', this.isPolling);
if (this.isPolling) {
this.pushVCToClient(certData);
} else {
// console.log('轮询已停止,跳过执行');
}
}, this.options.pollingInterval);
} else {
// console.log('VC推送超时');
this.stopPolling();
this.callbacks.onMessage("VC导入超时", "error");
this.callbacks.onLoading(false);
}
} catch (error) {
// console.log('pushVCToClient error:', error);
// 出错后继续轮询,直到超时
if (Date.now() - this.startTime < this.options.pollingTimeout) {
// console.log('请求出错,继续推送VC...', 'isPolling:', this.isPolling);
this.pollingTimer = setTimeout(() => {
// console.log('错误后setTimeout回调执行,isPolling:', this.isPolling);
if (this.isPolling) {
this.pushVCToClient(certData);
}
}, this.options.pollingInterval);
} else {
// console.log('VC推送超时(错误后)');
this.stopPolling();
this.callbacks.onMessage("VC导入失败", "error");
this.callbacks.onLoading(false);
}
}
}
/**
* 请求推送证书接口
*/
async apiPushCert(certData) {
const url = `${this.options.dnpServerUrl}${this.options.apiEndpoints.importCert}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certData),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
/**
* 销毁SDK实例
*/
destroy() {
this.stopPolling();
this.callbacks = {};
}
}
/**
* 创建 DNP 统一身份登录实例的工厂函数
*/
function createDnpLogin(config = {}) {
return new DNP(config);
}
/**
* 创建 DNP 身份注册实例的工厂函数
*/
function createDnpRegister(config = {}) {
return new DNP(config);
}
/**
* 创建 DNP 身份凭证导入实例的工厂函数
*/
function createDnpImportVc(config = {}) {
// console.log('===createDnpImportVc===', config);
return new DNP(config);
}
export { createDnpImportVc, createDnpLogin, createDnpRegister, DNP as default };