wires strava authorization to the web ui

This commit is contained in:
Lars Berning 2022-02-05 21:54:15 +01:00
parent 15cdf2e22f
commit dfc17c09b2
No known key found for this signature in database
GPG Key ID: 028E73C9E1D8A0B3
7 changed files with 86 additions and 30 deletions

View File

@ -48,8 +48,8 @@ function createWebServer () {
})
})
function notifyClients (message) {
const messageString = JSON.stringify(message)
function notifyClients (type, data) {
const messageString = JSON.stringify({ type, data })
wss.clients.forEach(function each (client) {
if (client.readyState === WebSocket.OPEN) {
client.send(messageString)

View File

@ -12,23 +12,40 @@ const rowingMetricsFields = ['strokesTotal', 'distanceTotal', 'caloriesTotal', '
'heartrateBatteryLevel', 'splitFormatted', 'strokesPerMinute', 'durationTotalFormatted']
export function createApp (app) {
const mode = window.location.hash
const appMode = mode === '#:standalone:' ? 'STANDALONE' : mode === '#:kiosk:' ? 'KIOSK' : 'BROWSER'
const urlParameters = new URLSearchParams(window.location.search)
const mode = urlParameters.get('mode')
const appMode = mode === 'standalone' ? 'STANDALONE' : mode === 'kiosk' ? 'KIOSK' : 'BROWSER'
app.updateState({ ...app.getState(), appMode })
const stravaAuthorizationCode = urlParameters.get('code')
let socket
initWebsocket()
resetFields()
requestWakeLock()
function websocketOpened () {
if (stravaAuthorizationCode) {
handleStravaAuthorization(stravaAuthorizationCode)
}
}
function handleStravaAuthorization (stravaAuthorizationCode) {
if (socket)socket.send(JSON.stringify({ command: 'stravaAuthorizationCode', data: stravaAuthorizationCode }))
}
let initialWebsocketOpenend = true
function initWebsocket () {
// use the native websocket implementation of browser to communicate with backend
// eslint-disable-next-line no-undef
socket = new WebSocket(`ws://${location.host}/websocket`)
socket.addEventListener('open', (event) => {
console.log('websocket opened')
if (initialWebsocketOpenend) {
websocketOpened()
initialWebsocketOpenend = false
}
})
socket.addEventListener('error', (error) => {
@ -46,21 +63,37 @@ export function createApp (app) {
// todo: we should use different types of messages to make processing easier
socket.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data)
let activeFields = rowingMetricsFields
// if we are in reset state only update heart rate
if (data.strokesTotal === 0) {
activeFields = ['heartrate', 'heartrateBatteryLevel']
const message = JSON.parse(event.data)
if (!message.type) {
console.error('message does not contain messageType specifier', message)
return
}
const data = message.data
switch (message.type) {
case 'metrics': {
let activeFields = rowingMetricsFields
// if we are in reset state only update heart rate
if (data.strokesTotal === 0) {
activeFields = ['heartrate', 'heartrateBatteryLevel']
}
const filteredData = filterObjectByKeys(data, activeFields)
let updatedState = { ...app.getState(), metrics: filteredData }
if (data.peripheralMode) {
updatedState = { ...app.getState(), peripheralMode: data.peripheralMode }
const filteredData = filterObjectByKeys(data, activeFields)
let updatedState = { ...app.getState(), metrics: filteredData }
if (data.peripheralMode) {
updatedState = { ...app.getState(), peripheralMode: data.peripheralMode }
}
app.updateState(updatedState)
break
}
case 'authorizeStrava': {
const currentUrl = encodeURIComponent(window.location.href)
window.location.href = `https://www.strava.com/oauth/authorize?client_id=${data.stravaClientId}&response_type=code&redirect_uri=${currentUrl}&approval_prompt=force&scope=activity:write`
break
}
default: {
console.error(`unknown message type: ${message.type}`)
}
}
app.updateState(updatedState)
} catch (err) {
console.log(err)
}

View File

@ -9,8 +9,8 @@
"type": "image/png"
}
],
"background_color": "#0059B3",
"background_color": "#002b57",
"display": "fullscreen",
"orientation": "any",
"start_url": "/#:standalone:"
"start_url": "/?mode=standalone"
}

View File

@ -5,18 +5,32 @@
Handles uploading workout data to different cloud providers
*/
import log from 'loglevel'
import EventEmitter from 'events'
import { createStravaAPI } from '../tools/StravaAPI.js'
import config from '../tools/ConfigManager.js'
function createWorkoutUploader (workoutRecorder) {
const emitter = new EventEmitter()
let stravaAuthorizationCodeResolver
function getStravaAuthorizationCode () {
return new Promise((resolve) => {
log.info('please open https://www.strava.com/oauth/authorize?client_id=&response_type=code&redirect_uri=http://localhost/index.html&approval_prompt=force&scope=activity:write')
setTimeout(() => { resolve('') }, 10)
emitter.emit('authorizeStrava', { stravaClientId: config.stravaClientId })
stravaAuthorizationCodeResolver = resolve
})
}
const stravaAPI = createStravaAPI(getStravaAuthorizationCode)
function stravaAuthorizationCode (stravaAuthorizationCode) {
if (stravaAuthorizationCodeResolver) {
stravaAuthorizationCodeResolver(stravaAuthorizationCode)
stravaAuthorizationCodeResolver = undefined
}
}
async function upload () {
if (workoutRecorder.canCreateRecordings()) {
log.debug('uploading workout to strava...')
@ -26,9 +40,10 @@ function createWorkoutUploader (workoutRecorder) {
}
}
return {
upload
}
return Object.assign(emitter, {
upload,
stravaAuthorizationCode
})
}
export { createWorkoutUploader }

View File

@ -52,7 +52,7 @@ peripheralManager.on('control', (event) => {
peripheralManager.notifyStatus({ name: 'startedOrResumedByUser' })
event.res = true
} else if (event?.req?.name === 'peripheralMode') {
webServer.notifyClients({ peripheralMode: event.req.peripheralMode })
webServer.notifyClients('metrics', { peripheralMode: event.req.peripheralMode })
event.res = true
} else {
log.info('unhandled Command', event.req)
@ -81,7 +81,7 @@ const workoutRecorder = createWorkoutRecorder()
const workoutUploader = createWorkoutUploader(workoutRecorder)
rowingStatistics.on('driveFinished', (metrics) => {
webServer.notifyClients(metrics)
webServer.notifyClients('metrics', metrics)
peripheralManager.notifyMetrics('strokeStateChanged', metrics)
})
@ -90,7 +90,7 @@ rowingStatistics.on('recoveryFinished', (metrics) => {
`, split: ${metrics.splitFormatted}, ratio: ${metrics.powerRatio.toFixed(2)}, dist: ${metrics.distanceTotal.toFixed(1)}m` +
`, cal: ${metrics.caloriesTotal.toFixed(1)}kcal, SPM: ${metrics.strokesPerMinute.toFixed(1)}, speed: ${metrics.speed.toFixed(2)}km/h` +
`, cal/hour: ${metrics.caloriesPerHour.toFixed(1)}kcal, cal/minute: ${metrics.caloriesPerMinute.toFixed(1)}kcal`)
webServer.notifyClients(metrics)
webServer.notifyClients('metrics', metrics)
peripheralManager.notifyMetrics('strokeFinished', metrics)
if (metrics.sessionState === 'rowing') {
workoutRecorder.recordStroke(metrics)
@ -98,7 +98,7 @@ rowingStatistics.on('recoveryFinished', (metrics) => {
})
rowingStatistics.on('webMetricsUpdate', (metrics) => {
webServer.notifyClients(metrics)
webServer.notifyClients('metrics', metrics)
})
rowingStatistics.on('peripheralMetricsUpdate', (metrics) => {
@ -123,6 +123,10 @@ if (config.heartrateMonitorANT) {
})
}
workoutUploader.on('authorizeStrava', (data) => {
webServer.notifyClients('authorizeStrava', data)
})
const webServer = createWebServer()
webServer.on('messageReceived', (message) => {
switch (message.command) {
@ -138,6 +142,10 @@ webServer.on('messageReceived', (message) => {
workoutUploader.upload()
break
}
case 'stravaAuthorizationCode': {
workoutUploader.stravaAuthorizationCode(message.data)
break
}
default: {
log.warn('invalid command received:', message)
}
@ -145,7 +153,7 @@ webServer.on('messageReceived', (message) => {
})
webServer.on('clientConnected', () => {
webServer.notifyClients({ peripheralMode: peripheralManager.getPeripheralMode() })
webServer.notifyClients('metrics', { peripheralMode: peripheralManager.getPeripheralMode() })
})
/*

View File

@ -12,4 +12,4 @@ openbox-session &
# Start Chromium in kiosk mode
sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' ~/.config/chromium/'Local State'
sed -i 's/"exited_cleanly":false/"exited_cleanly":true/; s/"exit_type":"[^"]\+"/"exit_type":"Normal"/' ~/.config/chromium/Default/Preferences
chromium-browser --disable-infobars --kiosk --noerrdialogs --disable-session-crashed-bubble --disable-pinch --check-for-update-interval=604800 --app="http://127.0.0.1/#:kiosk:"
chromium-browser --disable-infobars --kiosk --noerrdialogs --disable-session-crashed-bubble --disable-pinch --check-for-update-interval=604800 --app="http://127.0.0.1/?mode=kiosk"

View File

@ -21,7 +21,7 @@
"lint": "eslint ./app ./config && markdownlint-cli2 '**/*.md' '#node_modules'",
"start": "node app/server.js",
"dev": "npm-run-all --parallel dev:backend dev:frontend",
"dev:backend": "nodemon app/server.js",
"dev:backend": "nodemon --ignore 'app/client/**/*' app/server.js",
"dev:frontend": "snowpack dev",
"build": "rollup -c",
"build:watch": "rollup -cw",