From 4efaca5232baf423f046ef16827726f06ca34ff9 Mon Sep 17 00:00:00 2001 From: Alec Lomas Date: Mon, 2 Nov 2020 18:31:27 -0700 Subject: [PATCH 1/2] :heavy_plus_sign: add dotenv --- package-lock.json | 6 ++++++ package.json | 1 + 2 files changed, 7 insertions(+) diff --git a/package-lock.json b/package-lock.json index 2033e74..fffa6d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1823,6 +1823,12 @@ } } }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "dev": true + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", diff --git a/package.json b/package.json index c155f54..4ec7179 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "xml2js": "^0.4.23" }, "devDependencies": { + "dotenv": "^8.2.0", "eslint": "^7.12.1", "eslint-config-lowmess": "^4.0.0", "husky": "^4.3.0", -- 2.51.2 From 6a73218dfa5af80b50c266d1d03479f4a7ab1719 Mon Sep 17 00:00:00 2001 From: Alec Lomas Date: Mon, 2 Nov 2020 18:32:26 -0700 Subject: [PATCH 2/2] :sparkles: fetch new withings token once every 3 hours should fix #42 --- .github/workflows/update-token.yml | 29 ++++++++++ graphql/resolvers/steps.ts | 54 ------------------ package.json | 3 +- update-withings-token.js | 92 ++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/update-token.yml create mode 100644 update-withings-token.js diff --git a/.github/workflows/update-token.yml b/.github/workflows/update-token.yml new file mode 100644 index 0000000..6ac0ab8 --- /dev/null +++ b/.github/workflows/update-token.yml @@ -0,0 +1,29 @@ +name: Update Withings token + +on: + schedule: + # Run every 3 hours + - cron: '0 */3 * * *' + +jobs: + request: + name: Update token + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + + - name: Get npm cache + uses: actions/cache@v2 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install dependencies with npm + run: npm install + + - name: fetch new token + run: AWS_ACCESS=${{ secrets.AWS_ACCESS }} AWS_SECRET=${{ secrets.AWS_SECRET }} AWS_BUCKET=${{ secrets.AWS_BUCKET }} WITHINGS_CLIENT_ID=${{ secrets.WITHINGS_CLIENT_ID }} WITHINGS_CLIENT_SECRET=${{ secrets.WITHINGS_CLIENT_SECRET }} npm run update-token \ No newline at end of file diff --git a/graphql/resolvers/steps.ts b/graphql/resolvers/steps.ts index 21baec3..018aa1e 100644 --- a/graphql/resolvers/steps.ts +++ b/graphql/resolvers/steps.ts @@ -1,4 +1,3 @@ -import { URLSearchParams } from 'url' import aws from 'aws-sdk' import format from 'date-fns/format' import fetch from '../lib/fetchWithTimeout' @@ -38,58 +37,10 @@ const getWithings = (): Promise => { }) } -const setWithings = ( - config: WithingsConfig -): Promise => { - return new Promise((resolve, reject) => { - s3.putObject( - { - Bucket: process.env.AWS_BUCKET, - Key: 'withings.json', - Body: JSON.stringify(config), - }, - (error, data) => { - if (error) { - logError('S3 write error', error.message) - reject(error) - } else { - resolve(data) - } - } - ) - }) -} - interface Activity { readonly steps: number } -const getNewToken = async (): Promise => { - const { refresh_token } = await getWithings() - - const uri = 'https://wbsapi.withings.net/v2/oauth2' - - const params = new URLSearchParams() - - params.append('grant_type', 'refresh_token') - params.append('client_id', process.env.WITHINGS_CLIENT_ID) - params.append('client_secret', process.env.WITHINGS_CLIENT_SECRET) - params.append('refresh_token', refresh_token) - - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: params.toString(), - } - - const response = await fetch(uri, options) - const data = await response.json() - - await setWithings(data) -} - const getSteps = async (): Promise => { const uri = `https://wbsapi.withings.net/v2/measure?action=getactivity&startdateymd=${format( thirtyDaysAgo(), @@ -107,11 +58,6 @@ const getSteps = async (): Promise => { const response = await fetch(uri, options) const data = await response.json() - if (data.status === 401) { - await getNewToken() - return getSteps() - } - if (!data?.body?.activities) { throw new Error(`Withings did not provide steps data in their response`) } diff --git a/package.json b/package.json index 4ec7179..be00090 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "start": "vercel dev", "deploy": "vercel", "lint-staged": "lint-staged", - "typecheck": "tsc -p tsconfig.json --pretty --noEmit" + "typecheck": "tsc -p tsconfig.json --pretty --noEmit", + "update-token": "node ./update-withings-token" }, "repository": { "type": "git", diff --git a/update-withings-token.js b/update-withings-token.js new file mode 100644 index 0000000..3846384 --- /dev/null +++ b/update-withings-token.js @@ -0,0 +1,92 @@ +// There is some duplication of functionality between this and the steps +// resolver, but it was the easiest/fastest way to get this going with the +// mixed js/ts environment + +const { URLSearchParams } = require('url') +const aws = require('aws-sdk') +const dotenv = require('dotenv') +const fetch = require('node-fetch') + +dotenv.config() + +// eslint-disable-next-line import/no-named-as-default-member +aws.config.update({ + accessKeyId: process.env.AWS_ACCESS, + secretAccessKey: process.env.AWS_SECRET, + region: 'us-west-1', +}) + +const s3 = new aws.S3() + +const getWithings = () => { + return new Promise((resolve, reject) => { + s3.getObject( + { + Bucket: process.env.AWS_BUCKET, + Key: 'withings.json', + }, + (error, data) => { + if (error) { + console.error(error) + reject(error) + } else { + const config = JSON.parse(data.Body.toString()) + resolve(config) + } + } + ) + }) +} + +const setWithings = (config) => { + return new Promise((resolve, reject) => { + s3.putObject( + { + Bucket: process.env.AWS_BUCKET, + Key: 'withings.json', + Body: JSON.stringify(config), + }, + (error, data) => { + if (error) { + console.error(error) + reject(error) + } else { + resolve(data) + } + } + ) + }) +} + +const getNewToken = async () => { + const { refresh_token } = await getWithings() + + const uri = 'https://account.withings.com/oauth2/token' + + const params = new URLSearchParams() + + params.append('grant_type', 'refresh_token') + params.append('client_id', process.env.WITHINGS_CLIENT_ID) + params.append('client_secret', process.env.WITHINGS_CLIENT_SECRET) + params.append('refresh_token', refresh_token) + + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: params.toString(), + } + + const response = await fetch(uri, options) + const data = await response.json() + + if (data.access_token) { + await setWithings(data) + console.log('Token updated successfully!') + } else { + console.warn('Token update unsuccessful :(') + } +} + +getNewToken() -- 2.51.2