// 联络中心Chatbot对接SDK,UMD规范,RSA-OAEP加密 const CHATBOT_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvtFkJ2RL3LysY0qeRLI2\ncEWM2raPuDNlttUjCnOHpz5Hei9XYW+pf/k1upUB6RwWdZhk5xRN2GgOA9Sb5n6e\nSFv5b72XHveECEpq2YH2oEBaTN55d61hX5B7NJLuGtifDriw5h+OtePPEOetjQut\n4ghHJOqZoGfdZrY6jlbj3B9wJ3pTcupugi0c2wMvbV9dQrpzzzlKs94yMWDHdzNT\nx2jt2WX12R+256yDe4/2vdGFjuxTzm9nR0SLnny2ijPtNMOKRse2kgKiKqEg+c1b\n3KIrRAS5Fk3U71bg/RLgSFChENZsVQNUGsh0Uo/qDw3AgN0b2lvvL8++tpAH265q\nBwIDAQAB\n-----END PUBLIC KEY-----`; function pemToArrayBuffer(pem) { const b64 = pem.replace(/-----.*?-----/g, '').replace(/\s+/g, ''); const binary = atob(b64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes.buffer; } async function importPublicKey(pem) { const keyBuffer = pemToArrayBuffer(pem); console.log(window.crypto); return await window.crypto.subtle.importKey( 'spki', keyBuffer, { name: 'RSA-OAEP', hash: 'SHA-1' }, false, ['encrypt'] ); } async function encryptData(publicKey, data) { const encoder = new TextEncoder(); const encoded = encoder.encode(JSON.stringify(data)); if (encoded.length > 190) throw new Error('数据超长'); const encrypted = await window.crypto.subtle.encrypt( { name: 'RSA-OAEP' }, publicKey, encoded ); return btoa(String.fromCharCode(...new Uint8Array(encrypted))); } async function getIdentity(encryptObj) { if (!encryptObj || (!encryptObj.phone && !encryptObj.email && !encryptObj.uuid)) { throw new Error('必须提供至少一个敏感信息字段'); } const publicKey = await importPublicKey(CHATBOT_PUBLIC_KEY); console.log(`%c【encryptObj】=> ${encryptObj}`, 'color:lawngreen'); return await encryptData(publicKey, encryptObj); } function buildUrl(baseUrl, params) { // 完整解析URL结构 let urlPart = '', hashPart = '', hashQuery = ''; // 1. 分离基础URL和哈希部分 if (baseUrl.includes('#')) { const [urlBase, ...hashParts] = baseUrl.split('#'); urlPart = urlBase; const hashContent = hashParts.join('#'); // 2. 检查哈希部分是否包含查询参数 if (hashContent.includes('?')) { const [hashPath, hashQueryPart] = hashContent.split('?'); hashPart = '#' + hashPath; hashQuery = '?' + hashQueryPart; } else { hashPart = '#' + hashContent; } } else { urlPart = baseUrl; } let url = urlPart + hashPart + hashQuery // 4. 添加参数到URL const queryString = new URLSearchParams(params).toString(); if (queryString) { url += (url.includes('?') ? '&' : '?') + queryString; } return url; } class SDK { /** * 获取带加密identity参数的Chatbot跳转链接 * @param {string} baseUrl - Chatbot基础URL * @param {object} options - 其他参数 { source, lang, encrypt: {phone, email, uuid} } * @returns {Promise} 拼接好的URL */ async getUrl(baseUrl, options = {}) { const { source, lang = 'zh_CN', encrypt } = options; if (!encrypt) throw new Error('encrypt字段必填'); const identity = await getIdentity(encrypt); console.log(`%c【identity】=> ${identity}`, 'color:darkorange'); const params = { identity, source, lang }; return buildUrl(baseUrl, params); } } // UMD导出 (function (root, factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof module === 'object' && module.exports) { module.exports = factory(); } else { root.sdk = factory(); } }(this, function () { return new SDK(); }));