├── .github └── workflows │ └── npmpublish.yml ├── .gitignore ├── README.md ├── config.html ├── config.js ├── device-node.html ├── device-node.js ├── package-lock.json ├── package.json └── run-on-change.py /.github/workflows/npmpublish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: push 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: 12 16 | - name: Set Git Environment and Update package number 17 | run: | 18 | git config --global user.name 'GIT Package Updater' 19 | git config --global user.email 'razorRun@users.noreply.github.com' 20 | npm version patch 21 | git push 22 | - run: npm ci 23 | 24 | publish-npm: 25 | needs: build 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: actions/setup-node@v1 30 | with: 31 | node-version: 12 32 | registry-url: https://registry.npmjs.org/ 33 | - run: npm ci 34 | - run: npm publish 35 | env: 36 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | ### Quick summary - 4 | 5 | This npm module will allow you to link up Smartlife and Tuya devices with node red via a **cloud service without any firmware changes**. 6 | 7 | ### How do I get set up? 8 | 9 | ### Please DO NOT use support email in app store to reach for support. Log issues and support requests on github. 10 | 11 | 1. NOTE: This plugin is only conncted to our US DB, So please choose US (see the list below) on the app when you register. Enroll the devices to SmartLife AIR APP (This is a separate app but you can use all the devices that works with tuya and smart life. If your device is not working or does not show inputs/outputs, try updating the firmware from app-> device -> update firmware) 12 | 13 | - Download SmartLife AIR APP from Smartlife site, [One of the leading home automation companies in New Zealand](https://www.smartlife.nz/smartlife-labs). 14 | 15 | 2. Create an account under https://smartlife-air-plugins.smartlife.nz/ and add your SmartLife air credentials. 16 | 3. Install NodeRed node and add your https://smartlife-air-plugins.smartlife.nz/ Login credentials. 17 | 4. Choose the device, input and output. (Please change a status before you select the device, Backend has to receive at least one update in order to pop up in the list). 18 | 5. If you want to get last status without sending a command, you can do that by sending string "?" as a input 19 | 20 | ### More on Country limitation 21 | 22 | There is one limitation on this plugin due to the server location. This node-red plugin only talks to US DB. That’s mean you will have to **choose a country that belongs to US region when you sign up to the mobile app**. Unfortunately, if you have already created an account you will have to create a new one and enrol devices again. 23 | 24 | **Countries in the US region** 25 | Canada, The United States, Peru, Mexico, Argentina, Brazil, Chile, Colombia, Venezuela, Malaysia, Indonesia, Philippines, New Zealand, Thailand, Japan, Korea, Vietnam, Myanmar(Burma), Guatemala, Bolivia, Ecuador, Paraguay, Suriname, Uruguay, Hongkong SAR China, Macao SAR China, Taiwan China, Puerto Rico, Curacao 26 | 27 | ### Contribution guidelines 28 | 29 | - Writing tests TODO 30 | - Code review 31 | - Other guidelines 32 | 33 | * Author - Roshan Milinda -> [roshan.digital](https://roshan.digital/) 34 | 35 | ### license GPL (GPLv3) 36 | -------------------------------------------------------------------------------- /config.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 29 | 30 | 45 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(RED) { 2 | "use strict"; 3 | var firebase = require("firebase/app"); 4 | 5 | // Add the Firebase 6 | require("firebase/auth"); 7 | require("firebase/database"); 8 | 9 | const firebaseConfig = { 10 | apiKey: "AIzaSyBnbSCjJ9eQtAMl_CHRSJxelF5XG8UlLjU", 11 | authDomain: "smartlife-air.firebaseapp.com", 12 | databaseURL: "https://smartlife-air.firebaseio.com", 13 | projectId: "smartlife-air", 14 | storageBucket: "smartlife-air.appspot.com", 15 | messagingSenderId: "121967033816", 16 | appId: "1:121967033816:web:222bbc68de21111ac0c6ad", 17 | measurementId: "G-YHBTVPG19R" 18 | }; 19 | 20 | function loginNode(n) { 21 | RED.nodes.createNode(this, n); 22 | 23 | // Initialize Firebase 24 | if (!firebase.apps.length) { 25 | firebase.initializeApp(firebaseConfig); 26 | } 27 | if (n.email && n.password) { 28 | this.auth = firebase.auth(); 29 | this.db = firebase.database(); 30 | let retry = true; 31 | const login = ()=>{ 32 | if(retry) 33 | this.auth.signInWithEmailAndPassword(n.email, n.password).catch(error => { 34 | // Handle Errors here. 35 | console.log("Smartlife Air: Auth Error From Config Node") 36 | console.log(error.message); 37 | retry = true; 38 | setTimeout(() => { 39 | login(); 40 | }, 10000); 41 | }); 42 | } 43 | this.auth.onAuthStateChanged(function(user) { 44 | if (user) { 45 | retry = false; 46 | } else { 47 | retry = true; 48 | login(); 49 | } 50 | }); 51 | } 52 | } 53 | RED.nodes.registerType("smartlifeair-login", loginNode); 54 | }; 55 | -------------------------------------------------------------------------------- /device-node.html: -------------------------------------------------------------------------------- 1 | 38 | 39 | 109 | 110 | 123 | 124 | 125 | 126 | 127 | 128 | 307 | -------------------------------------------------------------------------------- /device-node.js: -------------------------------------------------------------------------------- 1 | module.exports = function(RED) { 2 | let lastOutPut = {}; 3 | let auth = false; 4 | function deviceNode(config) { 5 | try { 6 | RED.nodes.createNode(this, config); 7 | const node = this; 8 | const configNode = RED.nodes.getNode(config.account); 9 | 10 | let outputPath = {}; 11 | 12 | node.on("input", msg => { 13 | if (auth) { 14 | inputHandler(msg.payload); 15 | } else { 16 | node.warn("Authentication Pending or error in user credentials. - On User input"); 17 | } 18 | }); 19 | 20 | node.on("close", () => { 21 | if (outputPath) outputPath.off(); 22 | }); 23 | 24 | // Subscribe after 5 seconds 25 | setTimeout(() => { 26 | if (configNode && configNode.auth && configNode.db) { 27 | configNode.auth.onAuthStateChanged(user => { 28 | if (user) { 29 | // User is signed in. 30 | logWarn("User Authenticated as " + user.email); 31 | auth = true; 32 | if (config.selectedOutput) { 33 | if (config.selectedOutput == "root") { 34 | outputPath = configNode.db.ref( 35 | "/devices/" + config.selectedDevice + "/status" 36 | ); 37 | } else { 38 | outputPath = configNode.db.ref( 39 | "/devices/" + 40 | config.selectedDevice + 41 | "/status/" + 42 | config.selectedOutput 43 | ); 44 | } 45 | //subscribe to output path 46 | node.subscriber = outputPath.on("value", data => { 47 | let payload = {}; 48 | if (config.selectedOutput == "root") { 49 | Object.keys(data.val()).forEach(code => { 50 | payload[code] = data.val()[code].value; 51 | }); 52 | } else { 53 | payload = outputConverter( 54 | data.val().value, 55 | config.outputConversion 56 | ); 57 | } 58 | lastOutPut = payload; 59 | node.send({ 60 | payload: payload, 61 | completeData: data.val() 62 | }); 63 | }); 64 | } 65 | } else { 66 | // User is signed out. 67 | node.warn("Authentication Pending or error in user credentials. - On Startup"); 68 | console.log("Smartlife Air: Auth Object") 69 | console.log(JSON.stringify(configNode.auth)) 70 | auth = false; 71 | } 72 | }); 73 | } 74 | }, 5000); 75 | 76 | function logWarn(msg){ 77 | if(config.enabledDebugMsg === "true" || config.enabledDebugMsg === true){ // "WTF: NR keep passing string and Bool cov doesnt work To check later" 78 | node.warn(msg); 79 | } 80 | } 81 | 82 | function inputHandler(inputValue) { 83 | if (inputValue === "?"){ 84 | node.send({ 85 | payload:lastOutPut 86 | }) 87 | } 88 | else { 89 | if (config.selectedDevice && config.selectedInput) { 90 | const inputPath = configNode.db.ref( 91 | "/devices/" + config.selectedDevice + "/commands" 92 | ); 93 | console.log('Smartlife Air: Value Update Started'+ config.selectedDevice +' '+ config.selectedInput +' '+ inputValue); 94 | inputPath.set({ 95 | commandCode: config.selectedInput, 96 | value: inputValue 97 | }).then(function() { 98 | console.log('Smartlife Air: Value Update Finished'+ config.selectedDevice +' '+ config.selectedInput +' '+ inputValue); 99 | }).catch(function(error) { 100 | console.log('Smartlife Air: Cannot connect to the server'); 101 | console.log('Smartlife Air: Value Update Failed '+ config.selectedDevice +' '+ config.selectedInput +' '+ inputValue); 102 | }); 103 | 104 | } else { 105 | logWarn("Please select a device and an input channel"); 106 | } 107 | } 108 | } 109 | } catch (error) { 110 | node.warn(error); 111 | } 112 | } 113 | 114 | RED.nodes.registerType("device-node", deviceNode); 115 | }; 116 | 117 | function outputConverter(input, enable) { 118 | if (!JSON.parse(enable)) { 119 | return input; 120 | } 121 | switch (input) { 122 | case true: 123 | return "ON"; 124 | case false: 125 | return "OFF"; 126 | default: 127 | return input; 128 | } 129 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-smartlifeair", 3 | "version": "1.0.13", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@firebase/analytics": { 8 | "version": "0.2.12", 9 | "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.2.12.tgz", 10 | "integrity": "sha512-y/WwSNUOPJnAdYOxD+NCMwVbhfP/yrQrnGTn1zAAM1R8bBBmWay8HpHki7g5N3qQ+AQ+uMF6tKbp/JnJkini7A==", 11 | "requires": { 12 | "@firebase/analytics-types": "0.2.5", 13 | "@firebase/component": "0.1.4", 14 | "@firebase/installations": "0.4.1", 15 | "@firebase/util": "0.2.39", 16 | "tslib": "1.10.0" 17 | } 18 | }, 19 | "@firebase/analytics-types": { 20 | "version": "0.2.5", 21 | "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.2.5.tgz", 22 | "integrity": "sha512-aa746gTiILMn9TPBJXaYhYqnCL4CQwd4aYTAZseI9RZ/hf117xJTNy9/ZTmG5gl2AqxV0LgtdHYqKAjRlNqPIQ==" 23 | }, 24 | "@firebase/app": { 25 | "version": "0.5.3", 26 | "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.5.3.tgz", 27 | "integrity": "sha512-cQn8eQSJRMpyIRfBi2roIiw0weAorpdJ2Ssn7yDlkMo0ZSE56MgMSlX1mcDupXgNZtG+k3E+IEr+FfzD9SQBGg==", 28 | "requires": { 29 | "@firebase/app-types": "0.5.0", 30 | "@firebase/component": "0.1.4", 31 | "@firebase/logger": "0.1.34", 32 | "@firebase/util": "0.2.39", 33 | "dom-storage": "2.1.0", 34 | "tslib": "1.10.0", 35 | "xmlhttprequest": "1.8.0" 36 | } 37 | }, 38 | "@firebase/app-types": { 39 | "version": "0.5.0", 40 | "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.5.0.tgz", 41 | "integrity": "sha512-8j+vCXTpAkYGcFk86mPZ90V6HMFmn196RIEW9Opi0PN+VrPFC1l/eW0gptM8v7VXaQhECOxws3TN2g+dDaeSYA==" 42 | }, 43 | "@firebase/auth": { 44 | "version": "0.13.4", 45 | "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.13.4.tgz", 46 | "integrity": "sha512-dFDuLMHHmigs9ZH56h+UO78SMuvYkwRcPbEudaemYisGLXnYFa0pUKS2WfvA/9d/14X/VnzG8NGApAw5BylpvA==", 47 | "requires": { 48 | "@firebase/auth-types": "0.9.4" 49 | } 50 | }, 51 | "@firebase/auth-interop-types": { 52 | "version": "0.1.1", 53 | "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.1.tgz", 54 | "integrity": "sha512-rNpCOyCspZvNDoQVQLQQgWAGBMB2ClCWKN1c8cEFgLNFgnMJrjVB+tcL7KW2q2UjKa7l8Mxgwys7szTiEDAcvA==" 55 | }, 56 | "@firebase/auth-types": { 57 | "version": "0.9.4", 58 | "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.9.4.tgz", 59 | "integrity": "sha512-06ZrpYz1GaUfIJs7C3Yf4lARH8+2kzgKfgG/9B3FaGHFYLa5U7rLBGGaca4oiVI12jmhe9CV3+M8e3U2CRCr2w==" 60 | }, 61 | "@firebase/component": { 62 | "version": "0.1.4", 63 | "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.4.tgz", 64 | "integrity": "sha512-k3JZFUyHnSWC/7v+x+pIHLDNJPYA6xd7nqrQASOXH5TXhCR9meg0VsnJb+knD18491iRMKJnQWNSHdqPK9AX5w==", 65 | "requires": { 66 | "@firebase/util": "0.2.39", 67 | "tslib": "1.10.0" 68 | } 69 | }, 70 | "@firebase/database": { 71 | "version": "0.5.20", 72 | "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.5.20.tgz", 73 | "integrity": "sha512-31dNLqMW4nGrGzIDS5hh+1LFzkr/m1Kb+EcftQGC3NaGC3zHwGyG7ijn+Xo7gIWtXukvJvm1cFC7R+eOCPEejw==", 74 | "requires": { 75 | "@firebase/auth-interop-types": "0.1.1", 76 | "@firebase/component": "0.1.4", 77 | "@firebase/database-types": "0.4.10", 78 | "@firebase/logger": "0.1.34", 79 | "@firebase/util": "0.2.39", 80 | "faye-websocket": "0.11.3", 81 | "tslib": "1.10.0" 82 | } 83 | }, 84 | "@firebase/database-types": { 85 | "version": "0.4.10", 86 | "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.4.10.tgz", 87 | "integrity": "sha512-66puLsckt5HASgRN3CfhLn2iuGrgCjfH3u17OL0f5MtEweYLx+yW2QW5d539Wx30xD4B+INEdaRetw6xEa9t7g==", 88 | "requires": { 89 | "@firebase/app-types": "0.5.0" 90 | } 91 | }, 92 | "@firebase/firestore": { 93 | "version": "1.10.0", 94 | "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.10.0.tgz", 95 | "integrity": "sha512-m7RjiEmACg7BHZdAgWKEE5NGbXrc56cQeVqh1ptRNTjPZG0W0Ygtp4hmU5k9FuN9JogIZqBqcXRmRQcq9qOkNw==", 96 | "requires": { 97 | "@firebase/component": "0.1.4", 98 | "@firebase/firestore-types": "1.9.0", 99 | "@firebase/logger": "0.1.34", 100 | "@firebase/util": "0.2.39", 101 | "@firebase/webchannel-wrapper": "0.2.35", 102 | "@grpc/proto-loader": "^0.5.0", 103 | "grpc": "1.24.2", 104 | "tslib": "1.10.0" 105 | } 106 | }, 107 | "@firebase/firestore-types": { 108 | "version": "1.9.0", 109 | "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.9.0.tgz", 110 | "integrity": "sha512-UOtneGMUTLr58P56Y0GT/c4ZyC39vOoRAzgwad4PIsyc7HlKShbHKJpyys/LdlUYIsQdy/2El3Qy1veiBQ+ZJg==" 111 | }, 112 | "@firebase/functions": { 113 | "version": "0.4.31", 114 | "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.4.31.tgz", 115 | "integrity": "sha512-6AiAoABRaFb0M0MMIHDXfbvNVduKNdLAGG+6FtMNuUzFbWiLLKsPgFy0jMkF874z7eIDT4XhZLNAasFVor/alw==", 116 | "requires": { 117 | "@firebase/component": "0.1.4", 118 | "@firebase/functions-types": "0.3.13", 119 | "@firebase/messaging-types": "0.4.1", 120 | "isomorphic-fetch": "2.2.1", 121 | "tslib": "1.10.0" 122 | } 123 | }, 124 | "@firebase/functions-types": { 125 | "version": "0.3.13", 126 | "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.3.13.tgz", 127 | "integrity": "sha512-wD075tCmZo+t/GHs5xMhi3irwjbc6SWnjXAIHjuNhVDKic5gQNkHH5QnxX930WPrPhD0RV9wuSP15fy9T1mvjw==" 128 | }, 129 | "@firebase/installations": { 130 | "version": "0.4.1", 131 | "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.1.tgz", 132 | "integrity": "sha512-rZj3dce54YkKHLJpSvxtf+OWF1/yaQe3jRWt5Fy70XTTYv60RM5DkjbbjL7ItflBMzXZCVUTiBUogFWN4Y1LVg==", 133 | "requires": { 134 | "@firebase/component": "0.1.4", 135 | "@firebase/installations-types": "0.3.0", 136 | "@firebase/util": "0.2.39", 137 | "idb": "3.0.2", 138 | "tslib": "1.10.0" 139 | } 140 | }, 141 | "@firebase/installations-types": { 142 | "version": "0.3.0", 143 | "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.3.0.tgz", 144 | "integrity": "sha512-1W82H1F4WfuWjftMiWLNUTy1w2SD7svn8/U8k6T/CJSnzkET6m+3pPt3Q4FDI6E2zOgU8ZVGWm9IZ4DK84mP/A==" 145 | }, 146 | "@firebase/logger": { 147 | "version": "0.1.34", 148 | "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.1.34.tgz", 149 | "integrity": "sha512-J2h6ylpd1IcuonRM3HBdXThitds6aQSIeoPYRPvApSFy82NhFPKRzJlflAhlQWjJOh59/jyQBGWJNxCL6fp4hw==" 150 | }, 151 | "@firebase/messaging": { 152 | "version": "0.6.3", 153 | "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.6.3.tgz", 154 | "integrity": "sha512-PgkKsJwErLDsCqrcFSaLgFH/oXzMcwBbNN7HyAXTsWxUwhBbG7c5xtGRGd21F82EEmXcFl3VU/Y/kyYuXskrbQ==", 155 | "requires": { 156 | "@firebase/component": "0.1.4", 157 | "@firebase/installations": "0.4.1", 158 | "@firebase/messaging-types": "0.4.1", 159 | "@firebase/util": "0.2.39", 160 | "idb": "3.0.2", 161 | "tslib": "1.10.0" 162 | } 163 | }, 164 | "@firebase/messaging-types": { 165 | "version": "0.4.1", 166 | "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.4.1.tgz", 167 | "integrity": "sha512-z2ki1nIE8TYH9LiXdozEzrPi9Cfckh9/x7HbDfj5KoVFYYvwLndUczstpKm2hvAUD3GuJF0JRUuxMHpJ6pwqzQ==" 168 | }, 169 | "@firebase/performance": { 170 | "version": "0.2.31", 171 | "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.2.31.tgz", 172 | "integrity": "sha512-vg60k0wnMeTRhW8myOiJPn8vuVHlusnhg2YlN0PlH2epvzUPUv9bAeYVC6/XESORxmBmqUzfMsAaD3oCAQ8boQ==", 173 | "requires": { 174 | "@firebase/component": "0.1.4", 175 | "@firebase/installations": "0.4.1", 176 | "@firebase/logger": "0.1.34", 177 | "@firebase/performance-types": "0.0.8", 178 | "@firebase/util": "0.2.39", 179 | "tslib": "1.10.0" 180 | } 181 | }, 182 | "@firebase/performance-types": { 183 | "version": "0.0.8", 184 | "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.8.tgz", 185 | "integrity": "sha512-3RK15Bct9PRdr0YBdbZV2KTi5yrPzscJVEsdnsLHuwXBntqlQaouQqwlCNFn25dtCMY0cgV/yiaFmuo13mbJ8A==" 186 | }, 187 | "@firebase/polyfill": { 188 | "version": "0.3.31", 189 | "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.31.tgz", 190 | "integrity": "sha512-7XItMz50tdba57tCOTCSH8REvHYbrTU7MBOksnNZ3td/J9W/RkCPcLVSSnFWNmn0Jv1aufpUevryX1J4DZ/oiw==", 191 | "requires": { 192 | "core-js": "3.6.2", 193 | "promise-polyfill": "8.1.3", 194 | "whatwg-fetch": "2.0.4" 195 | }, 196 | "dependencies": { 197 | "whatwg-fetch": { 198 | "version": "2.0.4", 199 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", 200 | "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" 201 | } 202 | } 203 | }, 204 | "@firebase/remote-config": { 205 | "version": "0.1.12", 206 | "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.12.tgz", 207 | "integrity": "sha512-L8Qr2waj5NjgWYsjjhvMmSnUaav4LPdrshdz/l/QPBCkNpF5+hIxbeG2f8609D5brcQVnG7uR4lcWfkzl+zsfQ==", 208 | "requires": { 209 | "@firebase/component": "0.1.4", 210 | "@firebase/installations": "0.4.1", 211 | "@firebase/logger": "0.1.34", 212 | "@firebase/remote-config-types": "0.1.5", 213 | "@firebase/util": "0.2.39", 214 | "tslib": "1.10.0" 215 | } 216 | }, 217 | "@firebase/remote-config-types": { 218 | "version": "0.1.5", 219 | "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.5.tgz", 220 | "integrity": "sha512-1JR0XGVN0dNKJlu5sMYh0qL0jC85xNgXfUquUGNHhy9lH3++t1gD91MeiDBgxI73oFQR7PEPeu+CTeDS0g8lWQ==" 221 | }, 222 | "@firebase/storage": { 223 | "version": "0.3.25", 224 | "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.25.tgz", 225 | "integrity": "sha512-0dgfrbPuwFDMOsC3VeENSt4L5bTsLq/5spdDn/Cjj57T0lVRaXipmgD09y8CJ0/SYPfiRoxmMWKCMnVNeSDL/g==", 226 | "requires": { 227 | "@firebase/component": "0.1.4", 228 | "@firebase/storage-types": "0.3.8", 229 | "@firebase/util": "0.2.39", 230 | "tslib": "1.10.0" 231 | } 232 | }, 233 | "@firebase/storage-types": { 234 | "version": "0.3.8", 235 | "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.8.tgz", 236 | "integrity": "sha512-F0ED2WZaGjhjEdOk85c/1ikDQdWM1NiATFuTmRsaGYZyoERiwh/Mr6FnjqnLIeiJZqa6v2hk/aUgKosXjMWH/Q==" 237 | }, 238 | "@firebase/util": { 239 | "version": "0.2.39", 240 | "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.39.tgz", 241 | "integrity": "sha512-hxbQJ9TkFzd6g8/ZcWBjdrxjxS0jYnR1EN3i1ah7i3KtvuxAtwNJ04YRf0QhKhCoitTkJ1Yn3cGb0kFnGtJVRA==", 242 | "requires": { 243 | "tslib": "1.10.0" 244 | } 245 | }, 246 | "@firebase/webchannel-wrapper": { 247 | "version": "0.2.35", 248 | "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.35.tgz", 249 | "integrity": "sha512-7njiGBbFW0HCnuKNEJLcQt9EjfOzG8EJiXlFJwA3XfgiFxPVHmXrcF4d5yold2wfiwCwrXpeNTGZ854oRr6Hcw==" 250 | }, 251 | "@grpc/proto-loader": { 252 | "version": "0.5.3", 253 | "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.3.tgz", 254 | "integrity": "sha512-8qvUtGg77G2ZT2HqdqYoM/OY97gQd/0crSG34xNmZ4ZOsv3aQT/FQV9QfZPazTGna6MIoyUd+u6AxsoZjJ/VMQ==", 255 | "requires": { 256 | "lodash.camelcase": "^4.3.0", 257 | "protobufjs": "^6.8.6" 258 | } 259 | }, 260 | "@protobufjs/aspromise": { 261 | "version": "1.1.2", 262 | "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", 263 | "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" 264 | }, 265 | "@protobufjs/base64": { 266 | "version": "1.1.2", 267 | "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", 268 | "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" 269 | }, 270 | "@protobufjs/codegen": { 271 | "version": "2.0.4", 272 | "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", 273 | "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" 274 | }, 275 | "@protobufjs/eventemitter": { 276 | "version": "1.1.0", 277 | "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", 278 | "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" 279 | }, 280 | "@protobufjs/fetch": { 281 | "version": "1.1.0", 282 | "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", 283 | "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", 284 | "requires": { 285 | "@protobufjs/aspromise": "^1.1.1", 286 | "@protobufjs/inquire": "^1.1.0" 287 | } 288 | }, 289 | "@protobufjs/float": { 290 | "version": "1.0.2", 291 | "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", 292 | "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" 293 | }, 294 | "@protobufjs/inquire": { 295 | "version": "1.1.0", 296 | "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", 297 | "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" 298 | }, 299 | "@protobufjs/path": { 300 | "version": "1.1.2", 301 | "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", 302 | "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" 303 | }, 304 | "@protobufjs/pool": { 305 | "version": "1.1.0", 306 | "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", 307 | "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" 308 | }, 309 | "@protobufjs/utf8": { 310 | "version": "1.1.0", 311 | "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", 312 | "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" 313 | }, 314 | "@types/bytebuffer": { 315 | "version": "5.0.40", 316 | "resolved": "https://registry.npmjs.org/@types/bytebuffer/-/bytebuffer-5.0.40.tgz", 317 | "integrity": "sha512-h48dyzZrPMz25K6Q4+NCwWaxwXany2FhQg/ErOcdZS1ZpsaDnDMZg8JYLMTGz7uvXKrcKGJUZJlZObyfgdaN9g==", 318 | "requires": { 319 | "@types/long": "*", 320 | "@types/node": "*" 321 | } 322 | }, 323 | "@types/long": { 324 | "version": "4.0.1", 325 | "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", 326 | "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" 327 | }, 328 | "@types/node": { 329 | "version": "10.17.14", 330 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.14.tgz", 331 | "integrity": "sha512-G0UmX5uKEmW+ZAhmZ6PLTQ5eu/VPaT+d/tdLd5IFsKRPcbe6lPxocBtcYBFSaLaCW8O60AX90e91Nsp8lVHCNw==" 332 | }, 333 | "ansi-regex": { 334 | "version": "2.1.1", 335 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 336 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 337 | }, 338 | "ascli": { 339 | "version": "1.0.1", 340 | "resolved": "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz", 341 | "integrity": "sha1-vPpZdKYvGOgcq660lzKrSoj5Brw=", 342 | "requires": { 343 | "colour": "~0.7.1", 344 | "optjs": "~3.2.2" 345 | } 346 | }, 347 | "bytebuffer": { 348 | "version": "5.0.1", 349 | "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz", 350 | "integrity": "sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0=", 351 | "requires": { 352 | "long": "~3" 353 | }, 354 | "dependencies": { 355 | "long": { 356 | "version": "3.2.0", 357 | "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", 358 | "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" 359 | } 360 | } 361 | }, 362 | "camelcase": { 363 | "version": "2.1.1", 364 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", 365 | "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" 366 | }, 367 | "cliui": { 368 | "version": "3.2.0", 369 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", 370 | "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", 371 | "requires": { 372 | "string-width": "^1.0.1", 373 | "strip-ansi": "^3.0.1", 374 | "wrap-ansi": "^2.0.0" 375 | } 376 | }, 377 | "code-point-at": { 378 | "version": "1.1.0", 379 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 380 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 381 | }, 382 | "colour": { 383 | "version": "0.7.1", 384 | "resolved": "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz", 385 | "integrity": "sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g=" 386 | }, 387 | "core-js": { 388 | "version": "3.6.2", 389 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.2.tgz", 390 | "integrity": "sha512-hIE5dXkRzRvnZ5vhkRfQxUvDxQZmD9oueA08jDYRBKJHx+VIl/Pne/e0A4x9LObEEthC/TqiZybUoNM4tRgnKg==" 391 | }, 392 | "decamelize": { 393 | "version": "1.2.0", 394 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 395 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 396 | }, 397 | "dom-storage": { 398 | "version": "2.1.0", 399 | "resolved": "https://registry.npmjs.org/dom-storage/-/dom-storage-2.1.0.tgz", 400 | "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==" 401 | }, 402 | "encoding": { 403 | "version": "0.1.12", 404 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", 405 | "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", 406 | "requires": { 407 | "iconv-lite": "~0.4.13" 408 | } 409 | }, 410 | "faye-websocket": { 411 | "version": "0.11.3", 412 | "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", 413 | "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", 414 | "requires": { 415 | "websocket-driver": ">=0.5.1" 416 | } 417 | }, 418 | "firebase": { 419 | "version": "7.8.0", 420 | "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.8.0.tgz", 421 | "integrity": "sha512-oOXb8asc9mF+xN3nHUnt8cFDo4sDkSuTSLRmPM3Jpz6yriBHJYuO1k7G6sbJOtCVl+nkj0maIyByNVQxt2eBlA==", 422 | "requires": { 423 | "@firebase/analytics": "0.2.12", 424 | "@firebase/app": "0.5.3", 425 | "@firebase/app-types": "0.5.0", 426 | "@firebase/auth": "0.13.4", 427 | "@firebase/database": "0.5.20", 428 | "@firebase/firestore": "1.10.0", 429 | "@firebase/functions": "0.4.31", 430 | "@firebase/installations": "0.4.1", 431 | "@firebase/messaging": "0.6.3", 432 | "@firebase/performance": "0.2.31", 433 | "@firebase/polyfill": "0.3.31", 434 | "@firebase/remote-config": "0.1.12", 435 | "@firebase/storage": "0.3.25", 436 | "@firebase/util": "0.2.39" 437 | } 438 | }, 439 | "grpc": { 440 | "version": "1.24.2", 441 | "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.24.2.tgz", 442 | "integrity": "sha512-EG3WH6AWMVvAiV15d+lr+K77HJ/KV/3FvMpjKjulXHbTwgDZkhkcWbwhxFAoTdxTkQvy0WFcO3Nog50QBbHZWw==", 443 | "requires": { 444 | "@types/bytebuffer": "^5.0.40", 445 | "lodash.camelcase": "^4.3.0", 446 | "lodash.clone": "^4.5.0", 447 | "nan": "^2.13.2", 448 | "node-pre-gyp": "^0.14.0", 449 | "protobufjs": "^5.0.3" 450 | }, 451 | "dependencies": { 452 | "abbrev": { 453 | "version": "1.1.1", 454 | "bundled": true 455 | }, 456 | "ansi-regex": { 457 | "version": "2.1.1", 458 | "bundled": true 459 | }, 460 | "aproba": { 461 | "version": "1.2.0", 462 | "bundled": true 463 | }, 464 | "are-we-there-yet": { 465 | "version": "1.1.5", 466 | "bundled": true, 467 | "requires": { 468 | "delegates": "^1.0.0", 469 | "readable-stream": "^2.0.6" 470 | } 471 | }, 472 | "balanced-match": { 473 | "version": "1.0.0", 474 | "bundled": true 475 | }, 476 | "brace-expansion": { 477 | "version": "1.1.11", 478 | "bundled": true, 479 | "requires": { 480 | "balanced-match": "^1.0.0", 481 | "concat-map": "0.0.1" 482 | } 483 | }, 484 | "chownr": { 485 | "version": "1.1.3", 486 | "bundled": true 487 | }, 488 | "code-point-at": { 489 | "version": "1.1.0", 490 | "bundled": true 491 | }, 492 | "concat-map": { 493 | "version": "0.0.1", 494 | "bundled": true 495 | }, 496 | "console-control-strings": { 497 | "version": "1.1.0", 498 | "bundled": true 499 | }, 500 | "core-util-is": { 501 | "version": "1.0.2", 502 | "bundled": true 503 | }, 504 | "debug": { 505 | "version": "3.2.6", 506 | "bundled": true, 507 | "requires": { 508 | "ms": "^2.1.1" 509 | } 510 | }, 511 | "deep-extend": { 512 | "version": "0.6.0", 513 | "bundled": true 514 | }, 515 | "delegates": { 516 | "version": "1.0.0", 517 | "bundled": true 518 | }, 519 | "detect-libc": { 520 | "version": "1.0.3", 521 | "bundled": true 522 | }, 523 | "fs-minipass": { 524 | "version": "1.2.7", 525 | "bundled": true, 526 | "requires": { 527 | "minipass": "^2.6.0" 528 | } 529 | }, 530 | "fs.realpath": { 531 | "version": "1.0.0", 532 | "bundled": true 533 | }, 534 | "gauge": { 535 | "version": "2.7.4", 536 | "bundled": true, 537 | "requires": { 538 | "aproba": "^1.0.3", 539 | "console-control-strings": "^1.0.0", 540 | "has-unicode": "^2.0.0", 541 | "object-assign": "^4.1.0", 542 | "signal-exit": "^3.0.0", 543 | "string-width": "^1.0.1", 544 | "strip-ansi": "^3.0.1", 545 | "wide-align": "^1.1.0" 546 | } 547 | }, 548 | "glob": { 549 | "version": "7.1.4", 550 | "bundled": true, 551 | "requires": { 552 | "fs.realpath": "^1.0.0", 553 | "inflight": "^1.0.4", 554 | "inherits": "2", 555 | "minimatch": "^3.0.4", 556 | "once": "^1.3.0", 557 | "path-is-absolute": "^1.0.0" 558 | } 559 | }, 560 | "has-unicode": { 561 | "version": "2.0.1", 562 | "bundled": true 563 | }, 564 | "iconv-lite": { 565 | "version": "0.4.24", 566 | "bundled": true, 567 | "requires": { 568 | "safer-buffer": ">= 2.1.2 < 3" 569 | } 570 | }, 571 | "ignore-walk": { 572 | "version": "3.0.3", 573 | "bundled": true, 574 | "requires": { 575 | "minimatch": "^3.0.4" 576 | } 577 | }, 578 | "inflight": { 579 | "version": "1.0.6", 580 | "bundled": true, 581 | "requires": { 582 | "once": "^1.3.0", 583 | "wrappy": "1" 584 | } 585 | }, 586 | "inherits": { 587 | "version": "2.0.4", 588 | "bundled": true 589 | }, 590 | "ini": { 591 | "version": "1.3.5", 592 | "bundled": true 593 | }, 594 | "is-fullwidth-code-point": { 595 | "version": "1.0.0", 596 | "bundled": true, 597 | "requires": { 598 | "number-is-nan": "^1.0.0" 599 | } 600 | }, 601 | "isarray": { 602 | "version": "1.0.0", 603 | "bundled": true 604 | }, 605 | "minimatch": { 606 | "version": "3.0.4", 607 | "bundled": true, 608 | "requires": { 609 | "brace-expansion": "^1.1.7" 610 | } 611 | }, 612 | "minimist": { 613 | "version": "1.2.0", 614 | "bundled": true 615 | }, 616 | "minipass": { 617 | "version": "2.9.0", 618 | "bundled": true, 619 | "requires": { 620 | "safe-buffer": "^5.1.2", 621 | "yallist": "^3.0.0" 622 | } 623 | }, 624 | "minizlib": { 625 | "version": "1.3.3", 626 | "bundled": true, 627 | "requires": { 628 | "minipass": "^2.9.0" 629 | } 630 | }, 631 | "mkdirp": { 632 | "version": "0.5.1", 633 | "bundled": true, 634 | "requires": { 635 | "minimist": "0.0.8" 636 | }, 637 | "dependencies": { 638 | "minimist": { 639 | "version": "0.0.8", 640 | "bundled": true 641 | } 642 | } 643 | }, 644 | "ms": { 645 | "version": "2.1.2", 646 | "bundled": true 647 | }, 648 | "needle": { 649 | "version": "2.4.0", 650 | "bundled": true, 651 | "requires": { 652 | "debug": "^3.2.6", 653 | "iconv-lite": "^0.4.4", 654 | "sax": "^1.2.4" 655 | } 656 | }, 657 | "node-pre-gyp": { 658 | "version": "0.14.0", 659 | "bundled": true, 660 | "requires": { 661 | "detect-libc": "^1.0.2", 662 | "mkdirp": "^0.5.1", 663 | "needle": "^2.2.1", 664 | "nopt": "^4.0.1", 665 | "npm-packlist": "^1.1.6", 666 | "npmlog": "^4.0.2", 667 | "rc": "^1.2.7", 668 | "rimraf": "^2.6.1", 669 | "semver": "^5.3.0", 670 | "tar": "^4.4.2" 671 | } 672 | }, 673 | "nopt": { 674 | "version": "4.0.1", 675 | "bundled": true, 676 | "requires": { 677 | "abbrev": "1", 678 | "osenv": "^0.1.4" 679 | } 680 | }, 681 | "npm-bundled": { 682 | "version": "1.0.6", 683 | "bundled": true 684 | }, 685 | "npm-packlist": { 686 | "version": "1.4.6", 687 | "bundled": true, 688 | "requires": { 689 | "ignore-walk": "^3.0.1", 690 | "npm-bundled": "^1.0.1" 691 | } 692 | }, 693 | "npmlog": { 694 | "version": "4.1.2", 695 | "bundled": true, 696 | "requires": { 697 | "are-we-there-yet": "~1.1.2", 698 | "console-control-strings": "~1.1.0", 699 | "gauge": "~2.7.3", 700 | "set-blocking": "~2.0.0" 701 | } 702 | }, 703 | "number-is-nan": { 704 | "version": "1.0.1", 705 | "bundled": true 706 | }, 707 | "object-assign": { 708 | "version": "4.1.1", 709 | "bundled": true 710 | }, 711 | "once": { 712 | "version": "1.4.0", 713 | "bundled": true, 714 | "requires": { 715 | "wrappy": "1" 716 | } 717 | }, 718 | "os-homedir": { 719 | "version": "1.0.2", 720 | "bundled": true 721 | }, 722 | "os-tmpdir": { 723 | "version": "1.0.2", 724 | "bundled": true 725 | }, 726 | "osenv": { 727 | "version": "0.1.5", 728 | "bundled": true, 729 | "requires": { 730 | "os-homedir": "^1.0.0", 731 | "os-tmpdir": "^1.0.0" 732 | } 733 | }, 734 | "path-is-absolute": { 735 | "version": "1.0.1", 736 | "bundled": true 737 | }, 738 | "process-nextick-args": { 739 | "version": "2.0.1", 740 | "bundled": true 741 | }, 742 | "protobufjs": { 743 | "version": "5.0.3", 744 | "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz", 745 | "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==", 746 | "requires": { 747 | "ascli": "~1", 748 | "bytebuffer": "~5", 749 | "glob": "^7.0.5", 750 | "yargs": "^3.10.0" 751 | } 752 | }, 753 | "rc": { 754 | "version": "1.2.8", 755 | "bundled": true, 756 | "requires": { 757 | "deep-extend": "^0.6.0", 758 | "ini": "~1.3.0", 759 | "minimist": "^1.2.0", 760 | "strip-json-comments": "~2.0.1" 761 | } 762 | }, 763 | "readable-stream": { 764 | "version": "2.3.6", 765 | "bundled": true, 766 | "requires": { 767 | "core-util-is": "~1.0.0", 768 | "inherits": "~2.0.3", 769 | "isarray": "~1.0.0", 770 | "process-nextick-args": "~2.0.0", 771 | "safe-buffer": "~5.1.1", 772 | "string_decoder": "~1.1.1", 773 | "util-deprecate": "~1.0.1" 774 | } 775 | }, 776 | "rimraf": { 777 | "version": "2.7.1", 778 | "bundled": true, 779 | "requires": { 780 | "glob": "^7.1.3" 781 | } 782 | }, 783 | "safe-buffer": { 784 | "version": "5.1.2", 785 | "bundled": true 786 | }, 787 | "safer-buffer": { 788 | "version": "2.1.2", 789 | "bundled": true 790 | }, 791 | "sax": { 792 | "version": "1.2.4", 793 | "bundled": true 794 | }, 795 | "semver": { 796 | "version": "5.7.1", 797 | "bundled": true 798 | }, 799 | "set-blocking": { 800 | "version": "2.0.0", 801 | "bundled": true 802 | }, 803 | "signal-exit": { 804 | "version": "3.0.2", 805 | "bundled": true 806 | }, 807 | "string-width": { 808 | "version": "1.0.2", 809 | "bundled": true, 810 | "requires": { 811 | "code-point-at": "^1.0.0", 812 | "is-fullwidth-code-point": "^1.0.0", 813 | "strip-ansi": "^3.0.0" 814 | } 815 | }, 816 | "string_decoder": { 817 | "version": "1.1.1", 818 | "bundled": true, 819 | "requires": { 820 | "safe-buffer": "~5.1.0" 821 | } 822 | }, 823 | "strip-ansi": { 824 | "version": "3.0.1", 825 | "bundled": true, 826 | "requires": { 827 | "ansi-regex": "^2.0.0" 828 | } 829 | }, 830 | "strip-json-comments": { 831 | "version": "2.0.1", 832 | "bundled": true 833 | }, 834 | "tar": { 835 | "version": "4.4.13", 836 | "bundled": true, 837 | "requires": { 838 | "chownr": "^1.1.1", 839 | "fs-minipass": "^1.2.5", 840 | "minipass": "^2.8.6", 841 | "minizlib": "^1.2.1", 842 | "mkdirp": "^0.5.0", 843 | "safe-buffer": "^5.1.2", 844 | "yallist": "^3.0.3" 845 | } 846 | }, 847 | "util-deprecate": { 848 | "version": "1.0.2", 849 | "bundled": true 850 | }, 851 | "wide-align": { 852 | "version": "1.1.3", 853 | "bundled": true, 854 | "requires": { 855 | "string-width": "^1.0.2 || 2" 856 | } 857 | }, 858 | "wrappy": { 859 | "version": "1.0.2", 860 | "bundled": true 861 | }, 862 | "yallist": { 863 | "version": "3.1.1", 864 | "bundled": true 865 | } 866 | } 867 | }, 868 | "http-parser-js": { 869 | "version": "0.4.10", 870 | "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", 871 | "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=" 872 | }, 873 | "iconv-lite": { 874 | "version": "0.4.24", 875 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 876 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 877 | "requires": { 878 | "safer-buffer": ">= 2.1.2 < 3" 879 | } 880 | }, 881 | "idb": { 882 | "version": "3.0.2", 883 | "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", 884 | "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==" 885 | }, 886 | "invert-kv": { 887 | "version": "1.0.0", 888 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", 889 | "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" 890 | }, 891 | "is-fullwidth-code-point": { 892 | "version": "1.0.0", 893 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 894 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 895 | "requires": { 896 | "number-is-nan": "^1.0.0" 897 | } 898 | }, 899 | "is-stream": { 900 | "version": "1.1.0", 901 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 902 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 903 | }, 904 | "isomorphic-fetch": { 905 | "version": "2.2.1", 906 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", 907 | "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", 908 | "requires": { 909 | "node-fetch": "^1.0.1", 910 | "whatwg-fetch": ">=0.10.0" 911 | } 912 | }, 913 | "lcid": { 914 | "version": "1.0.0", 915 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", 916 | "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", 917 | "requires": { 918 | "invert-kv": "^1.0.0" 919 | } 920 | }, 921 | "lodash.camelcase": { 922 | "version": "4.3.0", 923 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", 924 | "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" 925 | }, 926 | "lodash.clone": { 927 | "version": "4.5.0", 928 | "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", 929 | "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=" 930 | }, 931 | "long": { 932 | "version": "4.0.0", 933 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 934 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" 935 | }, 936 | "nan": { 937 | "version": "2.14.0", 938 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", 939 | "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" 940 | }, 941 | "node-fetch": { 942 | "version": "1.7.3", 943 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", 944 | "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", 945 | "requires": { 946 | "encoding": "^0.1.11", 947 | "is-stream": "^1.0.1" 948 | } 949 | }, 950 | "number-is-nan": { 951 | "version": "1.0.1", 952 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 953 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 954 | }, 955 | "optjs": { 956 | "version": "3.2.2", 957 | "resolved": "https://registry.npmjs.org/optjs/-/optjs-3.2.2.tgz", 958 | "integrity": "sha1-aabOicRCpEQDFBrS+bNwvVu29O4=" 959 | }, 960 | "os-locale": { 961 | "version": "1.4.0", 962 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", 963 | "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", 964 | "requires": { 965 | "lcid": "^1.0.0" 966 | } 967 | }, 968 | "promise-polyfill": { 969 | "version": "8.1.3", 970 | "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", 971 | "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" 972 | }, 973 | "protobufjs": { 974 | "version": "6.8.8", 975 | "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", 976 | "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", 977 | "requires": { 978 | "@protobufjs/aspromise": "^1.1.2", 979 | "@protobufjs/base64": "^1.1.2", 980 | "@protobufjs/codegen": "^2.0.4", 981 | "@protobufjs/eventemitter": "^1.1.0", 982 | "@protobufjs/fetch": "^1.1.0", 983 | "@protobufjs/float": "^1.0.2", 984 | "@protobufjs/inquire": "^1.1.0", 985 | "@protobufjs/path": "^1.1.2", 986 | "@protobufjs/pool": "^1.1.0", 987 | "@protobufjs/utf8": "^1.1.0", 988 | "@types/long": "^4.0.0", 989 | "@types/node": "^10.1.0", 990 | "long": "^4.0.0" 991 | } 992 | }, 993 | "safe-buffer": { 994 | "version": "5.2.0", 995 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 996 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 997 | }, 998 | "safer-buffer": { 999 | "version": "2.1.2", 1000 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1001 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1002 | }, 1003 | "string-width": { 1004 | "version": "1.0.2", 1005 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1006 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1007 | "requires": { 1008 | "code-point-at": "^1.0.0", 1009 | "is-fullwidth-code-point": "^1.0.0", 1010 | "strip-ansi": "^3.0.0" 1011 | } 1012 | }, 1013 | "strip-ansi": { 1014 | "version": "3.0.1", 1015 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1016 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1017 | "requires": { 1018 | "ansi-regex": "^2.0.0" 1019 | } 1020 | }, 1021 | "tslib": { 1022 | "version": "1.10.0", 1023 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", 1024 | "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" 1025 | }, 1026 | "websocket-driver": { 1027 | "version": "0.7.3", 1028 | "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", 1029 | "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", 1030 | "requires": { 1031 | "http-parser-js": ">=0.4.0 <0.4.11", 1032 | "safe-buffer": ">=5.1.0", 1033 | "websocket-extensions": ">=0.1.1" 1034 | } 1035 | }, 1036 | "websocket-extensions": { 1037 | "version": "0.1.4", 1038 | "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", 1039 | "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" 1040 | }, 1041 | "whatwg-fetch": { 1042 | "version": "3.0.0", 1043 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", 1044 | "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" 1045 | }, 1046 | "window-size": { 1047 | "version": "0.1.4", 1048 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", 1049 | "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" 1050 | }, 1051 | "wrap-ansi": { 1052 | "version": "2.1.0", 1053 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", 1054 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", 1055 | "requires": { 1056 | "string-width": "^1.0.1", 1057 | "strip-ansi": "^3.0.1" 1058 | } 1059 | }, 1060 | "xmlhttprequest": { 1061 | "version": "1.8.0", 1062 | "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", 1063 | "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" 1064 | }, 1065 | "y18n": { 1066 | "version": "3.2.1", 1067 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", 1068 | "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" 1069 | }, 1070 | "yargs": { 1071 | "version": "3.32.0", 1072 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", 1073 | "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", 1074 | "requires": { 1075 | "camelcase": "^2.0.1", 1076 | "cliui": "^3.0.3", 1077 | "decamelize": "^1.1.1", 1078 | "os-locale": "^1.4.0", 1079 | "string-width": "^1.0.1", 1080 | "window-size": "^0.1.4", 1081 | "y18n": "^3.2.0" 1082 | } 1083 | } 1084 | } 1085 | } 1086 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-smartlifeair", 3 | "version": "1.0.13", 4 | "description": "NO NEED of Firmware changes or COMMAND LINE configurations. A simple NR node that works with almost any Smartlife and Tuya device. Read the instructions properly to save time. ", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "test" 8 | }, 9 | "keywords": [ 10 | "smartlifeair", 11 | "node-red", 12 | "smartlife", 13 | "tuya", 14 | "smart life", 15 | "smart homes", 16 | "smart things" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/razorRun/node-red-contrib-smartlifeair.git/" 21 | }, 22 | "author": "Roshan Milinda", 23 | "license": "GPL (GPLv3)", 24 | "homepage": "https://github.com/razorRun/node-red-contrib-smartlifeair.git/#readme", 25 | "dependencies": { 26 | "firebase": "^7.8.0" 27 | }, 28 | "node-red": { 29 | "nodes": { 30 | "smartlife-air-login": "config.js", 31 | "smartlife-air-device": "device-node.js" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /run-on-change.py: -------------------------------------------------------------------------------- 1 | # detect file changes from inside vigrant- 2 | import os.path 3 | import time 4 | import subprocess 5 | import glob 6 | 7 | 8 | def get_last_modefied_time(folderPath): 9 | files = glob.glob(folderPath) 10 | date_list = [] 11 | # print(files) 12 | for file in files: 13 | date_list.append(os.path.getmtime(file)) 14 | # print(date_list) 15 | return max(date_list) 16 | 17 | path = "/home/pi/node-red-contrib-smartlifeair/*" 18 | lmt = "" 19 | cmd1 = "npm install /home/pi/node-red-contrib-smartlifeair/" 20 | cmd2 = "sudo systemctl restart nodered.service" 21 | cmd1 = "npm uninstall /home/pi/node-red-contrib-smartlifeair/" 22 | 23 | while True: 24 | nmt = get_last_modefied_time(path) 25 | if nmt != lmt: 26 | print("Change Detected") 27 | subprocess.Popen(cmd3.split(), cwd='/root/.node-red') 28 | subprocess.Popen(cmd1.split(), cwd='/root/.node-red') 29 | subprocess.Popen(cmd2.split()) 30 | lmt = nmt 31 | time.sleep(2) 32 | --------------------------------------------------------------------------------