dnp-client
Version:
数据基础设施轻量连接器,帮助用户管理身份凭证、区域节点、业务节点,完成身份集成。
1,100 lines (1,002 loc) • 33.3 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",
// 证书
certificate: "",
// HTTP请求超时时间(毫秒),默认60秒
requestTimeout: 60 * 1000,
// API端点配置
apiEndpoints: {
getAuthResult: "/get/authResult", // 登录-认证结果
getToken: "/get/token", // 登录-获取token
getCert: "/get/cert", // 注册-获取证书
importCert: "/import/cert", // 获取身份凭证-导入证书
createCert: "/create/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";
}
/**
* 获取当前浏览器信息
*/
static getBrowserInfo() {
if (typeof window === "undefined" || !window.navigator) {
return "unknown";
}
const { userAgent } = window.navigator;
let browser = "unknown";
if (userAgent.indexOf("Chrome") > -1 && userAgent.indexOf("Edg") === -1) {
browser = "Chrome";
} else if (userAgent.indexOf("Firefox") > -1) {
browser = "Firefox";
} else if (
userAgent.indexOf("Safari") > -1 &&
userAgent.indexOf("Chrome") === -1
) {
browser = "Safari";
} else if (userAgent.indexOf("Edg") > -1) {
browser = "Edge";
} else if (
userAgent.indexOf("Opera") > -1 ||
userAgent.indexOf("OPR") > -1
) {
browser = "Opera";
} else if (
userAgent.indexOf("MSIE") > -1 ||
userAgent.indexOf("Trident") > -1
) {
browser = "IE";
}
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() {
// 检查是否提供了证书参数
if (!this.options.nodeId) {
const error = {
message: "身份认证失败(JS1001),请联系您当前要登录的平台方进行处理",
data: {
message: "缺少必需参数:nodeId",
},
};
this.callbacks.onError(error);
this.callbacks.onMessage(
"身份认证失败(JS1001),请联系您当前要登录的平台方进行处理",
"error"
);
this.callbacks.onLoading(false);
throw error;
}
if (typeof this.options.nodeId !== "string") {
const error = {
message: "身份认证失败(JS1002),请联系您当前要登录的平台方进行处理",
data: {
message: "nodeId 参数值无效,必须是字符串",
data: this.options.nodeId,
},
};
this.callbacks.onError(error);
this.callbacks.onMessage(
"身份认证失败(JS1002),请联系您当前要登录的平台方进行处理",
"error"
);
this.callbacks.onLoading(false);
throw error;
}
this.uuidString = v7().slice(0, 32);
// 浏览器环境
this.invokeDesktopApp(
`dnp://getToken?json=${JSON.stringify({
uuid: this.uuidString,
nodeId: this.options.nodeId,
browser: DNP.getBrowserInfo(),
})}`,
async () => {
// 先发送证书创建请求,成功后再开始轮询
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.waitForResult();
}
/**
* 轮询获取认证结果 - 根据原始 Vue 组件的逻辑
*/
async waitForResult() {
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.apiGetAuthResult(this.uuidString);
// console.log('===dnp-client.js===waitForResult res===', res);
if (res) {
if (res === "refuse") {
this.stopPolling();
this.callbacks.onMessage("客户端正忙,请稍后再试", "error");
this.callbacks.onLoading(false);
return;
}
if (res.data) {
// 客户端返回了token,直接登录
if (res.data?.success) {
this.stopPolling();
this.callbacks.onSuccess();
this.callbacks.onLoading(false);
return;
}
// 客户端返回了错误信息
if (!res.data?.success) {
this.stopPolling();
this.callbacks.onError(res.data);
this.callbacks.onLoading(false);
return;
}
}
// 如果有响应但没有tokenValue,继续轮询
this.pollingTimer = setTimeout(() => {
this.waitForResult();
}, this.options.pollingInterval);
return;
}
// 没有响应,继续轮询
this.pollingTimer = setTimeout(() => {
this.waitForResult();
}, this.options.pollingInterval);
} catch (error) {
// console.log('===dnp-client.js===waitForResult catch error===', error);
// 如果错误是CORS,就不继续轮询了
if (error.type === "CORS") {
this.stopPolling();
this.callbacks.onMessage(
"请求失败(JS5001),请联系您当前要登录的平台方进行处理",
"error"
);
this.callbacks.onError({
message: "请求失败(JS5001),请联系您当前要登录的平台方进行处理",
});
this.callbacks.onLoading(false);
return;
}
// 出错后继续轮询
this.pollingTimer = setTimeout(() => {
this.waitForResult();
}, this.options.pollingInterval);
}
}
/**
* 获取token(单次调用)
* @param {string} uuid - UUID参数
* @returns {Promise<string>} token值
*/
async fetchToken(uuid) {
if (!uuid) {
const error = {
message: "获取token失败(JS2001),请联系您当前要登录的平台方进行处理",
data: {
message: "缺少必需参数:uuid",
},
};
throw error;
}
const res = await this.apiGetToken(uuid);
if (res === "refuse") {
const error = {
message: "客户端正忙,请稍后再试",
};
throw error;
}
if (res.data?.tokenValue) {
return res.data.tokenValue;
}
const error = {
message: res.data?.message || "获取token失败",
};
throw error;
}
/**
* 轮询获取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.options.uuidString);
if (res) {
if (res === "refuse") {
this.stopPolling();
this.callbacks.onMessage("客户端正忙,请稍后再试", "error");
this.callbacks.onLoading(false);
return;
}
if (res.data) {
// 客户端返回了token,直接登录
if (res.data?.tokenValue) {
this.stopPolling();
this.callbacks.onSuccess(res.data.tokenValue);
this.callbacks.onLoading(false);
return;
}
// 客户端返回了错误信息
if (!res.data?.tokenValue) {
this.stopPolling();
this.callbacks.onError(res.data);
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) {
// console.log('===dnp-client.js===waitForVP catch error===', error);
// 如果错误是CORS,就不继续轮询了
if (error.type === "CORS") {
this.stopPolling();
this.callbacks.onMessage(
"请求失败(JS5001),请联系您当前要登录的平台方进行处理",
"error"
);
this.callbacks.onError({
message: "请求失败(JS5001),请联系您当前要登录的平台方进行处理",
});
this.callbacks.onLoading(false);
return;
}
// 出错后继续轮询
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===');
}
}
/**
* 统一的 HTTP 请求处理方法,包含错误处理和超时控制
* @param {string} url - 请求URL
* @param {Object} options - fetch选项
* @param {string} [errorMessage] - 自定义错误提示信息
* @returns {Promise<any>} 响应数据
*/
async fetchWithErrorHandling(url, options = {}, errorMessage = "") {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, this.options.requestTimeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const error = {
message: "请求失败(JS1004),请联系您当前要登录的平台方进行处理",
data: {
message: "请求失败",
data: response,
url,
},
};
throw error;
}
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
// 处理不同类型的错误
let errorMsg = errorMessage || "请求失败";
let errorType = "error";
// 获取错误信息字符串(兼容多种错误格式)
const errorMessageStr =
error?.message || error?.toString() || String(error);
const errorName = error?.name || "";
if (errorName === "AbortError" || errorName === "TimeoutError") {
// 超时错误
errorMsg = `请求超时,请检查网络连接或稍后重试,请求地址:${url}`;
errorType = "error";
} else if (
errorName === "TypeError" &&
(errorMessageStr.includes("Failed to fetch") ||
errorMessageStr.includes("NetworkError") ||
errorMessageStr.includes("Network request failed"))
) {
// 网络错误(包括跨域、连接失败等)
errorMsg = `网络请求失败,可能是跨域问题或服务器未响应,请检查网络连接和服务器配置,请求地址:${url}`;
errorType = "CORS";
} else if (errorMessageStr.includes("CORS")) {
// 明确的跨域错误
errorMsg = `跨域请求被阻止,请检查服务器CORS配置或联系管理员,请求地址:${url}`;
errorType = "CORS";
} else if (errorMessageStr && errorMessageStr !== "[object Object]") {
// 其他错误,使用原始错误信息并添加URL
errorMsg = `${errorMessageStr},请求地址:${url}`;
errorType = "error";
} else {
// 如果没有错误消息,添加URL
errorMsg = `${errorMsg},请求地址:${url}`;
}
// 通过回调通知错误
const enhancedError = {
message: errorMsg,
originalError: error,
status: error?.status,
type: errorType,
url,
};
// 如果提供了错误消息回调,则调用
if (this.callbacks.onMessage) {
this.callbacks.onMessage(errorMsg, errorType);
}
throw enhancedError;
}
}
/**
* 请求认证结果接口
*/
async apiGetAuthResult(uuid) {
const url = `${this.options.dnpServerUrl}${this.options.apiEndpoints.getAuthResult}?uuid=${uuid}`;
return this.fetchWithErrorHandling(
url,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
"身份认证失败(JS1004),联系您当前要登录的平台方进行处理"
);
}
/**
* 请求token接口
*/
async apiGetToken(uuid) {
const url = `${this.options.dnpServerUrl}${this.options.apiEndpoints.getToken}?uuid=${uuid}`;
return this.fetchWithErrorHandling(
url,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
"获取令牌失败(JS2002),请联系您当前要登录的平台方进行处理"
);
}
/**
* 创建证书
*/
async createCertificate() {
try {
const url = `${this.options.dnpServerUrl}${this.options.apiEndpoints.createCert}`;
const result = await this.fetchWithErrorHandling(
url,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
certificate: this.options.certificate,
}),
},
"身份注册失败(JS3008),请联系您当前要登录的平台方进行处理"
);
return result;
} catch (error) {
this.callbacks.onError(error);
this.callbacks.onLoading(false);
throw error;
}
}
/**
* 注册身份
* @param {Object} params - 注册参数对象
* @param {string} params.certType - 凭证类型,必选。可选值:"1"(主体身份)、"2"(连接器身份)、"3"(业务节点身份)
* @param {string|Object|Array} params.subject - 主体信息,必选。支持三种格式:
* 1. JSON 字符串格式:'{"O":"企业名称","C":"CN"}'
* 2. 对象格式:{O:"企业名称",C:"CN"}
* 3. 数组格式:[{name:"O",value:"企业名称"},{name:"C",value:"CN"}]
* 支持的字段:O(企业名称,必选)、OU(所属主体的标识,可选)、C(国家代码,必选)
* @param {string} [params.didMethod] - DID方法,可选参数
*/
async registerIdentity(params = {}) {
try {
// 清除之前的定时任务
this.stopPolling();
this.callbacks.onLoading(true);
// 验证必选参数 certType
if (!params.certType) {
const error = {
message: "身份注册失败(JS3001),请联系您当前要登录的平台方进行处理",
data: {
message: "缺少必需参数:certType",
},
};
this.callbacks.onError(error);
this.callbacks.onMessage(
"身份注册失败(JS3001),请联系您当前要登录的平台方进行处理",
"error"
);
this.callbacks.onLoading(false);
throw error;
}
// 验证 certType 值
if (!["1", "2", "3"].includes(params.certType)) {
const error = {
message: "身份注册失败(JS3002),请联系您当前要登录的平台方进行处理",
data: {
message: 'certType 参数值无效,必须是 "1"、"2" 或 "3"',
data: params.certType,
},
};
this.callbacks.onError(error);
this.callbacks.onMessage(
"身份注册失败(JS3002),请联系您当前要登录的平台方进行处理",
"error"
);
this.callbacks.onLoading(false);
throw error;
}
// 验证必选参数 subject
if (!params.subject) {
const error = {
message: "身份注册失败(JS3003),请联系您当前要登录的平台方进行处理",
data: {
message: "缺少必需参数:subject",
data: params.subject,
},
};
this.callbacks.onError(error);
this.callbacks.onMessage(
"身份注册失败(JS3003),请联系您当前要登录的平台方进行处理",
"error"
);
this.callbacks.onLoading(false);
throw error;
}
// 验证 subject 是否为有效的 JSON 字符串或数组
let subjectObj;
try {
subjectObj =
typeof params.subject === "string"
? JSON.parse(params.subject)
: params.subject;
// 如果是数组格式,转换为对象格式
if (Array.isArray(subjectObj)) {
const convertedSubject = {};
subjectObj.forEach((item) => {
if (item && typeof item === "object" && item.name && item.value) {
convertedSubject[item.name] = item.value;
}
});
subjectObj = convertedSubject;
}
} catch (_parseError) {
const error = {
message: "身份注册失败(JS3004),请联系您当前要登录的平台方进行处理",
data: {
message: "subject 参数必须是有效的 JSON 字符串格式或数组格式",
data: params.subject,
},
};
this.callbacks.onError(error);
this.callbacks.onMessage(
"身份注册失败(JS3004),请联系您当前要登录的平台方进行处理",
"error"
);
this.callbacks.onLoading(false);
throw error;
}
// 验证 subject 必填字段 O(企业名称)
if (!subjectObj.O) {
const error = {
message: "身份注册失败(JS3005),请联系您当前要登录的平台方进行处理",
data: {
message: "subject 中缺少必填字段:O(企业名称)",
data: params.subject,
},
};
this.callbacks.onError(error);
this.callbacks.onMessage(
"身份注册失败(JS3005),请联系您当前要登录的平台方进行处理",
"error"
);
this.callbacks.onLoading(false);
throw error;
}
// 验证 subject 必填字段 C(国家代码)
if (!subjectObj.C) {
const error = {
message: "身份注册失败(JS3006),请联系您当前要登录的平台方进行处理",
data: {
message: "subject 中缺少必填字段:C(国家代码)",
data: params.subject,
},
};
this.callbacks.onError(error);
this.callbacks.onMessage(
"身份注册失败(JS3006),请联系您当前要登录的平台方进行处理",
"error"
);
this.callbacks.onLoading(false);
throw error;
}
// 根据 certType 验证 OU 字段
// certType 为 "2"(连接器身份)或 "3"(业务节点身份)时,OU 必选
if (
(params.certType === "2" || params.certType === "3") &&
!subjectObj.OU
) {
const error = {
message: "身份注册失败(JS3007),请联系您当前要登录的平台方进行处理",
data: {
message:
"subject 中缺少必填字段:OU(所属主体的标识)。连接器身份和业务节点身份必须提供 OU 字段",
data: params.subject,
},
};
this.callbacks.onError(error);
this.callbacks.onMessage(
"身份注册失败(JS3007),请联系您当前要登录的平台方进行处理",
"error"
);
this.callbacks.onLoading(false);
throw error;
}
// 将 subject 对象转换回 JSON 字符串(如果原本是对象的话)
const subjectString =
typeof params.subject === "string"
? params.subject
: JSON.stringify(subjectObj);
await this.invokeDesktopAppRegister(
params.certType,
subjectString,
params.didMethod
);
} catch (error) {
this.callbacks.onError(error);
this.callbacks.onLoading(false);
}
}
/**
* 调用桌面应用注册身份
* @param {string} certType - 凭证类型
* @param {string} subject - 主体信息(JSON 字符串)
* @param {string} [didMethod] - DID方法,可选参数
*/
async invokeDesktopAppRegister(certType, subject, didMethod) {
this.uuidString = v7().slice(0, 32);
// 构建数据对象
const data = {
uuid: this.uuidString,
certType,
subject,
};
if (didMethod) data.didMethod = didMethod;
// 当前页面打开环境在桌面端内部
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('===dnp-client.js===waitForDid catch error===', error);
// 如果错误是CORS,就不继续轮询了
if (error.type === "CORS") {
this.stopPolling();
this.callbacks.onMessage(
"请求失败(JS5001),请联系您当前要登录的平台方进行处理",
"error"
);
this.callbacks.onError({
message: "请求失败(JS5001),请联系您当前要登录的平台方进行处理",
});
this.callbacks.onLoading(false);
return;
}
// 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}`;
return this.fetchWithErrorHandling(
url,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
"身份注册失败(JS3008),请联系您当前要登录的平台方进行处理"
);
}
/**
* 导入身份凭证
* @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}`;
return this.fetchWithErrorHandling(
url,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(certData),
},
"身份凭证导入失败(JS4003),请联系您当前要登录的平台方进行处理"
);
}
/**
* 销毁SDK实例
*/
destroy() {
this.stopPolling();
this.callbacks = {};
}
}
/**
* 创建 DNP 统一身份登录实例的工厂函数
*/
function createDnpLogin(config = {}) {
return new DNP(config);
}
/**
* 创建 DNP 获取Token的工厂函数
*/
function createFetchToken(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, createFetchToken, DNP as default };