dnp-client
Version:
数据基础设施轻量连接器,帮助用户管理身份凭证、区域节点、业务节点,完成身份集成。
475 lines (468 loc) • 20.7 kB
JavaScript
import { v7 as t } from "uuid";
class i {
constructor(t = {}) {
const {options: s = {}, callbacks: o = {}} = t;
this.options = {
pollingTimeout: 6e5,
pollingInterval: 1e3,
dnpServerUrl: "http://localhost:3521",
certificate: "",
requestTimeout: 6e4,
apiEndpoints: {
getAuthResult: "/get/authResult",
getToken: "/get/token",
getCert: "/get/cert",
importCert: "/import/cert",
createCert: "/create/cert"
},
baseUrl: "undefined" != typeof window ? `${window.location.origin}/api/v1` : "",
type: 1,
clientType: i.detectClientType(),
...s
}, this.uuidString = "", this.startTime = 0, this.isPolling = !1, this.pollingTimer = null,
this.callbacks = {
onLoading: t => {},
onSuccess: t => {},
onError: t => {},
onMessage: (t, i = "info") => {},
...o
}, 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() {
return "undefined" == typeof window ? "server" : "function" == typeof window.sendToMain ? "electron" : "browser";
}
static getBrowserInfo() {
if ("undefined" == typeof window || !window.navigator) return "unknown";
const {userAgent: t} = window.navigator;
let i = "unknown";
return t.indexOf("Chrome") > -1 && -1 === t.indexOf("Edg") ? i = "Chrome" : t.indexOf("Firefox") > -1 ? i = "Firefox" : t.indexOf("Safari") > -1 && -1 === t.indexOf("Chrome") ? i = "Safari" : t.indexOf("Edg") > -1 ? i = "Edge" : t.indexOf("Opera") > -1 || t.indexOf("OPR") > -1 ? i = "Opera" : (t.indexOf("MSIE") > -1 || t.indexOf("Trident") > -1) && (i = "IE"),
i;
}
async login() {
try {
this.stopPolling(), this.callbacks.onLoading(!0), await this.clickAuthLogin();
} catch (t) {
this.callbacks.onError(t), this.callbacks.onLoading(!1);
}
}
async clickAuthLogin() {
if (!this.options.nodeId) {
const t = {
message: "身份认证失败(JS1001),请联系您当前要登录的平台方进行处理",
data: {
message: "缺少必需参数:nodeId"
}
};
throw this.callbacks.onError(t), this.callbacks.onMessage("身份认证失败(JS1001),请联系您当前要登录的平台方进行处理", "error"),
this.callbacks.onLoading(!1), t;
}
if ("string" != typeof this.options.nodeId) {
const t = {
message: "身份认证失败(JS1002),请联系您当前要登录的平台方进行处理",
data: {
message: "nodeId 参数值无效,必须是字符串",
data: this.options.nodeId
}
};
throw this.callbacks.onError(t), this.callbacks.onMessage("身份认证失败(JS1002),请联系您当前要登录的平台方进行处理", "error"),
this.callbacks.onLoading(!1), t;
}
this.uuidString = t().slice(0, 32), this.invokeDesktopApp(`dnp://getToken?json=${JSON.stringify({
uuid: this.uuidString,
nodeId: this.options.nodeId,
browser: i.getBrowserInfo()
})}`, async () => {
this.invokeDesktopAppLogin();
});
}
invokeDesktopApp(t, i) {
try {
const s = document.createElement("iframe");
s.style.display = "none", s.src = t, document.body.appendChild(s), setTimeout(() => {
document.body.removeChild(s);
}, 1e3), i && "function" == typeof i && i();
} catch (t) {
this.callbacks.onError(t), this.callbacks.onLoading(!1);
}
}
invokeDesktopAppLogin() {
this.stopPolling(), this.startTime = Date.now(), this.isPolling = !0, this.waitForResult();
}
async waitForResult() {
if (this.isPolling) {
if (Date.now() - this.startTime >= this.options.pollingTimeout) return this.stopPolling(),
this.callbacks.onMessage("获取认证结果失败", "error"), void this.callbacks.onLoading(!1);
try {
const t = await this.apiGetAuthResult(this.uuidString);
if (t) {
if ("refuse" === t) return this.stopPolling(), this.callbacks.onMessage("客户端正忙,请稍后再试", "error"),
void this.callbacks.onLoading(!1);
if (t.data) {
if (t.data?.success) return this.stopPolling(), this.callbacks.onSuccess(), void this.callbacks.onLoading(!1);
if (!t.data?.success) return this.stopPolling(), this.callbacks.onError(t.data),
void this.callbacks.onLoading(!1);
}
return void (this.pollingTimer = setTimeout(() => {
this.waitForResult();
}, this.options.pollingInterval));
}
this.pollingTimer = setTimeout(() => {
this.waitForResult();
}, this.options.pollingInterval);
} catch (t) {
if ("CORS" === t.type) return this.stopPolling(), this.callbacks.onMessage("请求失败(JS5001),请联系您当前要登录的平台方进行处理", "error"),
this.callbacks.onError({
message: "请求失败(JS5001),请联系您当前要登录的平台方进行处理"
}), void this.callbacks.onLoading(!1);
this.pollingTimer = setTimeout(() => {
this.waitForResult();
}, this.options.pollingInterval);
}
}
}
async fetchToken(t) {
if (!t) {
throw {
message: "获取token失败(JS2001),请联系您当前要登录的平台方进行处理",
data: {
message: "缺少必需参数:uuid"
}
};
}
const i = await this.apiGetToken(t);
if ("refuse" === i) {
throw {
message: "客户端正忙,请稍后再试"
};
}
if (i.data?.tokenValue) return i.data.tokenValue;
throw {
message: i.data?.message || "获取token失败"
};
}
async waitForVP() {
if (this.isPolling) {
if (Date.now() - this.startTime >= this.options.pollingTimeout) return this.stopPolling(),
this.callbacks.onMessage("获取签名失败", "error"), void this.callbacks.onLoading(!1);
try {
const t = await this.apiGetToken(this.options.uuidString);
if (t) {
if ("refuse" === t) return this.stopPolling(), this.callbacks.onMessage("客户端正忙,请稍后再试", "error"),
void this.callbacks.onLoading(!1);
if (t.data) {
if (t.data?.tokenValue) return this.stopPolling(), this.callbacks.onSuccess(t.data.tokenValue),
void this.callbacks.onLoading(!1);
if (!t.data?.tokenValue) return this.stopPolling(), this.callbacks.onError(t.data),
void this.callbacks.onLoading(!1);
}
return void (this.pollingTimer = setTimeout(() => {
this.waitForVP();
}, this.options.pollingInterval));
}
this.pollingTimer = setTimeout(() => {
this.waitForVP();
}, this.options.pollingInterval);
} catch (t) {
if ("CORS" === t.type) return this.stopPolling(), this.callbacks.onMessage("请求失败(JS5001),请联系您当前要登录的平台方进行处理", "error"),
this.callbacks.onError({
message: "请求失败(JS5001),请联系您当前要登录的平台方进行处理"
}), void this.callbacks.onLoading(!1);
this.pollingTimer = setTimeout(() => {
this.waitForVP();
}, this.options.pollingInterval);
}
}
}
stopPolling() {
this.isPolling = !1, this.pollingTimer && (clearTimeout(this.pollingTimer), this.pollingTimer = null);
}
async fetchWithErrorHandling(t, i = {}, s = "") {
const o = new AbortController, e = setTimeout(() => {
o.abort();
}, this.options.requestTimeout);
try {
const s = await fetch(t, {
...i,
signal: o.signal
});
if (clearTimeout(e), !s.ok) {
throw {
message: "请求失败(JS1004),请联系您当前要登录的平台方进行处理",
data: {
message: "请求失败",
data: s,
url: t
}
};
}
return await s.json();
} catch (i) {
clearTimeout(e);
let o = s || "请求失败", n = "error";
const a = i?.message || i?.toString() || String(i), r = i?.name || "";
"AbortError" === r || "TimeoutError" === r ? (o = `请求超时,请检查网络连接或稍后重试,请求地址:${t}`,
n = "error") : "TypeError" === r && (a.includes("Failed to fetch") || a.includes("NetworkError") || a.includes("Network request failed")) ? (o = `网络请求失败,可能是跨域问题或服务器未响应,请检查网络连接和服务器配置,请求地址:${t}`,
n = "CORS") : a.includes("CORS") ? (o = `跨域请求被阻止,请检查服务器CORS配置或联系管理员,请求地址:${t}`,
n = "CORS") : a && "[object Object]" !== a ? (o = `${a},请求地址:${t}`, n = "error") : o = `${o},请求地址:${t}`;
const l = {
message: o,
originalError: i,
status: i?.status,
type: n,
url: t
};
throw this.callbacks.onMessage && this.callbacks.onMessage(o, n), l;
}
}
async apiGetAuthResult(t) {
const i = `${this.options.dnpServerUrl}${this.options.apiEndpoints.getAuthResult}?uuid=${t}`;
return this.fetchWithErrorHandling(i, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}, "身份认证失败(JS1004),联系您当前要登录的平台方进行处理");
}
async apiGetToken(t) {
const i = `${this.options.dnpServerUrl}${this.options.apiEndpoints.getToken}?uuid=${t}`;
return this.fetchWithErrorHandling(i, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}, "获取令牌失败(JS2002),请联系您当前要登录的平台方进行处理");
}
async createCertificate() {
try {
const t = `${this.options.dnpServerUrl}${this.options.apiEndpoints.createCert}`;
return await this.fetchWithErrorHandling(t, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
certificate: this.options.certificate
})
}, "身份注册失败(JS3008),请联系您当前要登录的平台方进行处理");
} catch (t) {
throw this.callbacks.onError(t), this.callbacks.onLoading(!1), t;
}
}
async registerIdentity(t = {}) {
try {
if (this.stopPolling(), this.callbacks.onLoading(!0), !t.certType) {
const t = {
message: "身份注册失败(JS3001),请联系您当前要登录的平台方进行处理",
data: {
message: "缺少必需参数:certType"
}
};
throw this.callbacks.onError(t), this.callbacks.onMessage("身份注册失败(JS3001),请联系您当前要登录的平台方进行处理", "error"),
this.callbacks.onLoading(!1), t;
}
if (![ "1", "2", "3" ].includes(t.certType)) {
const i = {
message: "身份注册失败(JS3002),请联系您当前要登录的平台方进行处理",
data: {
message: 'certType 参数值无效,必须是 "1"、"2" 或 "3"',
data: t.certType
}
};
throw this.callbacks.onError(i), this.callbacks.onMessage("身份注册失败(JS3002),请联系您当前要登录的平台方进行处理", "error"),
this.callbacks.onLoading(!1), i;
}
if (!t.subject) {
const i = {
message: "身份注册失败(JS3003),请联系您当前要登录的平台方进行处理",
data: {
message: "缺少必需参数:subject",
data: t.subject
}
};
throw this.callbacks.onError(i), this.callbacks.onMessage("身份注册失败(JS3003),请联系您当前要登录的平台方进行处理", "error"),
this.callbacks.onLoading(!1), i;
}
let i;
try {
if (i = "string" == typeof t.subject ? JSON.parse(t.subject) : t.subject, Array.isArray(i)) {
const t = {};
i.forEach(i => {
i && "object" == typeof i && i.name && i.value && (t[i.name] = i.value);
}), i = t;
}
} catch (i) {
const s = {
message: "身份注册失败(JS3004),请联系您当前要登录的平台方进行处理",
data: {
message: "subject 参数必须是有效的 JSON 字符串格式或数组格式",
data: t.subject
}
};
throw this.callbacks.onError(s), this.callbacks.onMessage("身份注册失败(JS3004),请联系您当前要登录的平台方进行处理", "error"),
this.callbacks.onLoading(!1), s;
}
if (!i.O) {
const i = {
message: "身份注册失败(JS3005),请联系您当前要登录的平台方进行处理",
data: {
message: "subject 中缺少必填字段:O(企业名称)",
data: t.subject
}
};
throw this.callbacks.onError(i), this.callbacks.onMessage("身份注册失败(JS3005),请联系您当前要登录的平台方进行处理", "error"),
this.callbacks.onLoading(!1), i;
}
if (!i.C) {
const i = {
message: "身份注册失败(JS3006),请联系您当前要登录的平台方进行处理",
data: {
message: "subject 中缺少必填字段:C(国家代码)",
data: t.subject
}
};
throw this.callbacks.onError(i), this.callbacks.onMessage("身份注册失败(JS3006),请联系您当前要登录的平台方进行处理", "error"),
this.callbacks.onLoading(!1), i;
}
if (("2" === t.certType || "3" === t.certType) && !i.OU) {
const i = {
message: "身份注册失败(JS3007),请联系您当前要登录的平台方进行处理",
data: {
message: "subject 中缺少必填字段:OU(所属主体的标识)。连接器身份和业务节点身份必须提供 OU 字段",
data: t.subject
}
};
throw this.callbacks.onError(i), this.callbacks.onMessage("身份注册失败(JS3007),请联系您当前要登录的平台方进行处理", "error"),
this.callbacks.onLoading(!1), i;
}
const s = "string" == typeof t.subject ? t.subject : JSON.stringify(i);
await this.invokeDesktopAppRegister(t.certType, s, t.didMethod);
} catch (t) {
this.callbacks.onError(t), this.callbacks.onLoading(!1);
}
}
async invokeDesktopAppRegister(i, s, o) {
this.uuidString = t().slice(0, 32);
const e = {
uuid: this.uuidString,
certType: i,
subject: s
};
o && (e.didMethod = o), "electron" !== this.options.clientType ? this.invokeDesktopApp(`dnp://genKeys?json=${JSON.stringify(e)}`, () => this.invokeDesktopAppRegisterFlow()) : "function" == typeof window.sendToMain ? (window.sendToMain({
type: "register",
data: e
}), this.invokeDesktopAppRegisterFlow()) : (this.callbacks.onMessage("身份注册暂时不可用,请使用其他方式", "error"),
this.callbacks.onLoading(!1));
}
invokeDesktopAppRegisterFlow() {
this.stopPolling(), this.startTime = Date.now(), this.isPolling = !0, this.waitForDid();
}
async waitForDid() {
if (this.isPolling) {
if (Date.now() - this.startTime >= this.options.pollingTimeout) return this.stopPolling(),
this.callbacks.onMessage("身份注册超时", "error"), void this.callbacks.onLoading(!1);
try {
const t = await this.apiGetCert("uuid", this.uuidString);
if (t) {
if ("refuse" === t) return this.stopPolling(), this.callbacks.onMessage("客户端正忙,请稍后再试", "error"),
void this.callbacks.onLoading(!1);
if (t.data) {
this.stopPolling();
const i = t.data;
return this.callbacks.onSuccess(i), void this.callbacks.onLoading(!1);
}
}
Date.now() - this.startTime < this.options.pollingTimeout ? this.pollingTimer = setTimeout(() => {
this.waitForDid();
}, this.options.pollingInterval) : (this.stopPolling(), this.callbacks.onMessage("身份注册超时", "error"),
this.callbacks.onLoading(!1));
} catch (t) {
if ("CORS" === t.type) return this.stopPolling(), this.callbacks.onMessage("请求失败(JS5001),请联系您当前要登录的平台方进行处理", "error"),
this.callbacks.onError({
message: "请求失败(JS5001),请联系您当前要登录的平台方进行处理"
}), void this.callbacks.onLoading(!1);
Date.now() - this.startTime < this.options.pollingTimeout ? this.pollingTimer = setTimeout(() => {
this.waitForDid();
}, this.options.pollingInterval) : (this.stopPolling(), this.callbacks.onMessage("身份注册超时", "error"),
this.callbacks.onLoading(!1));
}
}
}
async apiGetCert(t, i) {
const s = `${this.options.dnpServerUrl}${this.options.apiEndpoints.getCert}?key=${t}&code=${i}`;
return this.fetchWithErrorHandling(s, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}, "身份注册失败(JS3008),请联系您当前要登录的平台方进行处理");
}
async importCert(t) {
try {
this.stopPolling(), this.callbacks.onLoading(!0), await this.invokeDesktopAppImportVc(t);
} catch (t) {
this.callbacks.onError(t), this.callbacks.onLoading(!1);
}
}
async invokeDesktopAppImportVc(t) {
"electron" !== this.options.clientType ? this.invokeDesktopApp("dnp://import-vc", () => this.invokeDesktopAppImportVcFlow(t)) : "function" == typeof window.sendToMain ? (window.sendToMain({
type: "importCert",
data: t
}), this.invokeDesktopAppImportVcFlow(t)) : (this.callbacks.onMessage("身份凭证导入暂时不可用,请使用其他方式", "error"),
this.callbacks.onLoading(!1));
}
invokeDesktopAppImportVcFlow(t) {
this.stopPolling(), this.startTime = Date.now(), this.isPolling = !0, this.pushVCToClient(t);
}
async pushVCToClient(t) {
if (this.isPolling) {
if (Date.now() - this.startTime >= this.options.pollingTimeout) return this.stopPolling(),
this.callbacks.onMessage("VC导入超时", "error"), void this.callbacks.onLoading(!1);
try {
const i = await this.apiPushCert(t);
if (i) {
if ("refuse" === i) return this.stopPolling(), this.callbacks.onMessage("客户端正忙,请稍后再试", "error"),
void this.callbacks.onLoading(!1);
if (i.data) return this.stopPolling(), this.callbacks.onMessage("VC导入成功", "success"),
this.callbacks.onSuccess(i), void this.callbacks.onLoading(!1);
}
Date.now() - this.startTime < this.options.pollingTimeout ? this.pollingTimer = setTimeout(() => {
this.isPolling && this.pushVCToClient(t);
}, this.options.pollingInterval) : (this.stopPolling(), this.callbacks.onMessage("VC导入超时", "error"),
this.callbacks.onLoading(!1));
} catch (i) {
Date.now() - this.startTime < this.options.pollingTimeout ? this.pollingTimer = setTimeout(() => {
this.isPolling && this.pushVCToClient(t);
}, this.options.pollingInterval) : (this.stopPolling(), this.callbacks.onMessage("VC导入失败", "error"),
this.callbacks.onLoading(!1));
}
}
}
async apiPushCert(t) {
const i = `${this.options.dnpServerUrl}${this.options.apiEndpoints.importCert}`;
return this.fetchWithErrorHandling(i, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(t)
}, "身份凭证导入失败(JS4003),请联系您当前要登录的平台方进行处理");
}
destroy() {
this.stopPolling(), this.callbacks = {};
}
}
function createDnpLogin(t = {}) {
return new i(t);
}
function createFetchToken(t = {}) {
return new i(t);
}
function createDnpRegister(t = {}) {
return new i(t);
}
function createDnpImportVc(t = {}) {
return new i(t);
}
export { createDnpImportVc, createDnpLogin, createDnpRegister, createFetchToken, i as default };