homebridge-peloton 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +3 -8
- package/homebridge-ui/public/copy.js +1 -1
- package/homebridge-ui/public/peloton-banner.png +0 -0
- package/package.json +3 -2
- package/dist/scripts/auth-probe.d.mts +0 -15
- package/dist/scripts/auth-probe.mjs +0 -381
- package/dist/scripts/auth-probe.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to homebridge-peloton are listed here. The format follows Keep a Changelog, and the project follows semantic versioning from 0.1.0-beta.1. The release notes of a version are its section here and nothing else.
|
|
4
4
|
|
|
5
|
+
## 1.0.2 (2026-10-18)
|
|
6
|
+
|
|
7
|
+
A packaging and copy release, prepared as the plugin was verified by Homebridge (homebridge/plugins#1239). The developer auth probe stays out of the npm package, and nothing in the package description or the banner promises heart-rate zone triggers, which 1.0.x does not offer. No runtime code changes.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- The developer auth probe is no longer published to npm, at the suggestion of the Homebridge verification review (homebridge/plugins#1239). `"!dist/scripts"` in the `files` array of package.json keeps `auth-probe.mjs` and its `.d.ts` and `.map` files out of the package. The source stays in `src/scripts/` and `npm run build` still compiles it, so the probe runs from a clone. A package suite (`test/package.test.mjs`) runs `npm pack --dry-run --json` and fails if any path under `dist/scripts/` is in the package, if `dist/index.js`, `config.schema.json`, `homebridge-ui/server.js`, `homebridge-ui/public/index.html`, `README.md`, `LICENSE` or `CHANGELOG.md` is missing, or if any path is outside the files list.
|
|
12
|
+
- README, Auth probe: the section opens by saying the probe is not part of the npm package and runs only from a clone of this repository.
|
|
13
|
+
- Copy clarifications so nothing promises heart-rate zone triggers, which 1.0.x does not offer. The package.json description reads "Peloton workouts as HomeKit sensors for Homebridge automations: run scenes when a workout starts or ends." The banner tagline reads "HomeKit sensors driven by Peloton workouts: run scenes when a workout starts or ends."; `assets/peloton-banner.png` and `homebridge-ui/public/peloton-banner.png` are regenerated (1280 × 320) on the same layout, colours and type, and `bannerAlt` in copy.js, the README banner alt text and the banner line in design/README.md match. The Daily check-in caption keeps "heart-rate zones", since the check-in does refresh them.
|
|
14
|
+
- SPEC section 14 records the Homebridge verification (October 18, 2026, homebridge/plugins#1239): the Verified by Homebridge badge first in the README badge row, the icon in Homebridge's central plugin list, and a standing rule that the verification requirements stay met in every release. CLAUDE.md carries the same rule.
|
|
15
|
+
- Version 1.0.2 in package.json and the version literals in the tests and the bug report form. SPEC sections 5, 13, 14 and 16 and design/README.md carry the 1.0.2 clarifications.
|
|
16
|
+
- The package suite reads `npm pack --json` from npm 10 through 12. npm 12 prints an object keyed by package name where npm 10 and 11 print an array, which failed the first 1.0.2 release run before anything was published. The build workflow now installs npm@latest as the release workflow does, on the Node 22 and 24 legs; npm 12 refuses to install on Node 20, so that leg keeps its bundled npm 10 and the suite runs against both shapes.
|
|
17
|
+
|
|
5
18
|
## 1.0.1 (2026-09-17)
|
|
6
19
|
|
|
7
20
|
A copy and asset release. The settings page intro matches the README and the 1.0.0 release notes: Heart-rate zone triggers are not offered, so the page no longer says they are. No code changes.
|
package/README.md
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
<img src="assets/peloton-banner.png" alt="Peloton
|
|
2
|
-
|
|
3
|
-
<!--
|
|
4
|
-
verified-by-homebridge: this plugin has not been through Homebridge verification yet.
|
|
5
|
-
Do not claim it. Once the plugin is verified, replace this comment with the badge:
|
|
6
|
-
[](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
|
|
7
|
-
-->
|
|
1
|
+
<img src="assets/peloton-banner.png" alt="Peloton. HomeKit sensors driven by Peloton workouts: run scenes when a workout starts or ends." width="100%">
|
|
8
2
|
|
|
3
|
+
[](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
|
|
9
4
|
[](https://www.npmjs.com/package/homebridge-peloton)
|
|
10
5
|
[](https://www.npmjs.com/package/homebridge-peloton)
|
|
11
6
|
[](LICENSE)
|
|
@@ -182,7 +177,7 @@ The network is always mocked in tests. Fixtures live under `fixtures/`, sanitise
|
|
|
182
177
|
|
|
183
178
|
### Auth probe
|
|
184
179
|
|
|
185
|
-
The probe runs the headless sign-in on its own, without Homebridge, which is the first thing to try when sign-in stops working. Build first, then run from a directory where a token file may be written:
|
|
180
|
+
The probe is not part of the npm package and runs only from a clone of this repository. It runs the headless sign-in on its own, without Homebridge, which is the first thing to try when sign-in stops working. Build first, then run from a directory where a token file may be written:
|
|
186
181
|
|
|
187
182
|
```shell
|
|
188
183
|
npm run build
|
|
@@ -35,7 +35,7 @@ export const FIELD_LABELS = {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
export const PAGE = {
|
|
38
|
-
bannerAlt: 'Peloton. HomeKit sensors driven by Peloton workouts:
|
|
38
|
+
bannerAlt: 'Peloton. HomeKit sensors driven by Peloton workouts: run scenes when a workout starts or ends.',
|
|
39
39
|
lead1: 'Turns Peloton workouts into HomeKit sensors so automations can run when a workout starts or ends.',
|
|
40
40
|
lead2: 'Set up in two steps: connect your Peloton account, then add a trigger for what you want to automate. '
|
|
41
41
|
+ 'Save, restart Homebridge, and use the sensor in a HomeKit automation.',
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"name": "homebridge-peloton",
|
|
3
3
|
"displayName": "Peloton",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "1.0.
|
|
6
|
-
"description": "Peloton workouts as HomeKit sensors for Homebridge automations:
|
|
5
|
+
"version": "1.0.2",
|
|
6
|
+
"description": "Peloton workouts as HomeKit sensors for Homebridge automations: run scenes when a workout starts or ends.",
|
|
7
7
|
"author": "Alex Rodriguez (https://alex-rodriguez.com)",
|
|
8
8
|
"license": "Apache-2.0",
|
|
9
9
|
"homepage": "https://github.com/arodbuilds/homebridge-peloton#readme",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"types": "dist/index.d.ts",
|
|
29
29
|
"files": [
|
|
30
30
|
"dist",
|
|
31
|
+
"!dist/scripts",
|
|
31
32
|
"homebridge-ui/public",
|
|
32
33
|
"homebridge-ui/server.js",
|
|
33
34
|
"config.schema.json",
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Standalone auth probe for the Pi. This is how the headless flow gets its first real run.
|
|
4
|
-
*
|
|
5
|
-
* node dist/scripts/auth-probe.mjs login <email> [--dump <dir>] prompts for the password without echo
|
|
6
|
-
* node dist/scripts/auth-probe.mjs refresh
|
|
7
|
-
* node dist/scripts/auth-probe.mjs browser prints the URL, waits for the pasted callback URL
|
|
8
|
-
* node dist/scripts/auth-probe.mjs me
|
|
9
|
-
* node dist/scripts/auth-probe.mjs workout [--keys] --keys also prints the raw field names of the workout
|
|
10
|
-
* node dist/scripts/auth-probe.mjs graph <workoutId> prints metric slugs, sample counts, and heart-rate zone bounds
|
|
11
|
-
*
|
|
12
|
-
* Tokens live in ./probe-tokens.json with mode 0600. Nothing here prints a token.
|
|
13
|
-
* On failure the probe prints the stage and HTTP status only.
|
|
14
|
-
*/
|
|
15
|
-
export {};
|
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Standalone auth probe for the Pi. This is how the headless flow gets its first real run.
|
|
4
|
-
*
|
|
5
|
-
* node dist/scripts/auth-probe.mjs login <email> [--dump <dir>] prompts for the password without echo
|
|
6
|
-
* node dist/scripts/auth-probe.mjs refresh
|
|
7
|
-
* node dist/scripts/auth-probe.mjs browser prints the URL, waits for the pasted callback URL
|
|
8
|
-
* node dist/scripts/auth-probe.mjs me
|
|
9
|
-
* node dist/scripts/auth-probe.mjs workout [--keys] --keys also prints the raw field names of the workout
|
|
10
|
-
* node dist/scripts/auth-probe.mjs graph <workoutId> prints metric slugs, sample counts, and heart-rate zone bounds
|
|
11
|
-
*
|
|
12
|
-
* Tokens live in ./probe-tokens.json with mode 0600. Nothing here prints a token.
|
|
13
|
-
* On failure the probe prints the stage and HTTP status only.
|
|
14
|
-
*/
|
|
15
|
-
import { chmod, readFile, writeFile } from 'node:fs/promises';
|
|
16
|
-
import { createInterface } from 'node:readline/promises';
|
|
17
|
-
import { ApiError, PELOTON_API_BASE, getLatestWorkout, getMe, getPerformanceGraph } from '../api/peloton-api.js';
|
|
18
|
-
import { AuthError, browserFinish, browserStart, login, refresh } from '../auth/peloton-auth.js';
|
|
19
|
-
const TOKEN_FILE = './probe-tokens.json';
|
|
20
|
-
const REFRESH_AHEAD_MS = 6 * 60 * 60 * 1000;
|
|
21
|
-
const CTRL_C = String.fromCharCode(3);
|
|
22
|
-
const DELETE = String.fromCharCode(127);
|
|
23
|
-
const BACKSPACE = String.fromCharCode(8);
|
|
24
|
-
function parseArgs(argv) {
|
|
25
|
-
const positional = [];
|
|
26
|
-
let dump;
|
|
27
|
-
let keys = false;
|
|
28
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
29
|
-
const arg = argv[index];
|
|
30
|
-
if (arg === '--dump') {
|
|
31
|
-
dump = argv[index + 1];
|
|
32
|
-
index += 1;
|
|
33
|
-
}
|
|
34
|
-
else if (arg === '--keys') {
|
|
35
|
-
keys = true;
|
|
36
|
-
}
|
|
37
|
-
else if (arg !== undefined) {
|
|
38
|
-
positional.push(arg);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return { command: positional.shift(), positional, dump, keys };
|
|
42
|
-
}
|
|
43
|
-
function usage() {
|
|
44
|
-
console.error([
|
|
45
|
-
'Usage:',
|
|
46
|
-
' node dist/scripts/auth-probe.mjs login <email> [--dump <dir>]',
|
|
47
|
-
' node dist/scripts/auth-probe.mjs refresh',
|
|
48
|
-
' node dist/scripts/auth-probe.mjs browser',
|
|
49
|
-
' node dist/scripts/auth-probe.mjs me',
|
|
50
|
-
' node dist/scripts/auth-probe.mjs workout [--keys]',
|
|
51
|
-
' node dist/scripts/auth-probe.mjs graph <workoutId>',
|
|
52
|
-
].join('\n'));
|
|
53
|
-
process.exit(2);
|
|
54
|
-
}
|
|
55
|
-
/* ------------------------------------------------------------------------------------------------
|
|
56
|
-
* Token file
|
|
57
|
-
* ---------------------------------------------------------------------------------------------- */
|
|
58
|
-
async function loadTokens() {
|
|
59
|
-
let text;
|
|
60
|
-
try {
|
|
61
|
-
text = await readFile(TOKEN_FILE, 'utf8');
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
throw new Error(`No ${TOKEN_FILE} yet. Run login or browser first.`);
|
|
65
|
-
}
|
|
66
|
-
const parsed = JSON.parse(text);
|
|
67
|
-
if (typeof parsed.accessToken !== 'string' || typeof parsed.refreshToken !== 'string' || typeof parsed.expiresAt !== 'number') {
|
|
68
|
-
throw new Error(`${TOKEN_FILE} is malformed. Run login or browser again.`);
|
|
69
|
-
}
|
|
70
|
-
return parsed;
|
|
71
|
-
}
|
|
72
|
-
async function saveTokens(tokens) {
|
|
73
|
-
await writeFile(TOKEN_FILE, JSON.stringify(tokens, null, 2) + '\n', { mode: 0o600 });
|
|
74
|
-
await chmod(TOKEN_FILE, 0o600);
|
|
75
|
-
}
|
|
76
|
-
function describeExpiry(tokens) {
|
|
77
|
-
const hours = Math.round((tokens.expiresAt - Date.now()) / 36000) / 100;
|
|
78
|
-
return `access token expires ${new Date(tokens.expiresAt).toISOString()} (in ${hours} h)`;
|
|
79
|
-
}
|
|
80
|
-
/* ------------------------------------------------------------------------------------------------
|
|
81
|
-
* Prompts
|
|
82
|
-
* ---------------------------------------------------------------------------------------------- */
|
|
83
|
-
async function promptVisible(question) {
|
|
84
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
85
|
-
try {
|
|
86
|
-
return (await rl.question(question)).trim();
|
|
87
|
-
}
|
|
88
|
-
finally {
|
|
89
|
-
rl.close();
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
/** Reads a line from the terminal without echoing it. Falls back to a plain read when not a TTY. */
|
|
93
|
-
function promptHidden(question) {
|
|
94
|
-
const stdin = process.stdin;
|
|
95
|
-
process.stdout.write(question);
|
|
96
|
-
if (!stdin.isTTY) {
|
|
97
|
-
return new Promise((resolve) => {
|
|
98
|
-
let buffer = '';
|
|
99
|
-
stdin.setEncoding('utf8');
|
|
100
|
-
stdin.on('data', (chunk) => {
|
|
101
|
-
buffer += chunk;
|
|
102
|
-
const newline = buffer.indexOf('\n');
|
|
103
|
-
if (newline >= 0) {
|
|
104
|
-
stdin.pause();
|
|
105
|
-
resolve(buffer.slice(0, newline).replace(/\r$/, ''));
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
stdin.on('end', () => resolve(buffer.replace(/\r?\n$/, '')));
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
return new Promise((resolve, reject) => {
|
|
112
|
-
let value = '';
|
|
113
|
-
let onData = () => undefined;
|
|
114
|
-
const finish = () => {
|
|
115
|
-
stdin.removeListener('data', onData);
|
|
116
|
-
stdin.setRawMode(false);
|
|
117
|
-
stdin.pause();
|
|
118
|
-
process.stdout.write('\n');
|
|
119
|
-
};
|
|
120
|
-
onData = (chunk) => {
|
|
121
|
-
for (const char of chunk) {
|
|
122
|
-
if (char === '\r' || char === '\n') {
|
|
123
|
-
finish();
|
|
124
|
-
resolve(value);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (char === CTRL_C) {
|
|
128
|
-
finish();
|
|
129
|
-
reject(new Error('Cancelled'));
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
if (char === DELETE || char === BACKSPACE) {
|
|
133
|
-
value = value.slice(0, -1);
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
value += char;
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
stdin.setRawMode(true);
|
|
140
|
-
stdin.resume();
|
|
141
|
-
stdin.setEncoding('utf8');
|
|
142
|
-
stdin.on('data', onData);
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
/* ------------------------------------------------------------------------------------------------
|
|
146
|
-
* Commands
|
|
147
|
-
* ---------------------------------------------------------------------------------------------- */
|
|
148
|
-
async function commandLogin(args) {
|
|
149
|
-
const email = args.positional[0];
|
|
150
|
-
if (email === undefined) {
|
|
151
|
-
usage();
|
|
152
|
-
}
|
|
153
|
-
const password = await promptHidden('Peloton password: ');
|
|
154
|
-
if (password.length === 0) {
|
|
155
|
-
throw new Error('Empty password');
|
|
156
|
-
}
|
|
157
|
-
if (args.dump !== undefined) {
|
|
158
|
-
console.log(`Writing redacted HTML dumps to ${args.dump}`);
|
|
159
|
-
}
|
|
160
|
-
const tokens = await login(email, password, fetch, {
|
|
161
|
-
debugDump: args.dump,
|
|
162
|
-
onLoginPage: (target) => {
|
|
163
|
-
console.log(`Login page parsed: domain ${target.auth0Domain}, tenant ${target.auth0Tenant}, connection ${target.connection}`);
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
await saveTokens(tokens);
|
|
167
|
-
console.log(`Login ok, ${describeExpiry(tokens)}. Tokens saved to ${TOKEN_FILE}.`);
|
|
168
|
-
}
|
|
169
|
-
async function commandRefresh() {
|
|
170
|
-
const stored = await loadTokens();
|
|
171
|
-
const tokens = await refresh(stored.refreshToken);
|
|
172
|
-
await saveTokens({ ...tokens, userId: stored.userId });
|
|
173
|
-
const rotated = tokens.refreshToken !== stored.refreshToken;
|
|
174
|
-
console.log(`Refresh ok, ${describeExpiry(tokens)}. Refresh token ${rotated ? 'rotated' : 'unchanged'}.`);
|
|
175
|
-
}
|
|
176
|
-
async function commandBrowser() {
|
|
177
|
-
const start = browserStart();
|
|
178
|
-
console.log('Open this URL in a browser, sign in, then paste the full address the browser lands on:');
|
|
179
|
-
console.log('');
|
|
180
|
-
console.log(start.authorizeUrl);
|
|
181
|
-
console.log('');
|
|
182
|
-
const callbackUrl = await promptVisible('Callback URL: ');
|
|
183
|
-
const tokens = await browserFinish(callbackUrl, start.verifier, start.state);
|
|
184
|
-
await saveTokens(tokens);
|
|
185
|
-
console.log(`Browser sign-in ok, ${describeExpiry(tokens)}. Tokens saved to ${TOKEN_FILE}.`);
|
|
186
|
-
}
|
|
187
|
-
/** Runs fn with a valid access token, refreshing when close to expiry or on 401, like the account store. */
|
|
188
|
-
async function withToken(fn) {
|
|
189
|
-
let stored = await loadTokens();
|
|
190
|
-
if (stored.expiresAt - Date.now() <= REFRESH_AHEAD_MS) {
|
|
191
|
-
console.log('Access token is within 6 hours of expiry, refreshing first.');
|
|
192
|
-
stored = await refreshStored(stored);
|
|
193
|
-
}
|
|
194
|
-
try {
|
|
195
|
-
return await fn(stored.accessToken);
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
if (!(error instanceof ApiError) || error.status !== 401) {
|
|
199
|
-
throw error;
|
|
200
|
-
}
|
|
201
|
-
console.log('HTTP 401, refreshing and retrying once.');
|
|
202
|
-
}
|
|
203
|
-
stored = await refreshStored(stored);
|
|
204
|
-
return fn(stored.accessToken);
|
|
205
|
-
}
|
|
206
|
-
async function refreshStored(stored) {
|
|
207
|
-
const tokens = await refresh(stored.refreshToken);
|
|
208
|
-
const updated = { ...tokens, userId: stored.userId };
|
|
209
|
-
await saveTokens(updated);
|
|
210
|
-
return updated;
|
|
211
|
-
}
|
|
212
|
-
async function resolveUserId(accessToken) {
|
|
213
|
-
const stored = await loadTokens();
|
|
214
|
-
if (stored.userId !== undefined) {
|
|
215
|
-
return stored.userId;
|
|
216
|
-
}
|
|
217
|
-
const me = await getMe(accessToken);
|
|
218
|
-
await saveTokens({ ...stored, userId: me.id });
|
|
219
|
-
return me.id;
|
|
220
|
-
}
|
|
221
|
-
async function commandMe() {
|
|
222
|
-
await withToken(async (accessToken) => {
|
|
223
|
-
const me = await getMe(accessToken);
|
|
224
|
-
const stored = await loadTokens();
|
|
225
|
-
await saveTokens({ ...stored, userId: me.id });
|
|
226
|
-
console.log(`User id: ${me.id}`);
|
|
227
|
-
console.log(`Username: ${me.username}`);
|
|
228
|
-
console.log(`Name: ${me.firstName} ${me.lastName}`);
|
|
229
|
-
console.log(`Default avatar: ${me.isProfileImageDefault}`);
|
|
230
|
-
console.log(`Customised heart rate zones: ${me.customizedHeartRateZones.length}`);
|
|
231
|
-
for (const zone of me.customizedHeartRateZones) {
|
|
232
|
-
console.log(` zone ${zone.zone} (${zone.slug}): ${zone.minValue} to ${zone.maxValue}`);
|
|
233
|
-
}
|
|
234
|
-
console.log(`Max heart rate: default ${me.defaultMaxHeartRate ?? 'none'}, customised ${me.customizedMaxHeartRate ?? 'none'}`);
|
|
235
|
-
console.log(`Paired devices: ${me.pairedDevices.length}`);
|
|
236
|
-
const lastWorkoutAt = me.lastWorkoutAt === null ? 'none' : new Date(me.lastWorkoutAt * 1000).toISOString();
|
|
237
|
-
console.log(`last_workout_at on /api/me (stale, not used by the plugin): ${lastWorkoutAt}`);
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
async function commandWorkout(args) {
|
|
241
|
-
await withToken(async (accessToken) => {
|
|
242
|
-
const userId = await resolveUserId(accessToken);
|
|
243
|
-
const workout = await getLatestWorkout(userId, accessToken);
|
|
244
|
-
if (workout === null) {
|
|
245
|
-
console.log('No workouts yet.');
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
console.log(`Workout id: ${workout.id}`);
|
|
249
|
-
console.log(`Status: ${workout.status}`);
|
|
250
|
-
console.log(`Discipline: ${workout.fitnessDiscipline}`);
|
|
251
|
-
console.log(`Device type: ${workout.deviceType}`);
|
|
252
|
-
console.log(`Workout type: ${workout.workoutType}`);
|
|
253
|
-
console.log(`Title: ${workout.title}`);
|
|
254
|
-
console.log(`Name: ${workout.name}`);
|
|
255
|
-
console.log(`Platform: ${workout.platform}`);
|
|
256
|
-
console.log(`Peloton originated: ${workout.isPelotonOriginatedWorkout}, third-party fit feed import: ${workout.is3pFitFeedWorkout}`);
|
|
257
|
-
if (workout.ride !== null) {
|
|
258
|
-
console.log(`Ride: ${workout.ride.title} (${workout.ride.duration ?? 'unknown'} s, instructor ${workout.ride.instructorId ?? 'none'})`);
|
|
259
|
-
}
|
|
260
|
-
console.log(`Start: ${formatEpoch(workout.startTime)}, end: ${formatEpoch(workout.endTime)}, created: ${formatEpoch(workout.createdAt)}`);
|
|
261
|
-
console.log('Last workout at (from created_at): ' + formatEpoch(workout.createdAt));
|
|
262
|
-
if (args.keys) {
|
|
263
|
-
const keys = await rawWorkoutKeys(userId, accessToken);
|
|
264
|
-
console.log(`Raw workout fields (names only): ${keys.join(', ')}`);
|
|
265
|
-
}
|
|
266
|
-
if (workout.status !== 'IN_PROGRESS') {
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
const graph = await getPerformanceGraph(workout.id, 5, accessToken);
|
|
270
|
-
console.log(`Samples: ${graph.secondsSincePedalingStart.length}, latest at ${graph.secondsSincePedalingStart.at(-1) ?? 'none'} s`);
|
|
271
|
-
if (graph.heartRate === null) {
|
|
272
|
-
console.log('Heart rate metric: absent');
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
console.log(`Heart rate: latest ${graph.heartRate.latestSample ?? 'none'} bpm, ${graph.heartRate.zones.length} zone bounds`);
|
|
276
|
-
for (const zone of graph.heartRate.zones) {
|
|
277
|
-
console.log(` zone ${zone.zone} (${zone.slug}): ${zone.minValue} to ${zone.maxValue}`);
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Prints what the performance graph of one workout carries: every metric slug with its sample
|
|
283
|
-
* count, the heart-rate zone bounds, and the latest heart-rate sample. Settles SPEC items 3 and 8
|
|
284
|
-
* for any workout type by id. Nothing here prints a token.
|
|
285
|
-
*/
|
|
286
|
-
async function commandGraph(args) {
|
|
287
|
-
const workoutId = args.positional[0];
|
|
288
|
-
if (workoutId === undefined) {
|
|
289
|
-
usage();
|
|
290
|
-
}
|
|
291
|
-
await withToken(async (accessToken) => {
|
|
292
|
-
const metrics = await rawMetricSummary(workoutId, accessToken);
|
|
293
|
-
console.log(`Metrics: ${metrics.length}`);
|
|
294
|
-
for (const metric of metrics) {
|
|
295
|
-
console.log(` ${metric.slug}: ${metric.samples} samples${metric.zones === null ? '' : `, ${metric.zones} zone bounds`}`);
|
|
296
|
-
}
|
|
297
|
-
const graph = await getPerformanceGraph(workoutId, 5, accessToken);
|
|
298
|
-
console.log(`Sample offsets: ${graph.secondsSincePedalingStart.length}, latest at ${graph.secondsSincePedalingStart.at(-1) ?? 'none'} s`);
|
|
299
|
-
if (graph.heartRate === null) {
|
|
300
|
-
console.log('Heart rate metric: absent');
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
console.log(`Heart rate: latest ${graph.heartRate.latestSample ?? 'none'} bpm, ${graph.heartRate.zones.length} zone bounds`);
|
|
304
|
-
for (const zone of graph.heartRate.zones) {
|
|
305
|
-
console.log(` ${zone.slug}: min_value ${zone.minValue}, max_value ${zone.maxValue}`);
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
/** Fetches the performance graph raw and returns only each metric's slug, sample count, and zone count. */
|
|
310
|
-
async function rawMetricSummary(workoutId, accessToken) {
|
|
311
|
-
const response = await fetch(`${PELOTON_API_BASE}/api/workout/${encodeURIComponent(workoutId)}/performance_graph?every_n=5`, {
|
|
312
|
-
headers: { authorization: `Bearer ${accessToken}`, 'peloton-platform': 'web', accept: 'application/json' },
|
|
313
|
-
});
|
|
314
|
-
if (!response.ok) {
|
|
315
|
-
throw new ApiError(response.status, '/api/workout/{id}/performance_graph');
|
|
316
|
-
}
|
|
317
|
-
const body = await response.json();
|
|
318
|
-
return (body.metrics ?? []).map((entry) => {
|
|
319
|
-
const metric = entry !== null && typeof entry === 'object' ? entry : {};
|
|
320
|
-
return {
|
|
321
|
-
slug: typeof metric.slug === 'string' ? metric.slug : '(no slug)',
|
|
322
|
-
samples: Array.isArray(metric.values) ? metric.values.length : 0,
|
|
323
|
-
zones: Array.isArray(metric.zones) ? metric.zones.length : null,
|
|
324
|
-
};
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
/** Fetches the latest workout again and returns only the top-level field names. This is what settled SPEC open item 1: no device id field. */
|
|
328
|
-
async function rawWorkoutKeys(userId, accessToken) {
|
|
329
|
-
const response = await fetch(`${PELOTON_API_BASE}/api/user/${encodeURIComponent(userId)}/workouts?limit=1&sort_by=-created`, {
|
|
330
|
-
headers: { authorization: `Bearer ${accessToken}`, 'peloton-platform': 'web', accept: 'application/json' },
|
|
331
|
-
});
|
|
332
|
-
if (!response.ok) {
|
|
333
|
-
throw new ApiError(response.status, '/api/user/{id}/workouts');
|
|
334
|
-
}
|
|
335
|
-
const body = await response.json();
|
|
336
|
-
const first = body.data?.[0];
|
|
337
|
-
return first !== null && typeof first === 'object' ? Object.keys(first).sort() : [];
|
|
338
|
-
}
|
|
339
|
-
function formatEpoch(seconds) {
|
|
340
|
-
return seconds === null ? 'none' : new Date(seconds * 1000).toISOString();
|
|
341
|
-
}
|
|
342
|
-
/* ------------------------------------------------------------------------------------------------
|
|
343
|
-
* Entry
|
|
344
|
-
* ---------------------------------------------------------------------------------------------- */
|
|
345
|
-
async function main() {
|
|
346
|
-
const args = parseArgs(process.argv.slice(2));
|
|
347
|
-
switch (args.command) {
|
|
348
|
-
case 'login':
|
|
349
|
-
return commandLogin(args);
|
|
350
|
-
case 'refresh':
|
|
351
|
-
return commandRefresh();
|
|
352
|
-
case 'browser':
|
|
353
|
-
return commandBrowser();
|
|
354
|
-
case 'me':
|
|
355
|
-
return commandMe();
|
|
356
|
-
case 'workout':
|
|
357
|
-
return commandWorkout(args);
|
|
358
|
-
case 'graph':
|
|
359
|
-
return commandGraph(args);
|
|
360
|
-
default:
|
|
361
|
-
usage();
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
main().catch((error) => {
|
|
365
|
-
if (error instanceof AuthError) {
|
|
366
|
-
const status = error.status === undefined ? 'no HTTP status' : `HTTP ${error.status}`;
|
|
367
|
-
const code = error.code === undefined ? '' : `, code ${error.code}`;
|
|
368
|
-
console.error(`Auth failed at stage ${error.stage} (${status}${code})`);
|
|
369
|
-
}
|
|
370
|
-
else if (error instanceof ApiError) {
|
|
371
|
-
console.error(`API call failed: HTTP ${error.status} on ${error.path}`);
|
|
372
|
-
}
|
|
373
|
-
else if (error instanceof Error) {
|
|
374
|
-
console.error(`Failed: ${error.message}`);
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
console.error('Failed');
|
|
378
|
-
}
|
|
379
|
-
process.exit(1);
|
|
380
|
-
});
|
|
381
|
-
//# sourceMappingURL=auth-probe.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth-probe.mjs","sourceRoot":"","sources":["../../src/scripts/auth-probe.mts"],"names":[],"mappings":"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"}
|