import { AuthenticationTokenProvider } from './AuthenticationTokenProvider'; import { AuthenticationClientOptions, } from './types'; import { HttpClient } from '../common/HttpClient'; import { BaseAuthenticationClient } from './BaseAuthenticationClient'; import { SDK_VERSION } from '../version'; /** * @class EventAuthenticationClient 认证侧事件发布订阅模块 * @description 认证侧事件发布订阅模块 * @name EventAuthenticationClient */ export class EventAuthenticationClient { options: AuthenticationClientOptions; tokenProvider: AuthenticationTokenProvider; httpClient: HttpClient; baseClient: BaseAuthenticationClient; private wsMap: {[propName: string]: { socket: WebSocket, lockConnect: boolean, timeConnect: number }}; private eventBus: {[propName: string]: [Function, Function][]}; constructor( options: AuthenticationClientOptions, tokenProvider: AuthenticationTokenProvider, httpClient: HttpClient ) { this.options = options; this.tokenProvider = tokenProvider; this.httpClient = httpClient; this.wsMap = {}; this.eventBus = {} this.baseClient = new BaseAuthenticationClient(options); } private reconnect(eventName: string) { return new Promise((resolve, reject) => { if (this.options.retryTimes && this.wsMap[eventName].timeConnect < this.options.retryTimes) { if (!this.wsMap[eventName].lockConnect) { this.wsMap[eventName].lockConnect = true this.wsMap[eventName].timeConnect ++ setTimeout(() => { this.wsMap[eventName].lockConnect = false this.initWebSocket(eventName, true).then(() => { resolve(true) }).catch((e) => { reject(e) }) }, 2000); } } else { reject(`socket 服务器连接超时`); } }) } private initWebSocket(eventName: string, retry?: boolean) { return new Promise(async(resolve, reject) => { if (!this.wsMap[eventName] || retry) { const token = await this.tokenProvider.getToken(); this.wsMap[eventName] = { socket: new WebSocket(`${this.options.socketUri}/events/v1/authentication/sub?code=${eventName}&token=${token}`), timeConnect: retry ? this.wsMap[eventName].timeConnect : 0, lockConnect: false } this.wsMap[eventName].socket.onopen = () => { resolve(true) } this.wsMap[eventName].socket.addEventListener('message', (event) => { try { if (this.eventBus[eventName]) { this.eventBus[eventName].forEach(callback => { callback[0](event.data.toString("utf8")) }) } else { // 未订阅事件 console.warn("未订阅的事件:", eventName); } } catch (error) { return reject(`数据格式化错误,检查传输数据格式!!! ${error}`); } }) this.wsMap[eventName].socket.addEventListener('error', async(e) => { try { await this.reconnect(eventName) resolve(true) } catch (error) { return reject(`socket 连接异常:${JSON.stringify(e)}`) } }) this.wsMap[eventName].socket.onclose = async() => { try { await this.reconnect(eventName) resolve(true) } catch (error) { return reject('socket 服务器连接超时') } } } else { resolve(true) } }) } public sub(eventName: string, callback: Function, errCallback: Function) { /** * 1. 判断是否连接 socket * 2. 获取 socket 实例 * 3. 订阅 */ if (typeof eventName !== 'string') { throw new Error("订阅事件名称为 string 类型!!!") } if (typeof callback !== 'function') { throw new Error("订阅事件回调函数需要为 function 类型!!!"); } if (!this.options.socketUri) { throw new Error("订阅事件需要添加 socketUri 连接地址!!!") } this.initWebSocket(eventName).catch(e => { this.eventBus[eventName].forEach((item) => { item[1]?.(e) }) }) if (this.eventBus[eventName]) { this.eventBus[eventName].push([callback, errCallback]) } else { this.eventBus[eventName] = [[callback, errCallback]] } } /** * @summary 事件发布 * @description 客户调用发布事件到事件中心 * @returns */ public async pub(eventName: string, data: string) { if (typeof eventName !== 'string') { throw new Error("事件名称为 string 类型!!!") } if (typeof data !== 'string') { throw new Error("发布数据为 string 类型!!!") } return await this.httpClient.request({ method: 'POST', url: `${this.baseClient.appHost}/api/v3/pub-userEvent`, data: { eventType: eventName, source: `authing-js-sdk: ${SDK_VERSION}`, eventData: data }, }); } }