import store from '@/store' import { Modal, message } from 'ant-design-vue' import * as SignalR from '@microsoft/signalr' const EventEmitter = require('events') const defaultOptions = { log: false } class SocketConnection extends EventEmitter { constructor(connection, options = {}) { super() this.connection = connection this.options = Object.assign(defaultOptions, options) this.listened = [] this.toSend = [] this.offline = false this.socket = undefined } /** * 同一种消息只定义一次 * * @param {string| symbol} event * @param {(...args: any[]) => void} listener * @memberof SocketConnection */ one(event, listener) { if (this.listeners(event).length === 0) { this.on(event, listener) } } async _initialize() { if(this.socket == undefined){ return; } try { await this.socket.start() this.emit('onstart') if (this.offline) { this.emit('onrestart') } this.offline = false } catch (error) { setTimeout(async () => { await this._initialize() }, 5000) } } async start(token) { // 组件重新加载时, 如果 socket 存在, 不需要新建 if (!this.socket) { this.socket = new SignalR.HubConnectionBuilder() .configureLogging(SignalR.LogLevel.Information) .withUrl(`${process.env.VUE_APP_SOCKET_BASE_URL}/hubs/chathub`, { accessTokenFactory: () => token, skipNegotiation: true, transport: SignalR.HttpTransportType.WebSockets }) .build() this.socket.onclose(async () => { this.offline = true this.emit('onclose') await this._initialize() }) this.socket.on('ForceExist', () => { // 关闭连接 this.socket.stop() // 必须 this.socket = undefined store .dispatch('Logout') .then(() => { Modal.success({ title: '消息', content: '你已被强制下线', keyboard: false, onOk: () => { window.location.reload() } }) }) .catch(err => { message.error({ title: '错误', description: err.message }) }) }) this.socket.on('SingleLoginForceExist', () => { // 关闭连接 this.socket.stop() store .dispatch('Logout') .then(() => { Modal.success({ title: '消息', content: '您的账号已在其他地方登录,被强制下线', keyboard: false, onOk: () => { window.location.reload() } }) }) .catch(err => { message.error({ title: '错误', description: err.message }) }) }) await this._initialize() } } async authenticate(token) { await this.start(token) } listen(method) { if (this.offline) return if (this.listened.some(v => v === method)) return this.listened.push(method) this.one('onstart', () => { this.listened.forEach(method => { this.socket.on(method, data => { if (this.options.log) { } this.emit(method, data) }) }) }) } send(methodName, ...args) { if (this.options.log) { } if (this.offline) return if (this.socket) { this.socket.send(methodName, ...args) return } this.one('onstart', () => this.socket.send(methodName, ...args)) } async invoke(methodName, ...args) { if (this.options.log) { } if (this.offline) return false if (this.socket) { return this.socket.invoke(methodName, ...args) } // eslint-disable-next-line no-async-promise-executor return new Promise(async resolve => this.one('onstart', () => resolve(this.socket.invoke(methodName, ...args)))) } } if (!SignalR) { throw new Error('[Vue-SignalR] Cannot locate signalr-client') } function install(Vue, connection) { if (!connection) { throw new Error('[Vue-SignalR] Cannot locate connection') } connection = process.env.VUE_APP_SOCKET_BASE_URL+connection const Socket = new SocketConnection(connection) Vue.socket = Socket Object.defineProperties(Vue.prototype, { $socket: { get() { return Socket } } }) Vue.mixin({ created() { if (this.$options.sockets) { const methods = Object.getOwnPropertyNames(this.$options.sockets) methods.forEach(method => { Socket.listen(method) Socket.one(method, data => this.$options.sockets[method].call(this, data)) }) } if (this.$options.subscribe) { Socket.one('authenticated', () => { this.$options.subscribe.forEach(channel => { Socket.invoke('join', channel) }) }) } } }) } export default install