UNPKG

dnp-client

Version:

数据基础设施轻量连接器,帮助用户管理身份凭证、区域节点、业务节点,完成身份集成。

1,100 lines (1,002 loc) 33.3 kB
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 };