UNPKG

dnp-client

Version:

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

582 lines (523 loc) 16.4 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", // 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 };