|
@@ -0,0 +1,533 @@
|
|
|
+// 与客户端交互方法列表
|
|
|
+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<String> = [];
|
|
|
+ // 创建的控件列表
|
|
|
+ ctrls: Array<String> = [];
|
|
|
+ // 心跳
|
|
|
+ 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<O>(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;
|
|
|
+}
|