These scripts are designed to receive GPS and heart rate data about individual exercise activities from Runkeeper, convert it to GPX format, then upload it to Strava as a new activity.
Running in a NodeJS environment, an Express server is set up to listen for incoming requests over http. The app contains an authentication view where the user can authorise the app to send activities to the Strava account of their choice.
An external service (in this case, Zapier) receives Webhook notifications when a new activity is recorded in Runkeeper.
Zapier then executes a script which sends post
request with a payload of data to this app. The Zapier script (external/zapier.js) uses fetch
to send a payload of JSON to the express server.
The server saves the JSON locally, queues a function to process the data, then responds to Zapier with a relevant status code.
Seconds later, the queued function executes, converting the JSON data to a valid GPX file, then sending it to the authenticated Strava account, creating a new activity in Strava.
You can read more detail about the thinking behind the app on my blog.
- Within your Strava account, create a new API application.
- Ensure the Callback Domain is set to the domain that you intend to host the runkeeper-strava-sync app on.
- Take a note of the Client ID and Client Secret. These will be used in the app's
.env
file.
- Run
npm install
. - Specify the environment variables in a
.env
file in the root folder (see Environment Variables). - Run
npm start
.
This app can easily be deployed to an app service environment like Azure App Service or Heroku.
- Under Apps, create a new connection to Runkeeper
- Authenticate your Runkeeper account, allowing Zapier to listen for new activities
- Create a new Zap with New Activity in Runkeeper as the trigger, and a Code By Zapier action step
- Set up the zap to pass the Runkeeper Data into the code step as variables.
- Each data is passed as a key in an object called
inputData
. - See modules/validate-payload.js for the expected keys. These are camelCased versions of what Zapier calls the data coming from Runkeeper.
- The screenshot below should give you an idea of how this should look when finished.
- Each data is passed as a key in an object called
- Additionally, specify the
API_KEY
from.env
and the url where you hosted this app asapiKey
andapiEndpoint
respectively. - Set up the code step to run the script in external/zapier.js and turn on the zap.
Once you're done, the Zap should look something like this:
Once the app is running, to use the app, you must first authenticate a single Strava account. Visit the url, /strava-auth
to do this, then enter your API_KEY
string. Protecting the OAuth process using the app's key in this way means that the public cannot control which Strava account is authenticated for use by your instance of the app.
Once a Strava account is authenticated, Zapier should be able to send the data in a post
request to the root url (/
). The expected payload is described in json/example.json and modules/validate-payload.js. Again, this must be accompanied by the key (specified in the Zapier step above) to work.
With the Zap on, you should find that a new Runkeeper actvity triggers the zap, sends the data to this app, and results in a new activity being created in the authenticated Strava account.
process.ENV Variable | Description |
---|---|
PORT | The port to run the application on. Defaults to 3000 if not specified. |
API_KEY | A random, long string of your choosing, used as a password to keep the application private to you. |
STRAVA_CLIENT_ID | An integer which is given to you when you create an API application within your Strava account. |
STRAVA_CLIENT_SECRET | The string which is given to you as a secret key, associated with the Strava app you created. |
The following describes the folder structure of this application:
.
├── external # Related scripts which run outside of the main app
├── gpx # GPX files are created here before sending to Strava
├── json # JSON files are created here when the app receives valid data
├── logs # Errors are recorded here to help troubleshoot failed processing
├── modules # The main scripts and functions used by the app to process the data
├── strava # Credentials data store for the currently-authenticated Strava user
├── views # EJS template files for the Strava Authentication views
├── .env.example # Example of a valid .env file
├── .gitignore
├── index.js # App entry point and Express server code.
├── package-lock.json
├── package.json
├── LICENSE
└── README.md
Included in the repository is a batch upload script external/batch-upload-gpx.js designed for CLI use. This script allows you to batch upload multiple GPX files to Strava in one go.
Note that you must authenticate the main app to connect to a Strava account before using the script. You can do this by starting the main app and visiting /strava-auth
in your browser.
Example usage:
$ cd external
$ node batch-upload-gpx.js /path/to/directory_containing_gpx_files
Within the repo there is an example of an external script which can be run to "ping" the main app. The purpose of this is to keep the app "awake" when it is hosted in a volatile environment.
I personally set this app up on a free instance on Microsoft Azure App Service, and also using a free-tier Zapier account, which has a code runtime limit of just 1000ms.
Since the app is only used 1-2 times per day maximum, in order to ensure the app always responds quickly enough to Zapier whenever I record a new exercise activity, I set up this script to occasionally "ping" the app to keep it in memory at Azure, and keep the response time low.
The script external/runkeeper-strava-sync-ping.js is an example of how this "ping" can be automated using a cron job in a serverless code environment. In my case I've used Azure Functions to run this script.