// 与客户端交互方法列表 import wsPlugins from './plugins/index'; // const ReWebSocket = require('reconnecting-websocket'); // websocket重连 import ReWebSocket from '../node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs'; // const Bowser = require('bowser'); // 浏览器信息获取 import Bowser from "../node_modules/bowser/es5"; const browser = Bowser.getParser(window.navigator.userAgent); interface callbacks { // 连接客户端状态 connectResult: any, // 登录客户端状态 loginResult: any } export default class Ws { [x: string]: any; // 连接地址 static url: string = ''; // 传入的配置项(方法) static opts: Object = {}; // 密码加密公钥 static publicKey = ''; // 是否连接客户端 private isConnectSuccessQt: Boolean = false; // 是否登录客户端 private isLoginSuccess: Boolean = false; // 唯一实例 private static _instance: any = null; // 当前连接的webSocket webSocket: any; // 最大重连数 reConnectCount: number = 3; // 连接失败次数 connectFailCount: number = 0; // 重连完成标识 connectEnd: Boolean = false; // 登录完成标识 loginEnd: Boolean = false; // 最大重登数 reLoginCount: number = 1; // 连接失败次数 loginFailCount: number = 0; // 当前websocket登录配置 config: Object = {}; // 登陆IP loginIp: string; // 登陆端口 loginPort: string; // 登录用户名 userName: string; // 登录密码,密码与token二选一 userPwd: string; // 登录token,密码与token二选一 token: string; // 用户标识符 userCode: Number = 0; // 回调函数 private callback: callbacks; // 创建的控件ID列表 ids: Array = []; // 创建的控件列表 ctrls: Array = []; // 心跳 heartBeatTimer: any = null; // 监听的方法表 listerns: any; constructor({ url= 'ws://localhost:1234', publicKey= 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDbEpPpxpLJft4W9YZj8bRh2bYYZshBEsKOlxgyn11rlEyTasjBSZRV9aj33tvQ2T55izH0fWl+dL/dLZChawFrlGDcH8JuWge2xYMgII9mggcYa0UiQ7pLXJ9ivXZ/cOY3HzrRQdR7dGTSNn3Z0Ctbns6mLgvlA2r3qMNs/8wHBwIDAQAB', reConnectCount = 3, reLoginCount = 1, loginIp= location.hostname, loginPort= location.port, userName = '', userPwd = '', token = '', callback = { connectResult: null, loginResult: null } } : { url: string, publicKey: string, reConnectCount: number, reLoginCount: number, loginIp: string, loginPort: string, userName: string, userPwd: string, token: string callback: callbacks, }) { // if (/https/.test(location.protocol)) { // url = `wss:${url}`; // } else { // url = `ws:${url}`; // } // url = `ws://localhost:1234`; this.url = url; this.userCode = new Date().valueOf(); this.webSocket = null; this.reConnectCount = reConnectCount; this.reLoginCount = reLoginCount; this.publicKey = publicKey; this.loginIp = loginIp; this.loginPort = loginPort; this.userName = userName; this.userPwd = userPwd; this.token = token; this.callback = callback; this.connectFailCount = this.connectFailCount; // 基础操作所需功能或属性 this.ids = []; this.ctrls = []; // 是否连接客户端 this.isConnectSuccessQt = false; // 是否登陆客户端 this.isLoginSuccess = false; this.heartBeatTimer = null; // 初始化配置参数 this.initConfig(); // 连接客户端 this.connectQt(); // 获取与客户端交互方法列表 const plugins = []; Object.keys(wsPlugins).forEach(item => { if(hasKey(wsPlugins, item)){ plugins.push(wsPlugins[item]); } }); // 与客户端通讯功能注册 usePlugin(plugins); // 用户注册监听事件表 this.listerns = new Map(); // 传入登录配置,则登录 if(this.userName) { this.detectConnectQt().then((res: Boolean) => { if(res) { // 登陆客户端 this.loginClient(); // 心跳保活 this._heartbeat(); } }); } } /** * @description 获得实例对象 */ public static getInstance(options: any):Ws { if(!this._instance) { this._instance = new Ws(options); } return this._instance; } /** * @description 用户注册监听事件 * @params {String} eventType 事件名称 * @params {any} callback 回调函数 */ on(eventType: String, callback: any) { this.listerns.set(eventType, callback); } /** * @description 用户取消监听事件 * @params {String} eventType 事件名称 */ off(eventType: string) { delete this.listerns[eventType]; } /** * @description 发送消息给客户端 * @params {String} method 事件名称 * @params {Object} data 传输消息的数据内容 */ emit(method: any, data: { method: any; }) { const { webSocket } = this; data.method = method; // 不需要判断登录和连接的方法过滤 let filterList = ['heartbeat', 'login', 'logout', 'browserInfo']; if(filterList.includes(method)) { webSocket.send(JSON.stringify(data)); return; } return new Promise((resolve, reject) => { this.detectConnectQt().then(con => { if(con) { this.detectLoginClient().then(login => { if(login) { webSocket.send(JSON.stringify(data)); resolve(true); } else { // 登录失败 reject(2); } }) } else { // 连接失败 reject(1); } }) }) } /* * 初始化配置 */ initConfig() { this.config = { userName: this.userName, userCode: this.userCode, loginPort: this.loginPort, loginIp: this.loginIp, userPwd: this.userPwd, token: this.token, }; // 浏览器信息 const browserInfo = { name: '', version: '', platform: '' }; browserInfo.name = browser.getBrowserName().toLowerCase(); browserInfo.version = browser.getBrowser().version.toLowerCase(); browserInfo.platform = browser._ua.indexOf('Win64') >= 0 || browser._ua.indexOf('Wow64') >= 0 ? 'win64' : 'win32'; this.config['browser'] = browserInfo; } /** * @description 连接客户端 */ connectQt() { this.connectEnd = false; // 连接客户端 this.webSocket = new ReWebSocket(this.url, '', { maxRetries: this.reConnectCount }); this.addEvents(); } /** * @description 检测连接客户端状态 */ detectConnectQt() { let _this = this; return new Promise((resolve, reject) => { if(!this.connectEnd) { // 连接中 let _interval = setInterval(() => { if(_this.connectEnd) { clearInterval(_interval); resolve(_this.isConnectSuccessQt); } }, 50) } else { resolve(_this.isConnectSuccessQt); } }) } /** * @description 检测登录客户端状态 */ detectLoginClient() { let _this = this; return new Promise((resolve, reject) => { if(!this.loginEnd) { // 连接中 let _interval = setInterval(() => { if(_this.loginEnd) { clearInterval(_interval); resolve(_this.isLoginSuccess); } }, 50) } else { resolve(_this.isLoginSuccess); } }) } /** * @description 登录客户端 * @params {Object} config 登录相关配置 */ login(config: Object) { this.loginEnd = false; Object.assign(this.config, config); // 未连接客户端 if(!this.isOpen()) { // 连接失败 return false } else { // 若已登录,先注销 if (this.isLoginSuccess) { this.logout(); } // 登陆客户端 this.loginClient(); // 心跳保活 this._heartbeat(); } } /** * @description 连接客户端完成后登陆 */ // loginAfterConnectEnd() { // // 未连接客户端 // if(!this.isOpen()) { // // 连接失败 // return false // } else { // // 若已登录,先注销 // if (this.isLoginSuccess) { // this.logout(); // } // // 登陆客户端 // this.loginClient(); // // 心跳保活 // this._heartbeat(); // } // } /** * @description 登出客户端 */ logout() { // 退出客户端 this.logoutClient(); this.isLoginSuccess = false; if (typeof this.callback.loginResult === 'function') { this.callback.loginResult.call(this, this.isLoginSuccess); } } /** * @description 添加websocket/window监听事件 */ addEvents() { const webSocket = this.webSocket; webSocket.addEventListener('open', () => this.onOpen()); webSocket.addEventListener('message', (e: any) => this.onMessage(e)); webSocket.addEventListener('error', () => this.onError()); window.addEventListener('resize', () => this.reLocatedPosition()); window.addEventListener('scroll', () => this.reLocatedPosition()); } /** * @description 移除websocket/window监听事件 */ removeEvents() { const webSocket = this.webSocket; webSocket.removeEventListener('open', this.onOpen); webSocket.removeEventListener('message', this.onMessage); webSocket.removeEventListener('error', this.onError); webSocket.removeEventListener('resize', this.reLocatedPosition); webSocket.removeEventListener('scroll', this.reLocatedPosition); } /** * @description 连接客户端成功事件 */ onOpen() { this.isConnectSuccessQt = true; this.connectEnd = true; if (typeof this.callback.connectResult === 'function') { this.callback.connectResult.call(this, this.isConnectSuccessQt); } } /** * @description 接收客户端消息 * @params {Object} event 接收客户端的消息数据 */ onMessage(event: { data: string; }) { try { const data = JSON.parse(event.data); const { method } = data; const callback = this.listerns.get(method); if (method === 'loginState') { this.loginFailCount ++; if(this.loginFailCount < this.reLoginCount + 1) { // 登录未到最大次数 if(data.params.loginResult === 0) { // 登录成功 this.isLoginSuccess = data.params.loginResult === 0; if (typeof this.callback.loginResult === 'function') { this.callback.loginResult.call(this, this.isLoginSuccess); } } else { // 登录失败 this.loginClient(); } } else { // 登录达到最大次数 this.loginEnd = true; this.isLoginSuccess = data.params.loginResult === 0; if (typeof this.callback.loginResult === 'function') { this.callback.loginResult.call(this, this.isLoginSuccess); } } } if (callback) { if (method === 'loginState') { callback(this.isLoginSuccess); } else if (method === 'createCtrlResult') { callback(data.params.array); } else { callback(data); } } } catch (e) { // console.error('error', e); } } /** * @description 客户端发生错误事件 */ onError() { this.isConnectSuccessQt = false; clearTimeout(this.heartbeatTimer); this.connectFailCount ++; if(this.connectFailCount === this.reConnectCount + 1) { this.connectEnd = true; if (typeof this.callback.connectResult === 'function') { this.callback.connectResult.call(this, this.isConnectSuccessQt); } } } /** * @description 判断是否成功连接客户端 */ isOpen() { if (!this.webSocket) return false; return this.webSocket.readyState === 1; } /** * @description 心跳事件 */ _heartbeat() { this.heartbeat(); clearTimeout(this.heartbeatTimer); this.heartbeatTimer = setTimeout(() => { this._heartbeat(); }, 10000); } /** * @description 获取当前浏览器缩放和滚动条信息 */ getScrollInfo() { let ratio = detectZoom(); let scrollX = window.pageXOffset; let scrollY = window.pageYOffset; var hasscrollbary = hasScrollbarY(); var hasscrollbarx = hasScrollbarX(); var scrollbarWidth = getScrollbarWidth(); let scrollXH = hasscrollbarx ? scrollbarWidth : 0; let scrollYW = hasscrollbary ? scrollbarWidth : 0; return { ratio, scrollX, scrollY, scrollXH, scrollYW }; } } function promisify(func: Function) { return new Promise((resolve, reject) => { func('1', (data) => resolve(data)) }) } /** * @description 判断对象是否含有某属性 * @params {Object} obj 对象 * @params {String} key 属性key */ function hasKey(obj: O, key: keyof any): key is keyof O { return key in obj } /** * @description 与客户端通讯功能注册 * @params {Array} plugins 功能列表 */ function usePlugin(plugins: any[]) { plugins.forEach((plugin) => { Object.getOwnPropertyNames(plugin).forEach(prop => { Ws.prototype[prop] = plugin[prop]; }); }); } function detectZoom() { var ratio = 0, screen = window.screen, ua = navigator.userAgent.toLowerCase(); if (window.devicePixelRatio !== undefined) { ratio = window.devicePixelRatio; } else if (~ua.indexOf('msie')) { if (screen['deviceXDPI'] && screen['logicalXDPI']) { ratio = screen['deviceXDPI'] / screen['logicalXDPI']; } } else if (window.outerWidth !== undefined && window.innerWidth !== undefined) { ratio = window.outerWidth / window.innerWidth; } if (ratio) { ratio = Math.round(ratio * 100); } return ratio; } function hasScrollbarY() { return ( document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight) ); } function hasScrollbarX() { return ( document.body.scrollWidth > (window.innerWidth || document.documentElement.clientWidth) ); } function getScrollbarWidth() { var scrollDiv = document.createElement('div'); scrollDiv.style.cssText = 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;'; document.body.appendChild(scrollDiv); var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; document.body.removeChild(scrollDiv); return scrollbarWidth; }