📜 ⬆️ ⬇️

Own VPN client on JavaScript. 5 part - Electron component Vpn

PS Each part is a part, by itself it does not make sense to get the necessary context and not to experience cognitive dissonance from the lack of so necessary blocks of text, start reading from part 1

Vpn - Electron component , the main control element of the application.

Electron component - by this term I mean just the organization of the Electron code , which I talked about in 1 part .

Folder structure
')
vpn │ │ index.js │ ├───client //      │ index.html │ paper-plane.svg │ script.js │ style.css │ TweenMax.min.js │ └───icon blue.ico orange.ico red.ico yellow.ico 

index.js - The file in which the Electron component is created.

Contents of the index.js file.

Code
 const { BrowserWindow, Tray, ipcMain } = require('electron') module.exports = class VPN { constructor() { //   this.root = new BrowserWindow({ title: `JS.VPN-Client`, frame: false, //   transparent: true, //   alwaysOnTop: true, //     resizable: false, //   center: true, show: false, //      acceptFirstMouse: true, //         width: 116, height: 116, fullscreenable: false }) //   this.root.loadURL(`${__dirname}/client/index.html`) this.tray = {} //    this.isReconnect = false //    this.reconnect_stack = [] //    this.status = false //        this.cb_connect = () => {} this.cb_disconnect = () => {} } ready() { //      return new Promise(res => { this.root.once('ready-to-show', res) }) } showTray() { //  Tray (.  Electron)   Title this.tray = new Tray(`${__dirname}/icon/red.ico`) this.tray.setToolTip('') } show() { //   this.root.show() } hide() { //   this.root.hide() } isVisible() { //  boolean -         return this.root.isVisible() } onTrayClick(cb) { //    Tray this.tray.on('click', cb) } onTrayRightClick(cb) { //    Tray this.tray.on('right-click', cb) } center() { //     this.root.center() } onDisconnect(cb) { //    this.cb_disconnect = cb //   VPN_DISCONNECT (  VPN) ipcMain.on('VPN_DISCONNECT', e => { cb() e.returnValue = 'ok' }) } onConnect(cb) { //    this.cb_connect = cb //   VPN_CONNECT (  VPN) ipcMain.on('VPN_CONNECT', (e, data) => { cb() e.returnValue = 'ok' }) } connect() { //   this.cb_connect() } disconnect() { //   this.cb_disconnect() } onContext(cb) { //   VPN_CONTEXT (  ) ipcMain.on('VPN_CONTEXT', (e, data) => { cb(data) e.returnValue = 'ok' }) } stopReconnect() { //       this.reconnect_stack.map(i => clearTimeout(i)) this.reconnect_stack = [] } reconnect(cb, time = 15000) { //      this.stopReconnect() //     this.reconnect_stack.push( //   setTimeout(() => { //   this.isReconnect = true cb(() => { this.disconnect() this.connect() }) //   this.isReconnect = false }, time) ) } setStatus(animation, color) { let statusText = '' if (color == 'blue') { statusText = '' } if (color == 'yellow') { statusText = '' } if (color == 'orange') { statusText = ' ' } //  Title  Tray this.tray.setToolTip(statusText) //   this.tray.setImage(`${__dirname}/icon/${color}.ico`) this.status = color == 'red' ? false : true; //   VPN_STATUS      this.root.webContents.send('VPN_STATUS', JSON.stringify({ animation, color })) } } 


index.html - HTML page window.

Contents of the index.html file.

Code
 <!DOCTYPE html> <html lang='ru'> <head> <meta charset='UTF-8'> </head> <body> <link rel='stylesheet' type='text/css' href='style.css'> <script src='TweenMax.min.js'></script> <script src='script.js'></script> <div class='btn-border'> <div class='btn-body'> <div class='but-plane'></div> </div> </div> <script> const { ipcRenderer, remote } = require('electron') , plane = document.getElementsByClassName('but-plane')[0] , btnBorder = document.getElementsByClassName('btn-border')[0] , btnBody = document.getElementsByClassName('btn-body')[0] //     const win = remote.getCurrentWindow() //     -        localStorage.x && win.setPosition( parseInt(localStorage.x), parseInt(localStorage.y) ) //      setInterval(() => { const [x, y] = win.getPosition() localStorage.setItem('x', x) localStorage.setItem('y', y) }, 3) //     document.body.addEventListener('contextmenu', e => { //       localStorage.setItem('context_x', e.pageX) localStorage.setItem('context_y', e.pageY) //      ipcRenderer.sendSync('VPN_CONTEXT') e.preventDefault() }); //     VPN let start = false plane.addEventListener('click', () => { start = !start if (start) { //      VPN ipcRenderer.sendSync('VPN_CONNECT') } else { //      VPN ipcRenderer.sendSync('VPN_DISCONNECT') } }) //    const statVPN = new VPNstatus(plane, btnBody, btnBorder) //  statVPN.color('red') statVPN.reject() //       ipcRenderer.on('VPN_STATUS', (e, data) => { const { animation, color } = JSON.parse(data) statVPN.color(color) statVPN[animation]() }) </script> </body> </html> 


script.js - All window animation.

In addition to script.js , TweenMax.js is also used for animation.

The contents of the file script.js .

Code
 class VPNstatus { constructor (plane, btnBody, btnBorder) { this.plane = plane this.btnBody = btnBody this.btnBorder = btnBorder //   this.speedAnimation = 0.9 this.colors = { red: { boxShadow: '1px 1px 5px 1px rgba(255, 24, 37, 0.4)', borderBackground: '#e2333d', background: '#f06069' }, blue: { background: '#60a2f0', borderBackground: '#3286e3', boxShadow: '2px 2px 5px 1px rgba(40, 138, 255, 0.4)' }, yellow: { background: '#f5cc5b', borderBackground: '#f1c131', boxShadow: '2px 2px 5px 1px rgba(255, 197, 37, 0.4)' }, orange: { background: '#f09c60', borderBackground: '#e37a32', boxShadow: '2px 2px 5px 1px rgba(255, 127, 35, 0.4)' } } this.step = { start: { left: '-40px', top: '73px' }, center: { top: '19px', left: '13px' }, end: { top: '-45px', left: '71px' } } this.status = 0 } color (value) { btnBody.style.background = this.colors[value].background btnBorder.style.background = this.colors[value].borderBackground btnBorder.style.boxShadow = this.colors[value].boxShadow } waiting () { statVPN.center_to_end(() => { this.start_to_end(() => { if (this.status == 1) { TweenMax.killAll() this.plane.style.left = this.step.start.left; this.plane.style.top = this.step.start.top; statVPN.start_to_center() this.status = 2 } }) }) } resolve () { this.status = 1 } reject () { this.status = 2 statVPN.center_to_end(() => { statVPN.start_to_center() }) } start_to_end (callback) { this.plane.style.left = this.step.start.left; this.plane.style.top = this.step.start.top; TweenMax.to(this.plane, this.speedAnimation * 2, { top: this.step.end.top, left: this.step.end.left, repeat: -1, ease: Elastic.ease, onRepeat: callback ? callback : () => {} }) } center_to_end (callback) { this.plane.style.left = this.step.center.left; this.plane.style.top = this.step.center.top; TweenMax.to(this.plane, this.speedAnimation, { top: this.step.end.top, left: this.step.end.left, ease: Elastic.ease, onComplete: callback ? callback : () => {} }) } start_to_center (callback) { this.plane.style.left = this.step.start.left; this.plane.style.top = this.step.start.top; TweenMax.to(this.plane, this.speedAnimation, { top: this.step.center.top, left: this.step.center.left, ease: Elastic.ease, onComplete: callback ? callback : () => {} }) } } 


style.css - page styles.

In my application it is very important that the styles globally be disabled , overflow: hidden; without this property, there is a bug with a window when the position of the “quick launch panel” changes, namely, a scroll appears .

image

Contents of the style.css file.

Code
 * { padding: 0; margin: 0; overflow: hidden; -webkit-app-region: drag; } body { width: 100%; height: 100vh; background: rgba(0, 0, 0, 0); } .btn-border { position: absolute; left: 1px; top: 1px; width: 111px; height: 111px; background: #e2333d; border-radius: 100%; box-shadow: 1px 1px 5px 1px rgba(255, 24, 37, 0.4); display: flex; justify-content: center; align-items: center; } .btn-body { border-radius: 100%; overflow: hidden; width: 85px; height: 85px; background: #f06069; cursor: pointer; -webkit-app-region: no-drag; } .but-plane { position: relative; background: url('paper-plane.svg'); width: 60%; height: 60%; top: 71px; left: -38px; cursor: pointer; -webkit-app-region: no-drag; } 


Application

Application usage: / app / Vpn .

API

Vpn component interface. I am sure that after studying the next piece of code, the convenience of the approach I mentioned in part 1 will become obvious. After the component acquired such a shell as a class with its own methods, it became much easier to work with it. If you are still not convinced of this now, be sure to check back later.

 const { app } = require('electron') , VPN = require('./../../app/components/vpn') app.on('ready', async() => { const Vpn = new VPN() //          await Vpn.ready() //   Vpn.show() //  Tray Vpn.showTray() //   Vpn.onConnect(() => { console.log('Connect') //     Vpn.setStatus('waiting', 'yellow') setTimeout(() => { console.log('Connected!') //     Vpn.setStatus('resolve', 'blue') }, 4000) }) //   Vpn.onDisconnect(() => { console.log('Disconnect') //     Vpn.setStatus('reject', 'red') }) Vpn.onContext(() => { console.log('Context menu') }) //     5    //     Vpn.stopReconnect() //   Vpn.reconnect(next => { console.log('Reconnect') next() }, 5000) //     //stopReconnect() //    Tray Vpn.onTrayClick(() => { //     if (!Vpn.status) { Vpn.connect() } else { Vpn.disconnect() } }) //       Tray Vpn.onTrayRightClick(() => { if (Vpn.isVisible()) { Vpn.hide() } else { Vpn.show() } }) }) 

Test

Test version: / app_test / Vpn .

image

Possible statuses and colors for Vpn.setStatus .
ColorsAnimationsDescription
redrejectDisconnect
blueresolveConnection
yellowwaitingExpectation
orange


6 part - Notify component


VPN   JavaScript by JSus

Source: https://habr.com/ru/post/429434/


All Articles