From 3e3509115b9f250aee62387a6fc34e255e18be22 Mon Sep 17 00:00:00 2001 From: David Jakowenko Date: Tue, 24 May 2022 02:24:30 -0400 Subject: [PATCH] feat(config): anonymous telemetry data used to help deliver new features --- README.md | 10 ++++++++ api/server.js | 2 ++ api/src/constants/defaults.js | 1 + api/src/controllers/analytics.controller.js | 13 ++++++++++ api/src/routes/analytics.routes.js | 8 ++++++ api/src/routes/index.js | 1 + api/src/util/heartbeat.util.js | 28 +++++++++++++++++++++ frontend/src/App.vue | 17 +++++++++++++ 8 files changed, 80 insertions(+) create mode 100644 api/src/controllers/analytics.controller.js create mode 100644 api/src/routes/analytics.routes.js create mode 100644 api/src/util/heartbeat.util.js diff --git a/README.md b/README.md index 4a110bf6..e8b79963 100644 --- a/README.md +++ b/README.md @@ -548,6 +548,16 @@ ui: lines: 500 ``` +### `telemetry` + +```yaml +# telemetry settings (default: shown below) +# self hosted version of plausible.io +# 100% anonymous, used to help improve project +# no cookies and fully compliant with GDPR, CCPA and PECR +telemetry: true +``` + ## Storing Secrets **Note:** If using one of the [Home Assistant Add-ons](https://github.com/jakowenko/double-take-hassio-addons) then the default Home Assistant `/config/secrets.yaml` file is used. diff --git a/api/server.js b/api/server.js index 10b45080..ad078045 100644 --- a/api/server.js +++ b/api/server.js @@ -8,6 +8,7 @@ const storage = require('./src/util/storage.util'); const database = require('./src/util/db.util'); const config = require('./src/constants/config'); const shutdown = require('./src/util/shutdown.util'); +const heartbeat = require('./src/util/heartbeat.util'); const validate = require('./src/schemas/validate'); module.exports.start = async () => { @@ -21,6 +22,7 @@ module.exports.start = async () => { mqtt.connect(); storage.purge(); socket.connect(server); + heartbeat.cron(); }; try { diff --git a/api/src/constants/defaults.js b/api/src/constants/defaults.js index 0300c805..e8b3f4eb 100644 --- a/api/src/constants/defaults.js +++ b/api/src/constants/defaults.js @@ -1,4 +1,5 @@ module.exports = { + telemetry: true, auth: false, token: { image: '24h', diff --git a/api/src/controllers/analytics.controller.js b/api/src/controllers/analytics.controller.js new file mode 100644 index 00000000..136ca054 --- /dev/null +++ b/api/src/controllers/analytics.controller.js @@ -0,0 +1,13 @@ +const axios = require('axios'); +const { TELEMETRY } = require('../constants')(); + +module.exports.analytics = async (req, res) => { + if (process.env.NODE_ENV !== 'production' || !TELEMETRY) return res.send(); + const { data } = await axios({ + method: 'get', + url: 'https://analytics.jako.io/js/script.local.js', + }).catch((/* error */) => { + return res.send(); + }); + res.contentType('application/javascript').send(data); +}; diff --git a/api/src/routes/analytics.routes.js b/api/src/routes/analytics.routes.js new file mode 100644 index 00000000..42629e07 --- /dev/null +++ b/api/src/routes/analytics.routes.js @@ -0,0 +1,8 @@ +const express = require('express'); +const { analytics } = require('../controllers/analytics.controller'); + +const router = express.Router(); + +router.get('/', analytics); + +module.exports = router; diff --git a/api/src/routes/index.js b/api/src/routes/index.js index e75539e8..0cda1654 100644 --- a/api/src/routes/index.js +++ b/api/src/routes/index.js @@ -16,6 +16,7 @@ router.use('/proxy', require('./proxy.routes')); router.use('/logger', require('./logger.routes')); router.use('/status', require('./status.routes')); router.use('/export', require('./export.routes')); +router.use('/analytics/analytics.js', require('./analytics.routes')); router.use(STORAGE.TMP.PATH, express.static(STORAGE.TMP.PATH)); router.all('*', (req, res) => res.status(NOT_FOUND).error(`${req.originalUrl} not found`)); diff --git a/api/src/util/heartbeat.util.js b/api/src/util/heartbeat.util.js new file mode 100644 index 00000000..52eddc15 --- /dev/null +++ b/api/src/util/heartbeat.util.js @@ -0,0 +1,28 @@ +const os = require('os'); +const schedule = require('node-schedule'); +const axios = require('axios'); +const { version } = require('../../package.json'); +const { TELEMETRY } = require('../constants')(); + +module.exports.cron = async () => { + if (process.env.NODE_ENV !== 'production' || !TELEMETRY) return; + await this.track('start'); + schedule.scheduleJob('*/15 * * * *', () => this.track('heartbeat')); +}; + +module.exports.track = async (type) => { + await axios({ + method: 'post', + url: 'https://analytics.jako.io/api/event', + data: { + name: 'pageview', + url: `http://localhost/${type}`, + domain: 'double-take-api', + props: JSON.stringify({ + version, + arch: os.arch(), + }), + }, + validateStatus: () => true, + }); +}; diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 54e3ec63..e297cf27 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -54,6 +54,7 @@ export default { this.hidden = !status; this.loaded = !status; }); + this.addAnalytics(); }, mounted() { this.toolbarHeight = this.$refs.toolbar.getHeight(); @@ -161,6 +162,22 @@ export default { } return runSetup; }, + async addAnalytics() { + if (process.env.NODE_ENV !== 'production') return; + ApiService.get('config') + .then(({ data }) => { + if (data.telemetry) { + const analytics = document.createElement('script'); + analytics.type = 'text/javascript'; + analytics.src = `${Constants().api}/analytics/analytics.js`; + analytics.defer = true; + analytics.setAttribute('data-domain', 'double-take-frontend'); + analytics.setAttribute('data-api', 'https://analytics.jako.io/api/event'); + document.head.appendChild(analytics); + } + }) + .catch(() => {}); + }, }, };