wires strava authorization to the web ui
This commit is contained in:
parent
15cdf2e22f
commit
dfc17c09b2
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"background_color": "#0059B3",
|
||||
"background_color": "#002b57",
|
||||
"display": "fullscreen",
|
||||
"orientation": "any",
|
||||
"start_url": "/#:standalone:"
|
||||
"start_url": "/?mode=standalone"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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() })
|
||||
})
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue