├── .mdlrc ├── translations ├── en.json ├── de.json ├── id.json └── es.json ├── .travis.yml ├── .editorconfig ├── .codeclimate.yml ├── .eslintrc ├── package.json ├── LICENSE ├── scripts └── preinstall.js ├── MMM-Mobile.js ├── README.md ├── configuration.js └── node_helper.js /.mdlrc: -------------------------------------------------------------------------------- 1 | all 2 | rules "~MD013", "~MD026", "~MD033" 3 | -------------------------------------------------------------------------------- /translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "SCAN_QR_CODE": "Scan this QR-Code with the MagicMirror Mobile App!" 3 | } -------------------------------------------------------------------------------- /translations/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "SCAN_QR_CODE": "Scanne diesen QR-Code mit der MagicMirror Mobile App!" 3 | } -------------------------------------------------------------------------------- /translations/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "SCAN_QR_CODE": "Pindai QR-Code ini dengan Aplikasi MagicMirror Mobile" 3 | } -------------------------------------------------------------------------------- /translations/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "SCAN_QR_CODE": "Escanea este código QR con tu aplicación móvil MagicMirror!" 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | - "7" 5 | - "6" 6 | - "5" 7 | script: 8 | - npm run lint 9 | cache: 10 | directories: 11 | - node_modules 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Unix-style newlines with a newline ending every file 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [{*.json, *.yml}] 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | duplication: 3 | enabled: true 4 | config: 5 | languages: 6 | - javascript 7 | eslint: 8 | enabled: true 9 | channel: "eslint-3" 10 | checks: 11 | import/no-unresolved: 12 | enabled: false 13 | fixme: 14 | enabled: true 15 | markdownlint: 16 | enabled: true 17 | ratings: 18 | paths: 19 | - "**.js" 20 | - "**.md" 21 | exclude_paths: [ 22 | "node_modules/**/*" 23 | ] 24 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "rules": { 4 | "comma-dangle": 0, 5 | "indent": [2, 4], 6 | "max-len": [2, 120, { "ignoreStrings": true }], 7 | "radix": [2, "as-needed"], 8 | "no-console": 0 9 | }, 10 | "settings": { 11 | "import/core-modules": [ 12 | "node_helper", 13 | "../../config/config.js", 14 | "../default/defaultmodules.js", 15 | "configuration.js", 16 | "../../../config/config.js" 17 | ] 18 | }, 19 | "env": { 20 | "browser": true, 21 | "node": true, 22 | "es6": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mmm-mobile", 3 | "version": "1.0.0", 4 | "description": "middleware to configurate your MagicMirror via a mobile application", 5 | "scripts": { 6 | "preinstall": "node scripts/preinstall.js", 7 | "lint": "./node_modules/.bin/eslint ." 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/fewieden/MMM-Mobile.git" 12 | }, 13 | "keywords": [ 14 | "MagicMirror", 15 | "mobile application" 16 | ], 17 | "author": "fewieden", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/fewieden/MMM-Mobile/issues" 21 | }, 22 | "homepage": "https://github.com/fewieden/MMM-Mobile#readme", 23 | "devDependencies": { 24 | "eslint": "^3.14.1", 25 | "eslint-config-airbnb-base": "^11.0.1", 26 | "eslint-plugin-import": "^2.2.0" 27 | }, 28 | "dependencies": { 29 | "async": "^2.1.4", 30 | "moment": "^2.17.1", 31 | "qr-image": "^3.1.0", 32 | "simple-git": "^1.65.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 fewieden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/preinstall.js: -------------------------------------------------------------------------------- 1 | /* Magic Mirror 2 | * Module: MMM-Mobile 3 | * 4 | * By fewieden https://github.com/fewieden/MMM-Mobile 5 | * 6 | * MIT Licensed. 7 | */ 8 | 9 | /* eslint-env node */ 10 | 11 | const fs = require('fs'); 12 | 13 | if (fs.existsSync('../../config/config.js')) { 14 | const backup = fs.readFileSync('../../config/config.js', 'utf8'); 15 | console.log('Creating Backup for config.js!'); 16 | fs.writeFileSync('../../config/config.mobile_backup.js', backup, 'utf8'); 17 | 18 | // eslint-disable-next-line global-require 19 | const config = require('../../../config/config.js'); 20 | 21 | config.modules.sort((a, b) => { 22 | if (!Object.prototype.hasOwnProperty.call(a, 'position')) { 23 | return -1; 24 | } else if (!Object.prototype.hasOwnProperty.call(b, 'position')) { 25 | return 1; 26 | } else if (a.position < b.position) { 27 | return -1; 28 | } else if (a.position > b.position) { 29 | return 1; 30 | } 31 | return 0; 32 | }); 33 | 34 | const file = `var config = ${JSON.stringify(config, null, '\t')};\nif(typeof module !== 'undefined'){module.exports = config;}`; 35 | 36 | console.log('Saving updated config.js!'); 37 | fs.writeFileSync('../../config/config.js', file, 'utf8'); 38 | } else { 39 | console.log('No config file found!'); 40 | } 41 | -------------------------------------------------------------------------------- /MMM-Mobile.js: -------------------------------------------------------------------------------- 1 | /* Magic Mirror 2 | * Module: MMM-Mobile 3 | * 4 | * By fewieden https://github.com/fewieden/MMM-Mobile 5 | * 6 | * MIT Licensed. 7 | */ 8 | 9 | /* global Module Log MM */ 10 | 11 | Module.register('MMM-Mobile', { 12 | 13 | defaults: { 14 | ip: false, 15 | qrSize: 300, 16 | qrTimer: 60 * 1000 17 | }, 18 | 19 | start() { 20 | Log.info(`Starting module: ${this.name}`); 21 | this.sendSocketNotification('CONFIG', this.config); 22 | }, 23 | 24 | getTranslations() { 25 | return { 26 | en: 'translations/en.json', 27 | de: 'translations/de.json', 28 | es: 'translations/es.json', 29 | id: 'translations/id.json' 30 | }; 31 | }, 32 | 33 | notificationReceived(notification, payload, sender) { 34 | if (!sender && notification === 'ALL_MODULES_STARTED') { 35 | this.sendSocketNotification('CREATE_QR'); 36 | } 37 | }, 38 | 39 | socketNotificationReceived(notification) { 40 | if (notification === 'SHOW_QR') { 41 | this.sendNotification('SHOW_ALERT', { 42 | message: this.translate('SCAN_QR_CODE'), 43 | imageUrl: this.file('qr.png'), 44 | imageHeight: `${this.config.qrSize}px`, 45 | timer: this.config.qrTimer 46 | }); 47 | } else if (notification === 'HIDE_QR') { 48 | this.sendNotification('HIDE_ALERT'); 49 | } else if (notification === 'SHOW_MODULES') { 50 | MM.getModules().enumerate((module) => { 51 | module.show(1000); 52 | }); 53 | } else if (notification === 'HIDE_MODULES') { 54 | MM.getModules().enumerate((module) => { 55 | module.hide(1000); 56 | }); 57 | } 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MMM-Mobile [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://raw.githubusercontent.com/fewieden/MMM-Mobile/master/LICENSE) [![Build Status](https://travis-ci.org/fewieden/MMM-Mobile.svg?branch=master)](https://travis-ci.org/fewieden/MMM-Mobile) [![Code Climate](https://codeclimate.com/github/fewieden/MMM-Mobile/badges/gpa.svg?style=flat)](https://codeclimate.com/github/fewieden/MMM-Mobile) [![Known Vulnerabilities](https://snyk.io/test/github/fewieden/mmm-mobile/badge.svg)](https://snyk.io/test/github/fewieden/mmm-mobile) 2 | 3 | This is a middleware to configurate your MagicMirror via a mobile application for iOS, Android and Windows Phone. 4 | 5 | ## Dependencies 6 | 7 | * An installation of [MagicMirror2](https://github.com/MichMich/MagicMirror) 8 | * npm 9 | * [socket.io](https://www.npmjs.com/package/socket.io) 10 | * [qr-image](https://www.npmjs.com/package/qr-image) 11 | * [simple-git](https://www.npmjs.com/package/simple-git) 12 | * [async](https://www.npmjs.com/package/async) 13 | * [moment](https://www.npmjs.com/package/moment) 14 | 15 | ## Hostname 16 | 17 | Before using this module check if your network can resolve hostnames by accessing your mirror in your browser on another computer. 18 | If not you have to use the config option `ip`. 19 | 20 | ## Installation 21 | 22 | 1. Clone this repo into `~/MagicMirror/modules` directory. 23 | 1. Configure your `~/MagicMirror/config/config.js`: 24 | 25 | ``` 26 | { 27 | module: "alert" 28 | }, 29 | { 30 | module: "MMM-Mobile" 31 | } 32 | ``` 33 | 34 | 1. Run command `npm install` in `~/MagicMirror/modules/MMM-Mobile` directory. 35 | 36 | It is important to also have the alert module in your config, otherwise you will not see the qr-code. 37 | 38 | ## Config Options 39 | 40 | | **Option** | **Default** | **Description** | 41 | | --- | --- | --- | 42 | | `ip` | `false` | Enter the ip of your MagicMirror. Default uses the hostname. | 43 | | `qrSize` | `300` | Size of the qr-code in pixel | 44 | | `qrTimer` | `60 * 1000 (1 min)` | time to display qr-code. | 45 | -------------------------------------------------------------------------------- /configuration.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | config: { 3 | values: { 4 | Object: { 5 | values: { 6 | titleReplace: { 7 | description: 'An object of textual replacements applied to the tile of the event. This allow to remove or replace certains words in the title.', 8 | values: { 9 | Object: { 10 | values: { 11 | '*': { 12 | values: { 13 | String: '*' 14 | } 15 | } 16 | } 17 | } 18 | }, 19 | default: { 'De verjaardag van ': '', '\'s birthday': '' } 20 | }, 21 | calendars: { 22 | description: 'The list of calendars.', 23 | values: { 24 | Array: { 25 | values: { 26 | Object: { 27 | values: { 28 | url: { 29 | values: { 30 | String: '*' 31 | } 32 | }, 33 | symbol: { 34 | values: { 35 | String: '*' 36 | } 37 | }, 38 | repeatingCountTitle: { 39 | values: { 40 | String: '*' 41 | } 42 | }, 43 | user: { 44 | values: { 45 | String: '*' 46 | } 47 | }, 48 | pass: { 49 | values: { 50 | String: '*' 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | }, 58 | default: [ 59 | { 60 | url: 'http://www.calendarlabs.com/templates/ical/US-Holidays.ics', 61 | symbol: 'calendar' 62 | } 63 | ] 64 | }, 65 | maximumEntries: { 66 | description: 'The maximum number of events shown.', 67 | values: { 68 | Integer: { 69 | min: 0, 70 | max: 100 71 | } 72 | }, 73 | default: 10 74 | }, 75 | maximumNumberOfDays: { 76 | description: 'The maximum number of days in the future.', 77 | values: { 78 | Integer: '*' 79 | }, 80 | default: 365 81 | }, 82 | displaySymbol: { 83 | description: 'Display a symbol in front of an entry.', 84 | values: { 85 | Boolean: [ 86 | true, 87 | false 88 | ] 89 | }, 90 | default: true 91 | }, 92 | defaultSymbol: { 93 | description: 'The default symbol.', 94 | link: { 95 | name: 'See Font Awesome website.', 96 | url: 'http://fontawesome.io/icons/' 97 | }, 98 | values: { 99 | String: '*' 100 | }, 101 | default: 'calendar' 102 | }, 103 | maxTitleLength: { 104 | description: 'The maximum title length.', 105 | values: { 106 | Integer: { 107 | min: 10, 108 | max: 50 109 | } 110 | }, 111 | default: 25 112 | }, 113 | fetchInterval: { 114 | description: 'How often does the content needs to be fetched? (Milliseconds)', 115 | values: { 116 | Integer: { 117 | min: 1000, 118 | max: 86400000 119 | } 120 | }, 121 | default: 300000 122 | }, 123 | animationSpeed: { 124 | description: 'Speed of the update animation. (Milliseconds)', 125 | values: { 126 | Integer: { 127 | min: 0, 128 | max: 5000 129 | } 130 | }, 131 | default: 2000 132 | }, 133 | fade: { 134 | description: 'Fade the future events to black. (Gradient)', 135 | values: { 136 | Boolean: [ 137 | true, 138 | false 139 | ] 140 | }, 141 | default: true 142 | }, 143 | fadePoint: { 144 | description: 'Where to start fade?', 145 | values: { 146 | Float: { 147 | min: 0.1, 148 | max: 1.0 149 | } 150 | }, 151 | default: 0.25 152 | }, 153 | displayRepeatingCountTitle: { 154 | description: 'Show count title for yearly repeating events (e.g. \'X. Birthday\', \'X. Anniversary\').', 155 | values: { 156 | Boolean: [ 157 | true, 158 | false 159 | ] 160 | }, 161 | default: false 162 | }, 163 | dateFormat: { 164 | description: 'Format to use for the date of events (when using absolute dates).', 165 | link: { 166 | name: 'See Moment.js formats', 167 | url: 'http://momentjs.com/docs/#/parsing/string-format/' 168 | }, 169 | values: { 170 | String: '*' 171 | }, 172 | default: 'MMM Do' 173 | }, 174 | timeFormat: { 175 | description: 'Display event times as absolute dates, or relative time.', 176 | values: { 177 | String: [ 178 | 'absolute', 179 | 'relative' 180 | ] 181 | }, 182 | default: 'relative' 183 | }, 184 | getRelative: { 185 | description: 'How much time (in hours) should be left until calendar events start getting relative?', 186 | values: { 187 | Integer: { min: 0, max: 48 } 188 | }, 189 | default: 6 190 | }, 191 | urgency: { 192 | description: 'When using a timeFormat of absolute, the urgency setting allows you to display events within a specific time frame as relative. This allows events within a certain time frame to be displayed as relative (in xx days).', 193 | values: { 194 | Integer: '*' 195 | }, 196 | default: 7 197 | }, 198 | broadcastEvents: { 199 | description: 'Broadcast all the events to all other modules.', 200 | values: { 201 | Boolean: [ 202 | true, 203 | false 204 | ] 205 | }, 206 | default: true 207 | }, 208 | hidePrivate: { 209 | description: 'Hides private calendar events.', 210 | values: { 211 | Boolean: [ 212 | true, 213 | false 214 | ] 215 | }, 216 | default: false 217 | } 218 | } 219 | } 220 | } 221 | } 222 | }; 223 | 224 | module.exports = config; 225 | -------------------------------------------------------------------------------- /node_helper.js: -------------------------------------------------------------------------------- 1 | /* Magic Mirror 2 | * Module: MMM-Mobile 3 | * 4 | * By fewieden https://github.com/fewieden/MMM-Mobile 5 | * 6 | * MIT Licensed. 7 | */ 8 | 9 | /* eslint-env node */ 10 | 11 | const NodeHelper = require('node_helper'); 12 | const fs = require('fs'); 13 | const crypto = require('crypto'); 14 | const os = require('os'); 15 | const qrcode = require('qr-image'); 16 | const Git = require('simple-git'); 17 | const async = require('async'); 18 | const config = require('../../config/config.js'); 19 | const moment = require('moment'); 20 | const exec = require('child_process').exec; 21 | 22 | const prefix = 'var config = '; 23 | const suffix = ';\nif (typeof module !== \'undefined\'){module.exports = config;}'; 24 | 25 | module.exports = NodeHelper.create({ 26 | 27 | mobile: { 28 | config: null, 29 | modules: [], 30 | user: null 31 | }, 32 | 33 | start() { 34 | console.log(`Starting module helper: ${this.name}`); 35 | if (fs.existsSync('modules/MMM-Mobile/mobile.json')) { 36 | this.mobile = JSON.parse(fs.readFileSync('modules/MMM-Mobile/mobile.json', 'utf8')); 37 | } 38 | 39 | this.appSocket(); 40 | this.getModules(); 41 | setInterval(() => { 42 | this.getModules(); 43 | }, 15 * 60 * 1000); 44 | }, 45 | 46 | socketNotificationReceived(notification, payload) { 47 | if (notification === 'CONFIG') { 48 | this.mobile.config = payload; 49 | if (!Object.prototype.hasOwnProperty.call(this.mobile, 'user') || this.mobile.user == null) { 50 | this.generateSecret(); 51 | } else { 52 | this.sendSocketNotification('SHOW_QR'); 53 | } 54 | } 55 | }, 56 | 57 | generateSecret() { 58 | const secret = crypto.randomBytes(128).toString('hex'); 59 | const code = qrcode.image(JSON.stringify({ 60 | port: config.port, 61 | host: this.mobile.config.ip ? this.mobile.config.ip : os.hostname(), 62 | token: secret 63 | }), { type: 'png' }); 64 | code.pipe(fs.createWriteStream('modules/MMM-Mobile/qr.png')); 65 | this.mobile.user = crypto.createHash('sha256').update(secret).digest('base64'); 66 | fs.writeFile('modules/MMM-Mobile/mobile.json', JSON.stringify(this.mobile, null, '\t'), 'utf8', (err) => { 67 | if (err) { 68 | console.log(`${this.name}: Save settings failed!`); 69 | return; 70 | } 71 | this.sendSocketNotification('SHOW_QR'); 72 | }); 73 | }, 74 | 75 | getModules() { 76 | const candidates = fs.readdirSync('modules'); 77 | const ignore = ['node_modules', 'default']; 78 | const modules = []; 79 | 80 | // eslint-disable-next-line global-require 81 | const defaultmodules = require('../default/defaultmodules.js'); 82 | 83 | // eslint-disable-next-line global-require 84 | const calendarConfig = require('../MMM-Mobile/configuration.js'); 85 | 86 | for (let i = 0; i < defaultmodules.length; i += 1) { 87 | const module = { 88 | name: defaultmodules[i], 89 | github_name: defaultmodules[i], 90 | github_user: 'MagicMirror', 91 | installed: true, 92 | image: '' 93 | }; 94 | if (module.github_name === 'calendar') { 95 | module.config = calendarConfig; 96 | } 97 | modules.push(module); 98 | } 99 | 100 | async.each(candidates, (candidate, callback) => { 101 | if (ignore.indexOf(candidate) === -1 && fs.lstatSync(`modules/${candidate}`).isDirectory()) { 102 | const module = { 103 | name: candidate.replace(/^MMM-/i, '').replace(/^MM-/i, ''), 104 | installed: true, 105 | image: '' 106 | }; 107 | const git = Git(`modules/${candidate}`); 108 | git.getRemotes(true, (error, result) => { 109 | if (!error) { 110 | for (let i = 0; i < result.length; i += 1) { 111 | if (result[i].name === 'origin') { 112 | const link = result[i].refs.fetch.split('/'); 113 | module.github_user = link[link.length - 2]; 114 | module.github_name = candidate; 115 | module.github_url = `https://github.com/${module.github_user}/${module.github_name}`; 116 | git.fetch().status((err, res) => { 117 | if (!err) { 118 | module.status = { 119 | ahead: res.ahead, 120 | behind: res.behind, 121 | branch: res.current 122 | }; 123 | modules.push(module); 124 | } 125 | callback(); 126 | }); 127 | break; 128 | } 129 | } 130 | } else { 131 | callback(); 132 | } 133 | }); 134 | } else { 135 | callback(); 136 | } 137 | }, (error) => { 138 | if (error) { 139 | console.log(error); 140 | } else { 141 | modules.sort((a, b) => { 142 | const name = a.name.toLowerCase(); 143 | const name2 = b.name.toLowerCase(); 144 | if (name < name2) { 145 | return -1; 146 | } else if (name > name2) { 147 | return 1; 148 | } 149 | return 0; 150 | }); 151 | this.mobile.modules = modules; 152 | fs.writeFile('modules/MMM-Mobile/mobile.json', JSON.stringify(this.mobile, null, '\t'), 'utf8', (err) => { 153 | if (err) { 154 | console.log(`${this.name}: Save modules failed!`); 155 | return; 156 | } 157 | console.log(`${this.name}: Saved modules!`); 158 | }); 159 | } 160 | }); 161 | }, 162 | 163 | appSocket() { 164 | const namespace = `${this.name}/app`; 165 | 166 | this.io.of(namespace).use((socket, next) => { 167 | const hash = crypto.createHash('sha256').update(socket.handshake.query.token).digest('base64'); 168 | if (this.mobile.user && this.mobile.user === hash) { 169 | console.log(`${this.name}: Access granted!`); 170 | next(); 171 | } else { 172 | console.log(`${this.name}: Authentication failed!`); 173 | next(new Error('Authentication failed!')); 174 | } 175 | }); 176 | 177 | this.io.of(namespace).on('connection', (socket) => { 178 | console.log(`${this.name}: App connected!`); 179 | 180 | socket.on('CONFIG', () => { 181 | console.log(`${this.name}: Config requested!`); 182 | socket.emit('CONFIG', config); 183 | }); 184 | 185 | socket.on('INSTALLATIONS', () => { 186 | console.log(`${this.name}: Modules requested!`); 187 | socket.emit('INSTALLATIONS', this.mobile.modules); 188 | }); 189 | 190 | socket.on('INSTALL_MODULE', (data) => { 191 | this.installModule(socket, data); 192 | }); 193 | 194 | socket.on('UPDATE_MODULE', (data) => { 195 | this.updateModule(socket, data); 196 | }); 197 | 198 | socket.on('INSTALL_MODULE_DEPENDENCIES', (data) => { 199 | this.installModuleDependencies(socket, data); 200 | }); 201 | 202 | socket.on('SYNC', (data) => { 203 | this.sync(socket, data); 204 | }); 205 | 206 | socket.on('RESTART_MIRROR', () => { 207 | socket.emit('RESTART_MIRROR', { status: 'WILL_BE_RESTARTED' }); 208 | exec('pm2 restart mm', (error) => { 209 | if (error) { 210 | console.log(`${this.name}: Restarting mirror failed!`); 211 | } 212 | }); 213 | }); 214 | 215 | socket.on('SHOW_MODULES', () => { 216 | console.log(`${this.name}: Showing modules!`); 217 | this.sendSocketNotification('SHOW_MODULES'); 218 | }); 219 | 220 | socket.on('HIDE_MODULES', () => { 221 | console.log(`${this.name}: Hiding modules!`); 222 | this.sendSocketNotification('HIDE_MODULES'); 223 | }); 224 | }); 225 | }, 226 | 227 | installModule(socket, data) { 228 | Git('modules').clone(data.url, data.name, (error) => { 229 | this.requestResponse(socket, 'INSTALL_MODULE', error); 230 | }); 231 | }, 232 | 233 | updateModule(socket, data) { 234 | Git(`modules/${data.name}`).pull((error) => { 235 | this.requestResponse(socket, 'UPDATE_MODULE', error); 236 | }); 237 | }, 238 | 239 | requestResponse(socket, msg, error) { 240 | if (error) { 241 | console.log(`${this.name}: ${msg} failed!`); 242 | socket.emit(msg, { error }); 243 | } else { 244 | this.getModules(); 245 | console.log(`${this.name}: ${msg} successfully!`); 246 | socket.emit(msg, { status: 'success' }); 247 | } 248 | }, 249 | 250 | installModuleDependencies(socket, data) { 251 | if (fs.existsSync(`modules/${data.name}/package.json`)) { 252 | // eslint-disable-next-line global-require, import/no-dynamic-require 253 | const pack = require(`../${data.name}/package.json`); 254 | exec('npm install', { cwd: `modules/${data.name}` }, (error) => { 255 | if (error) { 256 | this.installModuleDependenciesFail(socket, error); 257 | return; 258 | } 259 | if (Object.prototype.hasOwnProperty.call(pack, 'scripts') && 260 | Object.prototype.hasOwnProperty.call(pack.scripts, 'module_dependencies')) { 261 | exec('npm run module_dependencies', { cwd: `modules/${data.name}` }, (err) => { 262 | if (err) { 263 | this.installModuleDependenciesFail(socket, err); 264 | } else { 265 | this.installModuleDependenciesSuccess(socket); 266 | } 267 | }); 268 | } else { 269 | this.installModuleDependenciesSuccess(socket); 270 | } 271 | }); 272 | } else { 273 | this.installModuleDependenciesFail(socket, 'NO_DEPENDENCIES_DEFINED'); 274 | } 275 | }, 276 | 277 | installModuleDependenciesSuccess(socket) { 278 | console.log(`${this.name}: Installed module dependencies successfully!`); 279 | socket.emit('INSTALL_MODULE_DEPENDENCIES', { status: 'success' }); 280 | }, 281 | 282 | installModuleDependenciesFail(socket, error) { 283 | console.log(`${this.name}: Install module dependencies failed!`); 284 | socket.emit('INSTALL_MODULE_DEPENDENCIES', { error }); 285 | }, 286 | 287 | sync(socket, data) { 288 | fs.rename('config/config.js', `config/config.js.${moment().format()}.backup`, (err) => { 289 | if (err) { 290 | this.syncFailed(socket); 291 | return; 292 | } 293 | fs.writeFile('config/config.js', prefix + JSON.stringify(data, null, '\t') + suffix, 'utf8', (error) => { 294 | if (error) { 295 | this.syncFailed(socket); 296 | } else { 297 | console.log(`${this.name}: Sync requested!`); 298 | socket.emit('SYNC', { status: 'SUCCESSFULLY_SYNCED' }); 299 | } 300 | }); 301 | }); 302 | }, 303 | 304 | syncFailed(socket) { 305 | console.log(`${this.name}: Sync failed!`); 306 | socket.emit('SYNC', { status: 'SYNC_FAILED' }); 307 | } 308 | }); 309 | --------------------------------------------------------------------------------