main.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. // 与客户端交互方法列表
  2. import wsPlugins from './plugins/index';
  3. // const ReWebSocket = require('reconnecting-websocket');
  4. // websocket重连
  5. import ReWebSocket from '../node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs';
  6. // const Bowser = require('bowser');
  7. // 浏览器信息获取
  8. import Bowser from "../node_modules/bowser/es5";
  9. const browser = Bowser.getParser(window.navigator.userAgent);
  10. interface callbacks {
  11. // 连接客户端状态
  12. connectResult: any,
  13. // 登录客户端状态
  14. loginResult: any
  15. }
  16. export default class Ws {
  17. [x: string]: any;
  18. // 连接地址
  19. static url: string = '';
  20. // 传入的配置项(方法)
  21. static opts: Object = {};
  22. // 密码加密公钥
  23. static publicKey = '';
  24. // 是否连接客户端
  25. private isConnectSuccessQt: Boolean = false;
  26. // 是否登录客户端
  27. private isLoginSuccess: Boolean = false;
  28. // 唯一实例
  29. private static _instance: any = null;
  30. // 当前连接的webSocket
  31. webSocket: any;
  32. // 最大重连数
  33. reConnectCount: number = 3;
  34. // 连接失败次数
  35. connectFailCount: number = 0;
  36. // 重连完成标识
  37. connectEnd: Boolean = false;
  38. // 登录完成标识
  39. loginEnd: Boolean = false;
  40. // 最大重登数
  41. reLoginCount: number = 1;
  42. // 连接失败次数
  43. loginFailCount: number = 0;
  44. // 当前websocket登录配置
  45. config: Object = {};
  46. // 登陆IP
  47. loginIp: string;
  48. // 登陆端口
  49. loginPort: string;
  50. // 登录用户名
  51. userName: string;
  52. // 登录密码,密码与token二选一
  53. userPwd: string;
  54. // 登录token,密码与token二选一
  55. token: string;
  56. // 用户标识符
  57. userCode: Number = 0;
  58. // 回调函数
  59. private callback: callbacks;
  60. // 创建的控件ID列表
  61. ids: Array<String> = [];
  62. // 创建的控件列表
  63. ctrls: Array<String> = [];
  64. // 心跳
  65. heartBeatTimer: any = null;
  66. // 监听的方法表
  67. listerns: any;
  68. constructor({
  69. url= 'ws://localhost:1234',
  70. publicKey= 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDbEpPpxpLJft4W9YZj8bRh2bYYZshBEsKOlxgyn11rlEyTasjBSZRV9aj33tvQ2T55izH0fWl+dL/dLZChawFrlGDcH8JuWge2xYMgII9mggcYa0UiQ7pLXJ9ivXZ/cOY3HzrRQdR7dGTSNn3Z0Ctbns6mLgvlA2r3qMNs/8wHBwIDAQAB',
  71. reConnectCount = 3,
  72. reLoginCount = 1,
  73. loginIp= location.hostname,
  74. loginPort= location.port,
  75. userName = '',
  76. userPwd = '',
  77. token = '',
  78. callback = {
  79. connectResult: null,
  80. loginResult: null
  81. }
  82. } : {
  83. url: string,
  84. publicKey: string,
  85. reConnectCount: number,
  86. reLoginCount: number,
  87. loginIp: string,
  88. loginPort: string,
  89. userName: string,
  90. userPwd: string,
  91. token: string
  92. callback: callbacks,
  93. }) {
  94. // if (/https/.test(location.protocol)) {
  95. // url = `wss:${url}`;
  96. // } else {
  97. // url = `ws:${url}`;
  98. // }
  99. // url = `ws://localhost:1234`;
  100. this.url = url;
  101. this.userCode = new Date().valueOf();
  102. this.webSocket = null;
  103. this.reConnectCount = reConnectCount;
  104. this.reLoginCount = reLoginCount;
  105. this.publicKey = publicKey;
  106. this.loginIp = loginIp;
  107. this.loginPort = loginPort;
  108. this.userName = userName;
  109. this.userPwd = userPwd;
  110. this.token = token;
  111. this.callback = callback;
  112. this.connectFailCount = this.connectFailCount;
  113. // 基础操作所需功能或属性
  114. this.ids = [];
  115. this.ctrls = [];
  116. // 是否连接客户端
  117. this.isConnectSuccessQt = false;
  118. // 是否登陆客户端
  119. this.isLoginSuccess = false;
  120. this.heartBeatTimer = null;
  121. // 初始化配置参数
  122. this.initConfig();
  123. // 连接客户端
  124. this.connectQt();
  125. // 获取与客户端交互方法列表
  126. const plugins = [];
  127. Object.keys(wsPlugins).forEach(item => {
  128. if(hasKey(wsPlugins, item)){
  129. plugins.push(wsPlugins[item]);
  130. }
  131. });
  132. // 与客户端通讯功能注册
  133. usePlugin(plugins);
  134. // 用户注册监听事件表
  135. this.listerns = new Map();
  136. // 传入登录配置,则登录
  137. if(this.userName) {
  138. this.detectConnectQt().then((res: Boolean) => {
  139. if(res) {
  140. // 登陆客户端
  141. this.loginClient();
  142. // 心跳保活
  143. this._heartbeat();
  144. }
  145. });
  146. }
  147. }
  148. /**
  149. * @description 获得实例对象
  150. */
  151. public static getInstance(options: any):Ws {
  152. if(!this._instance) {
  153. this._instance = new Ws(options);
  154. }
  155. return this._instance;
  156. }
  157. /**
  158. * @description 用户注册监听事件
  159. * @params {String} eventType 事件名称
  160. * @params {any} callback 回调函数
  161. */
  162. on(eventType: String, callback: any) {
  163. this.listerns.set(eventType, callback);
  164. }
  165. /**
  166. * @description 用户取消监听事件
  167. * @params {String} eventType 事件名称
  168. */
  169. off(eventType: string) {
  170. delete this.listerns[eventType];
  171. }
  172. /**
  173. * @description 发送消息给客户端
  174. * @params {String} method 事件名称
  175. * @params {Object} data 传输消息的数据内容
  176. */
  177. emit(method: any, data: { method: any; }) {
  178. const { webSocket } = this;
  179. data.method = method;
  180. // 不需要判断登录和连接的方法过滤
  181. let filterList = ['heartbeat', 'login', 'logout', 'browserInfo'];
  182. if(filterList.includes(method)) {
  183. webSocket.send(JSON.stringify(data));
  184. return;
  185. }
  186. return new Promise((resolve, reject) => {
  187. this.detectConnectQt().then(con => {
  188. if(con) {
  189. this.detectLoginClient().then(login => {
  190. if(login) {
  191. webSocket.send(JSON.stringify(data));
  192. resolve(true);
  193. } else {
  194. // 登录失败
  195. reject(2);
  196. }
  197. })
  198. } else {
  199. // 连接失败
  200. reject(1);
  201. }
  202. })
  203. })
  204. }
  205. /*
  206. * 初始化配置
  207. */
  208. initConfig() {
  209. this.config = {
  210. userName: this.userName,
  211. userCode: this.userCode,
  212. loginPort: this.loginPort,
  213. loginIp: this.loginIp,
  214. userPwd: this.userPwd,
  215. token: this.token,
  216. };
  217. // 浏览器信息
  218. const browserInfo = {
  219. name: '',
  220. version: '',
  221. platform: ''
  222. };
  223. browserInfo.name = browser.getBrowserName().toLowerCase();
  224. browserInfo.version = browser.getBrowser().version.toLowerCase();
  225. browserInfo.platform =
  226. browser._ua.indexOf('Win64') >= 0 || browser._ua.indexOf('Wow64') >= 0
  227. ? 'win64'
  228. : 'win32';
  229. this.config['browser'] = browserInfo;
  230. }
  231. /**
  232. * @description 连接客户端
  233. */
  234. connectQt() {
  235. this.connectEnd = false;
  236. // 连接客户端
  237. this.webSocket = new ReWebSocket(this.url, '', {
  238. maxRetries: this.reConnectCount
  239. });
  240. this.addEvents();
  241. }
  242. /**
  243. * @description 检测连接客户端状态
  244. */
  245. detectConnectQt() {
  246. let _this = this;
  247. return new Promise((resolve, reject) => {
  248. if(!this.connectEnd) { // 连接中
  249. let _interval = setInterval(() => {
  250. if(_this.connectEnd) {
  251. clearInterval(_interval);
  252. resolve(_this.isConnectSuccessQt);
  253. }
  254. }, 50)
  255. } else {
  256. resolve(_this.isConnectSuccessQt);
  257. }
  258. })
  259. }
  260. /**
  261. * @description 检测登录客户端状态
  262. */
  263. detectLoginClient() {
  264. let _this = this;
  265. return new Promise((resolve, reject) => {
  266. if(!this.loginEnd) { // 连接中
  267. let _interval = setInterval(() => {
  268. if(_this.loginEnd) {
  269. clearInterval(_interval);
  270. resolve(_this.isLoginSuccess);
  271. }
  272. }, 50)
  273. } else {
  274. resolve(_this.isLoginSuccess);
  275. }
  276. })
  277. }
  278. /**
  279. * @description 登录客户端
  280. * @params {Object} config 登录相关配置
  281. */
  282. login(config: Object) {
  283. this.loginEnd = false;
  284. Object.assign(this.config, config);
  285. // 未连接客户端
  286. if(!this.isOpen()) {
  287. // 连接失败
  288. return false
  289. } else {
  290. // 若已登录,先注销
  291. if (this.isLoginSuccess) {
  292. this.logout();
  293. }
  294. // 登陆客户端
  295. this.loginClient();
  296. // 心跳保活
  297. this._heartbeat();
  298. }
  299. }
  300. /**
  301. * @description 连接客户端完成后登陆
  302. */
  303. // loginAfterConnectEnd() {
  304. // // 未连接客户端
  305. // if(!this.isOpen()) {
  306. // // 连接失败
  307. // return false
  308. // } else {
  309. // // 若已登录,先注销
  310. // if (this.isLoginSuccess) {
  311. // this.logout();
  312. // }
  313. // // 登陆客户端
  314. // this.loginClient();
  315. // // 心跳保活
  316. // this._heartbeat();
  317. // }
  318. // }
  319. /**
  320. * @description 登出客户端
  321. */
  322. logout() {
  323. // 退出客户端
  324. this.logoutClient();
  325. this.isLoginSuccess = false;
  326. if (typeof this.callback.loginResult === 'function') {
  327. this.callback.loginResult.call(this, this.isLoginSuccess);
  328. }
  329. }
  330. /**
  331. * @description 添加websocket/window监听事件
  332. */
  333. addEvents() {
  334. const webSocket = this.webSocket;
  335. webSocket.addEventListener('open', () => this.onOpen());
  336. webSocket.addEventListener('message', (e: any) => this.onMessage(e));
  337. webSocket.addEventListener('error', () => this.onError());
  338. window.addEventListener('resize', () => this.reLocatedPosition());
  339. window.addEventListener('scroll', () => this.reLocatedPosition());
  340. }
  341. /**
  342. * @description 移除websocket/window监听事件
  343. */
  344. removeEvents() {
  345. const webSocket = this.webSocket;
  346. webSocket.removeEventListener('open', this.onOpen);
  347. webSocket.removeEventListener('message', this.onMessage);
  348. webSocket.removeEventListener('error', this.onError);
  349. webSocket.removeEventListener('resize', this.reLocatedPosition);
  350. webSocket.removeEventListener('scroll', this.reLocatedPosition);
  351. }
  352. /**
  353. * @description 连接客户端成功事件
  354. */
  355. onOpen() {
  356. this.isConnectSuccessQt = true;
  357. this.connectEnd = true;
  358. if (typeof this.callback.connectResult === 'function') {
  359. this.callback.connectResult.call(this, this.isConnectSuccessQt);
  360. }
  361. }
  362. /**
  363. * @description 接收客户端消息
  364. * @params {Object} event 接收客户端的消息数据
  365. */
  366. onMessage(event: { data: string; }) {
  367. try {
  368. const data = JSON.parse(event.data);
  369. const { method } = data;
  370. const callback = this.listerns.get(method);
  371. if (method === 'loginState') {
  372. this.loginFailCount ++;
  373. if(this.loginFailCount < this.reLoginCount + 1) { // 登录未到最大次数
  374. if(data.params.loginResult === 0) { // 登录成功
  375. this.isLoginSuccess = data.params.loginResult === 0;
  376. if (typeof this.callback.loginResult === 'function') {
  377. this.callback.loginResult.call(this, this.isLoginSuccess);
  378. }
  379. } else { // 登录失败
  380. this.loginClient();
  381. }
  382. } else { // 登录达到最大次数
  383. this.loginEnd = true;
  384. this.isLoginSuccess = data.params.loginResult === 0;
  385. if (typeof this.callback.loginResult === 'function') {
  386. this.callback.loginResult.call(this, this.isLoginSuccess);
  387. }
  388. }
  389. }
  390. if (callback) {
  391. if (method === 'loginState') {
  392. callback(this.isLoginSuccess);
  393. } else if (method === 'createCtrlResult') {
  394. callback(data.params.array);
  395. } else {
  396. callback(data);
  397. }
  398. }
  399. } catch (e) {
  400. // console.error('error', e);
  401. }
  402. }
  403. /**
  404. * @description 客户端发生错误事件
  405. */
  406. onError() {
  407. this.isConnectSuccessQt = false;
  408. clearTimeout(this.heartbeatTimer);
  409. this.connectFailCount ++;
  410. if(this.connectFailCount === this.reConnectCount + 1) {
  411. this.connectEnd = true;
  412. if (typeof this.callback.connectResult === 'function') {
  413. this.callback.connectResult.call(this, this.isConnectSuccessQt);
  414. }
  415. }
  416. }
  417. /**
  418. * @description 判断是否成功连接客户端
  419. */
  420. isOpen() {
  421. if (!this.webSocket) return false;
  422. return this.webSocket.readyState === 1;
  423. }
  424. /**
  425. * @description 心跳事件
  426. */
  427. _heartbeat() {
  428. this.heartbeat();
  429. clearTimeout(this.heartbeatTimer);
  430. this.heartbeatTimer = setTimeout(() => {
  431. this._heartbeat();
  432. }, 10000);
  433. }
  434. /**
  435. * @description 获取当前浏览器缩放和滚动条信息
  436. */
  437. getScrollInfo() {
  438. let ratio = detectZoom();
  439. let scrollX = window.pageXOffset;
  440. let scrollY = window.pageYOffset;
  441. var hasscrollbary = hasScrollbarY();
  442. var hasscrollbarx = hasScrollbarX();
  443. var scrollbarWidth = getScrollbarWidth();
  444. let scrollXH = hasscrollbarx ? scrollbarWidth : 0;
  445. let scrollYW = hasscrollbary ? scrollbarWidth : 0;
  446. return { ratio, scrollX, scrollY, scrollXH, scrollYW };
  447. }
  448. }
  449. function promisify(func: Function) {
  450. return new Promise((resolve, reject) => {
  451. func('1', (data) => resolve(data))
  452. })
  453. }
  454. /**
  455. * @description 判断对象是否含有某属性
  456. * @params {Object} obj 对象
  457. * @params {String} key 属性key
  458. */
  459. function hasKey<O>(obj: O, key: keyof any): key is keyof O {
  460. return key in obj
  461. }
  462. /**
  463. * @description 与客户端通讯功能注册
  464. * @params {Array} plugins 功能列表
  465. */
  466. function usePlugin(plugins: any[]) {
  467. plugins.forEach((plugin) => {
  468. Object.getOwnPropertyNames(plugin).forEach(prop => {
  469. Ws.prototype[prop] = plugin[prop];
  470. });
  471. });
  472. }
  473. function detectZoom() {
  474. var ratio = 0,
  475. screen = window.screen,
  476. ua = navigator.userAgent.toLowerCase();
  477. if (window.devicePixelRatio !== undefined) {
  478. ratio = window.devicePixelRatio;
  479. } else if (~ua.indexOf('msie')) {
  480. if (screen['deviceXDPI'] && screen['logicalXDPI']) {
  481. ratio = screen['deviceXDPI'] / screen['logicalXDPI'];
  482. }
  483. } else if (window.outerWidth !== undefined && window.innerWidth !== undefined) {
  484. ratio = window.outerWidth / window.innerWidth;
  485. }
  486. if (ratio) {
  487. ratio = Math.round(ratio * 100);
  488. }
  489. return ratio;
  490. }
  491. function hasScrollbarY() {
  492. return (
  493. document.body.scrollHeight >
  494. (window.innerHeight || document.documentElement.clientHeight)
  495. );
  496. }
  497. function hasScrollbarX() {
  498. return (
  499. document.body.scrollWidth >
  500. (window.innerWidth || document.documentElement.clientWidth)
  501. );
  502. }
  503. function getScrollbarWidth() {
  504. var scrollDiv = document.createElement('div');
  505. scrollDiv.style.cssText =
  506. 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;';
  507. document.body.appendChild(scrollDiv);
  508. var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
  509. document.body.removeChild(scrollDiv);
  510. return scrollbarWidth;
  511. }