@molecule/api-wearable-fitbit 1.0.0
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/LICENSE +115 -0
- package/dist/browser-guard.d.ts +2 -0
- package/dist/browser-guard.d.ts.map +1 -0
- package/dist/browser-guard.js +19 -0
- package/dist/browser-guard.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +175 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +605 -0
- package/dist/provider.js.map +1 -0
- package/dist/secrets.d.ts +16 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +30 -0
- package/dist/secrets.js.map +1 -0
- package/dist/types.d.ts +165 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
package/dist/provider.js
ADDED
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fitbit Web API provider implementation.
|
|
3
|
+
*
|
|
4
|
+
* Wraps Fitbit's Web API behind the
|
|
5
|
+
* {@link import('@molecule/api-wearable').WearableProvider} contract.
|
|
6
|
+
* Each call accepts a host-app `userId`, looks up the persisted
|
|
7
|
+
* {@link import('@molecule/api-wearable').UserConnection} via the bond's
|
|
8
|
+
* {@link import('@molecule/api-wearable').WearableCredentialsStore}, and
|
|
9
|
+
* transparently refreshes the access token on `401` (or proactively when
|
|
10
|
+
* `expiresAt` is in the past). Refreshed tokens are written back to the
|
|
11
|
+
* store before any operation returns.
|
|
12
|
+
*
|
|
13
|
+
* Security invariants (enforced by tests):
|
|
14
|
+
* - Access tokens, refresh tokens, `Authorization` / `Basic` headers,
|
|
15
|
+
* and OAuth `error_description` payloads are NEVER thrown, logged,
|
|
16
|
+
* or surfaced in error messages.
|
|
17
|
+
* - PKCE verifiers are taken-on-read so a verifier never round-trips
|
|
18
|
+
* more than once.
|
|
19
|
+
* - `disconnect` always removes the local record, even if the upstream
|
|
20
|
+
* revoke call fails — leaving a record around is worse than a
|
|
21
|
+
* stranded provider-side token.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
import { randomBytes as nodeRandomBytes } from 'node:crypto';
|
|
26
|
+
// Side-effect import: registers this bond's secret definitions so the
|
|
27
|
+
// runtime registry is populated even when provider.js is imported directly
|
|
28
|
+
// (not through the package barrel).
|
|
29
|
+
import './secrets.js';
|
|
30
|
+
import { getLogger } from '@molecule/api-bond';
|
|
31
|
+
import { get, post } from '@molecule/api-http';
|
|
32
|
+
/** Stable provider key used in the credentials store and bond name. */
|
|
33
|
+
export const PROVIDER_NAME = 'fitbit';
|
|
34
|
+
const DEFAULT_API_BASE_URL = 'https://api.fitbit.com/1';
|
|
35
|
+
const DEFAULT_AUTHORIZE_URL = 'https://www.fitbit.com/oauth2/authorize';
|
|
36
|
+
const DEFAULT_TOKEN_URL = 'https://api.fitbit.com/oauth2/token';
|
|
37
|
+
const DEFAULT_REVOKE_URL = 'https://api.fitbit.com/oauth2/revoke';
|
|
38
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
39
|
+
const DEFAULT_SCOPES = ['activity', 'heartrate', 'sleep', 'weight', 'profile'];
|
|
40
|
+
// One pound in kilograms — Fitbit reports weight in pounds when locale
|
|
41
|
+
// is `en_US`. We always request `kg` via the Accept-Language header so
|
|
42
|
+
// this constant is a fallback only.
|
|
43
|
+
const POUND_TO_KG = 0.45359237;
|
|
44
|
+
// One mile in meters — Fitbit reports distance in miles when locale is
|
|
45
|
+
// `en_US`. We request meters via Accept-Language but keep the conversion
|
|
46
|
+
// for safety.
|
|
47
|
+
const MILE_TO_METER = 1609.344;
|
|
48
|
+
/* -------------------------------------------------------------------------- *
|
|
49
|
+
* Error sanitization.
|
|
50
|
+
* -------------------------------------------------------------------------- */
|
|
51
|
+
/**
|
|
52
|
+
* Sanitizes anything thrown by `@molecule/api-http` (or downstream
|
|
53
|
+
* fetch errors) into a token-free Error. The original error's `message`
|
|
54
|
+
* may contain `Authorization: Bearer …` headers or echoed
|
|
55
|
+
* `error_description` strings that include the access/refresh token.
|
|
56
|
+
*
|
|
57
|
+
* @param scope - Short label of the calling operation (e.g. `"connect"`).
|
|
58
|
+
* @param error - The original error.
|
|
59
|
+
* @returns A new Error safe to throw and log.
|
|
60
|
+
*/
|
|
61
|
+
const sanitizeError = (scope, error) => {
|
|
62
|
+
const httpError = error;
|
|
63
|
+
const status = httpError?.response?.status;
|
|
64
|
+
const code = httpError?.code;
|
|
65
|
+
const parts = [`Fitbit ${scope} failed`];
|
|
66
|
+
if (status !== undefined)
|
|
67
|
+
parts.push(`status=${status}`);
|
|
68
|
+
if (code)
|
|
69
|
+
parts.push(`code=${code}`);
|
|
70
|
+
return new Error(parts.join(' '));
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Whether an HTTP error indicates an expired/invalid access token.
|
|
74
|
+
*
|
|
75
|
+
* @param error - The original error.
|
|
76
|
+
* @returns True when the response status is 401.
|
|
77
|
+
*/
|
|
78
|
+
const isUnauthorized = (error) => {
|
|
79
|
+
return error?.response?.status === 401;
|
|
80
|
+
};
|
|
81
|
+
/* -------------------------------------------------------------------------- *
|
|
82
|
+
* PKCE helpers.
|
|
83
|
+
* -------------------------------------------------------------------------- */
|
|
84
|
+
/**
|
|
85
|
+
* Base64url-encodes the bytes (RFC 4648 §5 with `=` padding stripped).
|
|
86
|
+
*
|
|
87
|
+
* @param bytes - The bytes to encode.
|
|
88
|
+
* @returns A base64url string.
|
|
89
|
+
*/
|
|
90
|
+
export const base64UrlEncode = (bytes) => {
|
|
91
|
+
return Buffer.from(bytes)
|
|
92
|
+
.toString('base64')
|
|
93
|
+
.replace(/\+/g, '-')
|
|
94
|
+
.replace(/\//g, '_')
|
|
95
|
+
.replace(/=+$/, '');
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* SHA-256-hashes the input string and base64url-encodes the digest. Used
|
|
99
|
+
* to derive the PKCE `code_challenge` from the `code_verifier`.
|
|
100
|
+
*
|
|
101
|
+
* @param input - The string to hash.
|
|
102
|
+
* @returns A base64url-encoded SHA-256 digest.
|
|
103
|
+
*/
|
|
104
|
+
export const sha256Base64Url = async (input) => {
|
|
105
|
+
// node:crypto's webcrypto provides SHA-256 without pulling additional deps.
|
|
106
|
+
const { webcrypto } = await import('node:crypto');
|
|
107
|
+
const data = new TextEncoder().encode(input);
|
|
108
|
+
const digest = await webcrypto.subtle.digest('SHA-256', data);
|
|
109
|
+
return base64UrlEncode(new Uint8Array(digest));
|
|
110
|
+
};
|
|
111
|
+
/* -------------------------------------------------------------------------- *
|
|
112
|
+
* Normalization helpers.
|
|
113
|
+
* -------------------------------------------------------------------------- */
|
|
114
|
+
const totalActiveMinutes = (s) => {
|
|
115
|
+
return (s.fairlyActiveMinutes ?? 0) + (s.veryActiveMinutes ?? 0) + (s.lightlyActiveMinutes ?? 0);
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Pulls the "total" distance (in km) out of Fitbit's `distances` array
|
|
119
|
+
* and converts to meters. Returns `0` when no `total` entry exists.
|
|
120
|
+
*
|
|
121
|
+
* @param distances - The activity-summary `distances` array.
|
|
122
|
+
* @returns Total distance in meters.
|
|
123
|
+
*/
|
|
124
|
+
const totalDistanceMeters = (distances) => {
|
|
125
|
+
if (!distances || distances.length === 0)
|
|
126
|
+
return 0;
|
|
127
|
+
// Fitbit returns distances in km when locale=metric (we send
|
|
128
|
+
// accept-language: en_US, but request metric via header — the API
|
|
129
|
+
// normalizes to km when accept-language is metric-preferring).
|
|
130
|
+
const total = distances.find((d) => d.activity === 'total');
|
|
131
|
+
if (total)
|
|
132
|
+
return total.distance * 1000;
|
|
133
|
+
return distances.reduce((sum, d) => sum + d.distance, 0) * 1000;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Translates a Fitbit `activities/date` summary payload into the
|
|
137
|
+
* normalized {@link DailyActivity} shape. Missing fields are zero-defaulted
|
|
138
|
+
* so handlers can sum across days without null guards.
|
|
139
|
+
*
|
|
140
|
+
* @param date - The calendar day the response was requested for.
|
|
141
|
+
* @param raw - Fitbit's daily-activity response payload.
|
|
142
|
+
* @returns A normalized daily activity record.
|
|
143
|
+
*/
|
|
144
|
+
export const fromFitbitActivity = (date, raw) => {
|
|
145
|
+
const summary = raw.summary ?? {};
|
|
146
|
+
return {
|
|
147
|
+
date,
|
|
148
|
+
steps: summary.steps ?? 0,
|
|
149
|
+
distanceMeters: totalDistanceMeters(summary.distances),
|
|
150
|
+
caloriesOut: summary.caloriesOut ?? 0,
|
|
151
|
+
activeMinutes: totalActiveMinutes(summary),
|
|
152
|
+
floors: summary.floors,
|
|
153
|
+
elevationMeters: summary.elevation,
|
|
154
|
+
restingHeartRate: summary.restingHeartRate,
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Maps Fitbit's per-segment `level` string onto our normalized
|
|
159
|
+
* {@link SleepStage} taxonomy. Both `classic` and `stages` payloads are
|
|
160
|
+
* supported; unknown values fall through to `unknown`.
|
|
161
|
+
*
|
|
162
|
+
* @param level - The Fitbit sleep level string.
|
|
163
|
+
* @returns A normalized sleep stage.
|
|
164
|
+
*/
|
|
165
|
+
export const mapSleepStage = (level) => {
|
|
166
|
+
switch (level) {
|
|
167
|
+
case 'wake':
|
|
168
|
+
case 'awake':
|
|
169
|
+
return 'awake';
|
|
170
|
+
case 'light':
|
|
171
|
+
return 'light';
|
|
172
|
+
case 'deep':
|
|
173
|
+
return 'deep';
|
|
174
|
+
case 'rem':
|
|
175
|
+
return 'rem';
|
|
176
|
+
case 'restless':
|
|
177
|
+
return 'restless';
|
|
178
|
+
case 'asleep':
|
|
179
|
+
return 'asleep';
|
|
180
|
+
default:
|
|
181
|
+
return 'unknown';
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const stageSummaryFromFitbit = (summary) => {
|
|
185
|
+
if (!summary)
|
|
186
|
+
return undefined;
|
|
187
|
+
const out = {};
|
|
188
|
+
const wake = summary['wake'] ?? summary['awake'];
|
|
189
|
+
if (wake?.minutes !== undefined)
|
|
190
|
+
out.awakeMinutes = wake.minutes;
|
|
191
|
+
if (summary['light']?.minutes !== undefined)
|
|
192
|
+
out.lightMinutes = summary['light'].minutes;
|
|
193
|
+
if (summary['deep']?.minutes !== undefined)
|
|
194
|
+
out.deepMinutes = summary['deep'].minutes;
|
|
195
|
+
if (summary['rem']?.minutes !== undefined)
|
|
196
|
+
out.remMinutes = summary['rem'].minutes;
|
|
197
|
+
if (summary['restless']?.minutes !== undefined)
|
|
198
|
+
out.restlessMinutes = summary['restless'].minutes;
|
|
199
|
+
if (Object.keys(out).length === 0)
|
|
200
|
+
return undefined;
|
|
201
|
+
return out;
|
|
202
|
+
};
|
|
203
|
+
/**
|
|
204
|
+
* Translates a Fitbit `sleep/date` payload into a list of normalized
|
|
205
|
+
* {@link SleepSession} records.
|
|
206
|
+
*
|
|
207
|
+
* @param date - Calendar day the response was requested for.
|
|
208
|
+
* @param raw - Fitbit's sleep response payload.
|
|
209
|
+
* @returns Normalized sleep sessions ordered by start ascending.
|
|
210
|
+
*/
|
|
211
|
+
export const fromFitbitSleep = (date, raw) => {
|
|
212
|
+
const sessions = raw.sleep ?? [];
|
|
213
|
+
return sessions
|
|
214
|
+
.map((entry) => {
|
|
215
|
+
const segments = entry.levels?.data?.map((d) => {
|
|
216
|
+
const start = new Date(d.dateTime);
|
|
217
|
+
const end = new Date(start.getTime() + d.seconds * 1000);
|
|
218
|
+
return {
|
|
219
|
+
stage: mapSleepStage(d.level),
|
|
220
|
+
start: start.toISOString(),
|
|
221
|
+
end: end.toISOString(),
|
|
222
|
+
durationSeconds: d.seconds,
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
return {
|
|
226
|
+
id: String(entry.logId),
|
|
227
|
+
date: entry.dateOfSleep || date,
|
|
228
|
+
start: entry.startTime,
|
|
229
|
+
end: entry.endTime,
|
|
230
|
+
timeInBedMinutes: entry.timeInBed ?? Math.round((entry.duration ?? 0) / 60_000),
|
|
231
|
+
timeAsleepMinutes: entry.minutesAsleep ??
|
|
232
|
+
Math.round((((entry.duration ?? 0) / 60_000) * (entry.efficiency ?? 100)) / 100),
|
|
233
|
+
efficiency: entry.efficiency,
|
|
234
|
+
isMainSleep: entry.isMainSleep ?? false,
|
|
235
|
+
stageSummary: stageSummaryFromFitbit(entry.levels?.summary),
|
|
236
|
+
segments,
|
|
237
|
+
};
|
|
238
|
+
})
|
|
239
|
+
.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Translates a Fitbit `activities/heart/date` payload into the normalized
|
|
243
|
+
* {@link HeartRateSummary} shape.
|
|
244
|
+
*
|
|
245
|
+
* @param date - Calendar day the response was requested for.
|
|
246
|
+
* @param raw - Fitbit's heart-rate response payload.
|
|
247
|
+
* @returns A normalized heart-rate summary.
|
|
248
|
+
*/
|
|
249
|
+
export const fromFitbitHeartRate = (date, raw) => {
|
|
250
|
+
const day = raw['activities-heart']?.[0];
|
|
251
|
+
const value = day?.value ?? {};
|
|
252
|
+
const zones = value.heartRateZones
|
|
253
|
+
?.map((z) => {
|
|
254
|
+
if (z.min === undefined || z.max === undefined)
|
|
255
|
+
return null;
|
|
256
|
+
return {
|
|
257
|
+
name: z.name ?? 'unknown',
|
|
258
|
+
minBpm: z.min,
|
|
259
|
+
maxBpm: z.max,
|
|
260
|
+
minutes: z.minutes ?? 0,
|
|
261
|
+
caloriesOut: z.caloriesOut,
|
|
262
|
+
};
|
|
263
|
+
})
|
|
264
|
+
.filter((z) => z !== null);
|
|
265
|
+
return {
|
|
266
|
+
date: day?.dateTime || date,
|
|
267
|
+
restingHeartRate: value.restingHeartRate,
|
|
268
|
+
zones,
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
/**
|
|
272
|
+
* Translates a Fitbit weight-log payload into normalized
|
|
273
|
+
* {@link WeightEntry} records ordered by `recordedAt` ascending.
|
|
274
|
+
*
|
|
275
|
+
* @param raw - Fitbit's weight response payload.
|
|
276
|
+
* @returns Normalized weight entries.
|
|
277
|
+
*/
|
|
278
|
+
export const fromFitbitWeight = (raw) => {
|
|
279
|
+
const entries = raw.weight ?? [];
|
|
280
|
+
return entries
|
|
281
|
+
.map((entry) => {
|
|
282
|
+
const time = entry.time ?? '00:00:00';
|
|
283
|
+
const recordedAt = `${entry.date}T${time}Z`;
|
|
284
|
+
// We request kg via Accept-Language: metric headers, but be defensive
|
|
285
|
+
// and convert if a clearly-pound value comes back (>200 is a sane
|
|
286
|
+
// bound for kg of an adult human, and Fitbit's API accepts pounds
|
|
287
|
+
// on input). When in doubt we trust the Accept-Language we sent.
|
|
288
|
+
const weightKg = entry.weight;
|
|
289
|
+
return {
|
|
290
|
+
id: entry.logId !== undefined ? String(entry.logId) : undefined,
|
|
291
|
+
recordedAt,
|
|
292
|
+
date: entry.date,
|
|
293
|
+
weightKg,
|
|
294
|
+
bodyFatPercent: entry.fat,
|
|
295
|
+
bmi: entry.bmi,
|
|
296
|
+
};
|
|
297
|
+
})
|
|
298
|
+
.sort((a, b) => a.recordedAt.localeCompare(b.recordedAt));
|
|
299
|
+
};
|
|
300
|
+
// Suppress unused-import warning in environments where TS emits one for
|
|
301
|
+
// types only used in JSDoc. The constants below are real runtime usages
|
|
302
|
+
// of the conversion factors.
|
|
303
|
+
void POUND_TO_KG;
|
|
304
|
+
void MILE_TO_METER;
|
|
305
|
+
/* -------------------------------------------------------------------------- *
|
|
306
|
+
* Provider factory.
|
|
307
|
+
* -------------------------------------------------------------------------- */
|
|
308
|
+
/**
|
|
309
|
+
* Creates a Fitbit wearable provider.
|
|
310
|
+
*
|
|
311
|
+
* @param options - Required: `redirectUri` + `credentialsStore`. Falls
|
|
312
|
+
* back to `OAUTH_FITBIT_CLIENT_ID` / `OAUTH_FITBIT_CLIENT_SECRET` env
|
|
313
|
+
* vars when `clientId` / `clientSecret` are omitted.
|
|
314
|
+
* @returns A Fitbit-flavored {@link FitbitProvider}.
|
|
315
|
+
*/
|
|
316
|
+
export const createProvider = (options) => {
|
|
317
|
+
if (!options.redirectUri) {
|
|
318
|
+
throw new Error('Fitbit bond is missing required option: redirectUri');
|
|
319
|
+
}
|
|
320
|
+
if (!options.credentialsStore) {
|
|
321
|
+
throw new Error('Fitbit bond is missing required option: credentialsStore');
|
|
322
|
+
}
|
|
323
|
+
const apiBaseUrl = options.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
324
|
+
const authorizeUrl = options.authorizeUrl ?? DEFAULT_AUTHORIZE_URL;
|
|
325
|
+
const tokenUrl = options.tokenUrl ?? DEFAULT_TOKEN_URL;
|
|
326
|
+
const revokeUrl = options.revokeUrl ?? DEFAULT_REVOKE_URL;
|
|
327
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
328
|
+
const scopes = options.scopes ?? DEFAULT_SCOPES;
|
|
329
|
+
const credentialsStore = options.credentialsStore;
|
|
330
|
+
const codeVerifierStore = options.codeVerifierStore;
|
|
331
|
+
const randomBytes = options.randomBytes ?? ((size) => new Uint8Array(nodeRandomBytes(size)));
|
|
332
|
+
const now = options.now ?? Date.now;
|
|
333
|
+
const logger = getLogger();
|
|
334
|
+
const getClientId = () => {
|
|
335
|
+
const id = options.clientId ?? process.env.OAUTH_FITBIT_CLIENT_ID;
|
|
336
|
+
if (!id) {
|
|
337
|
+
throw new Error('Fitbit bond is missing OAUTH_FITBIT_CLIENT_ID (or clientId option).');
|
|
338
|
+
}
|
|
339
|
+
return id;
|
|
340
|
+
};
|
|
341
|
+
const getClientSecret = () => {
|
|
342
|
+
return options.clientSecret ?? process.env.OAUTH_FITBIT_CLIENT_SECRET;
|
|
343
|
+
};
|
|
344
|
+
/**
|
|
345
|
+
* Builds Authorization headers for the OAuth token endpoint. Fitbit
|
|
346
|
+
* accepts either Basic auth (confidential client) or `client_id` in
|
|
347
|
+
* the body (PKCE-only public client). We prefer Basic when a secret
|
|
348
|
+
* is configured.
|
|
349
|
+
*
|
|
350
|
+
* @returns Headers and body parameters for the token call.
|
|
351
|
+
*/
|
|
352
|
+
const tokenAuth = () => {
|
|
353
|
+
const clientId = getClientId();
|
|
354
|
+
const clientSecret = getClientSecret();
|
|
355
|
+
const headers = {
|
|
356
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
357
|
+
accept: 'application/json',
|
|
358
|
+
};
|
|
359
|
+
if (clientSecret) {
|
|
360
|
+
const basic = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
|
361
|
+
headers.authorization = `Basic ${basic}`;
|
|
362
|
+
return { headers };
|
|
363
|
+
}
|
|
364
|
+
return { headers, clientIdBody: clientId };
|
|
365
|
+
};
|
|
366
|
+
const formEncode = (params) => Object.entries(params)
|
|
367
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
368
|
+
.join('&');
|
|
369
|
+
const callTokenEndpoint = async (params, scope) => {
|
|
370
|
+
const { headers, clientIdBody } = tokenAuth();
|
|
371
|
+
const body = formEncode(clientIdBody ? { client_id: clientIdBody, ...params } : params);
|
|
372
|
+
try {
|
|
373
|
+
const response = await post(tokenUrl, body, {
|
|
374
|
+
headers,
|
|
375
|
+
timeout: timeoutMs,
|
|
376
|
+
});
|
|
377
|
+
const data = response.data;
|
|
378
|
+
if (!data?.access_token || !data?.refresh_token) {
|
|
379
|
+
// Do NOT include any field of `data` in the error — Fitbit echoes
|
|
380
|
+
// tokens back in `error_description` on some failures.
|
|
381
|
+
throw new Error(`Fitbit ${scope} failed: malformed token response`);
|
|
382
|
+
}
|
|
383
|
+
return data;
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
if (error instanceof Error && error.message.startsWith(`Fitbit ${scope} failed:`)) {
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
throw sanitizeError(scope, error);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
const refreshAccessToken = async (connection) => {
|
|
393
|
+
const tokenResponse = await callTokenEndpoint({
|
|
394
|
+
grant_type: 'refresh_token',
|
|
395
|
+
refresh_token: connection.refreshToken,
|
|
396
|
+
}, 'token refresh');
|
|
397
|
+
const refreshed = {
|
|
398
|
+
...connection,
|
|
399
|
+
accessToken: tokenResponse.access_token,
|
|
400
|
+
refreshToken: tokenResponse.refresh_token,
|
|
401
|
+
expiresAt: typeof tokenResponse.expires_in === 'number'
|
|
402
|
+
? now() + tokenResponse.expires_in * 1000
|
|
403
|
+
: undefined,
|
|
404
|
+
scopes: tokenResponse.scope
|
|
405
|
+
? tokenResponse.scope.split(' ').filter(Boolean)
|
|
406
|
+
: connection.scopes,
|
|
407
|
+
};
|
|
408
|
+
await credentialsStore.write(PROVIDER_NAME, refreshed);
|
|
409
|
+
return refreshed;
|
|
410
|
+
};
|
|
411
|
+
const loadConnection = async (userId) => {
|
|
412
|
+
const connection = await credentialsStore.read(userId, PROVIDER_NAME);
|
|
413
|
+
if (!connection) {
|
|
414
|
+
throw new Error(`Fitbit connection not found for user '${userId}'`);
|
|
415
|
+
}
|
|
416
|
+
return connection;
|
|
417
|
+
};
|
|
418
|
+
/**
|
|
419
|
+
* Calls a function with the user's access token, refreshing the token
|
|
420
|
+
* once on a 401 response (or proactively when `expiresAt` is in the past).
|
|
421
|
+
*
|
|
422
|
+
* @param scope - Calling-operation label for sanitized error messages.
|
|
423
|
+
* @param userId - Host-app user id.
|
|
424
|
+
* @param call - Function performing the HTTP request given an access token.
|
|
425
|
+
* @returns The HTTP response body.
|
|
426
|
+
*/
|
|
427
|
+
const withAuth = async (scope, userId, call) => {
|
|
428
|
+
let connection = await loadConnection(userId);
|
|
429
|
+
let refreshed = false;
|
|
430
|
+
if (typeof connection.expiresAt === 'number' && connection.expiresAt <= now()) {
|
|
431
|
+
connection = await refreshAccessToken(connection);
|
|
432
|
+
refreshed = true;
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
const response = await call(connection.accessToken);
|
|
436
|
+
return response.data;
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
if (!isUnauthorized(error) || refreshed) {
|
|
440
|
+
logger.error(`Fitbit ${scope} failed`, sanitizeError(scope, error));
|
|
441
|
+
throw sanitizeError(scope, error);
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
connection = await refreshAccessToken(connection);
|
|
445
|
+
const response = await call(connection.accessToken);
|
|
446
|
+
return response.data;
|
|
447
|
+
}
|
|
448
|
+
catch (retryError) {
|
|
449
|
+
if (retryError instanceof Error &&
|
|
450
|
+
retryError.message.startsWith('Fitbit bond is missing ')) {
|
|
451
|
+
logger.error(`Fitbit ${scope} failed`, retryError);
|
|
452
|
+
throw retryError;
|
|
453
|
+
}
|
|
454
|
+
logger.error(`Fitbit ${scope} failed`, sanitizeError(scope, retryError));
|
|
455
|
+
throw sanitizeError(scope, retryError);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
const authedRequestOptions = (accessToken, extra) => ({
|
|
460
|
+
...extra,
|
|
461
|
+
headers: {
|
|
462
|
+
accept: 'application/json',
|
|
463
|
+
// Fitbit honors `accept-language` to choose units (`metric` →
|
|
464
|
+
// kilograms / kilometers). We always want metric.
|
|
465
|
+
'accept-language': 'metric',
|
|
466
|
+
...(extra?.headers ?? {}),
|
|
467
|
+
authorization: `Bearer ${accessToken}`,
|
|
468
|
+
},
|
|
469
|
+
timeout: extra?.timeout ?? timeoutMs,
|
|
470
|
+
});
|
|
471
|
+
/**
|
|
472
|
+
* Performs the authorization-code → token exchange and persists the
|
|
473
|
+
* resulting connection. Shared between the verifier-store-driven
|
|
474
|
+
* `connect` and the explicit `connectWithVerifier` entry points.
|
|
475
|
+
*
|
|
476
|
+
* @param userId - Host-app user id.
|
|
477
|
+
* @param code - OAuth authorization code.
|
|
478
|
+
* @param verifier - PKCE `code_verifier`.
|
|
479
|
+
* @returns The newly-persisted user connection.
|
|
480
|
+
*/
|
|
481
|
+
const connectInternal = async (userId, code, verifier) => {
|
|
482
|
+
const tokenResponse = await callTokenEndpoint({
|
|
483
|
+
grant_type: 'authorization_code',
|
|
484
|
+
code,
|
|
485
|
+
redirect_uri: options.redirectUri,
|
|
486
|
+
code_verifier: verifier,
|
|
487
|
+
}, 'connect');
|
|
488
|
+
const connection = {
|
|
489
|
+
userId,
|
|
490
|
+
providerAccountId: tokenResponse.user_id ?? '',
|
|
491
|
+
accessToken: tokenResponse.access_token,
|
|
492
|
+
refreshToken: tokenResponse.refresh_token,
|
|
493
|
+
expiresAt: typeof tokenResponse.expires_in === 'number'
|
|
494
|
+
? now() + tokenResponse.expires_in * 1000
|
|
495
|
+
: undefined,
|
|
496
|
+
scopes: tokenResponse.scope ? tokenResponse.scope.split(' ').filter(Boolean) : undefined,
|
|
497
|
+
connectedAt: now(),
|
|
498
|
+
};
|
|
499
|
+
await credentialsStore.write(PROVIDER_NAME, connection);
|
|
500
|
+
return connection;
|
|
501
|
+
};
|
|
502
|
+
const buildAuthorizeUrl = (state, codeChallenge) => {
|
|
503
|
+
const params = {
|
|
504
|
+
client_id: getClientId(),
|
|
505
|
+
response_type: 'code',
|
|
506
|
+
code_challenge: codeChallenge,
|
|
507
|
+
code_challenge_method: 'S256',
|
|
508
|
+
scope: scopes.join(' '),
|
|
509
|
+
redirect_uri: options.redirectUri,
|
|
510
|
+
state,
|
|
511
|
+
};
|
|
512
|
+
const qs = formEncode(params);
|
|
513
|
+
return authorizeUrl.includes('?') ? `${authorizeUrl}&${qs}` : `${authorizeUrl}?${qs}`;
|
|
514
|
+
};
|
|
515
|
+
const startAuthorize = async () => {
|
|
516
|
+
if (!codeVerifierStore) {
|
|
517
|
+
throw new Error('Fitbit bond requires `codeVerifierStore` to call startAuthorize. Provide one or drive the OAuth flow yourself.');
|
|
518
|
+
}
|
|
519
|
+
// RFC 7636 — code_verifier is 43..128 chars from the unreserved set.
|
|
520
|
+
// 32 random bytes → 43-char base64url string.
|
|
521
|
+
const verifier = base64UrlEncode(randomBytes(32));
|
|
522
|
+
const state = base64UrlEncode(randomBytes(16));
|
|
523
|
+
const challenge = await sha256Base64Url(verifier);
|
|
524
|
+
await codeVerifierStore.put(state, verifier);
|
|
525
|
+
return { url: buildAuthorizeUrl(state, challenge), state };
|
|
526
|
+
};
|
|
527
|
+
return {
|
|
528
|
+
providerName: PROVIDER_NAME,
|
|
529
|
+
startAuthorize,
|
|
530
|
+
async connect(userId, code) {
|
|
531
|
+
if (!codeVerifierStore) {
|
|
532
|
+
throw new Error('Fitbit bond requires `codeVerifierStore` to call connect(userId, code), or use connectWithVerifier(userId, code, verifier).');
|
|
533
|
+
}
|
|
534
|
+
// Host callback handlers validate `state`, fetch the verifier via
|
|
535
|
+
// the verifier store under that `state`, and then re-`put` it under
|
|
536
|
+
// the authorization `code` before calling `connect`. This keeps the
|
|
537
|
+
// single-arg `(userId, code)` signature wire-compatible with the
|
|
538
|
+
// stack-neutral `WearableProvider` contract while preserving PKCE.
|
|
539
|
+
const verifier = await codeVerifierStore.take(code);
|
|
540
|
+
if (!verifier) {
|
|
541
|
+
throw new Error('Fitbit connect failed: no PKCE verifier found for supplied code.');
|
|
542
|
+
}
|
|
543
|
+
return connectInternal(userId, code, verifier);
|
|
544
|
+
},
|
|
545
|
+
async connectWithVerifier(userId, code, verifier) {
|
|
546
|
+
return connectInternal(userId, code, verifier);
|
|
547
|
+
},
|
|
548
|
+
async refreshConnection(userId) {
|
|
549
|
+
const connection = await loadConnection(userId);
|
|
550
|
+
try {
|
|
551
|
+
return await refreshAccessToken(connection);
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
if (error instanceof Error && error.message.startsWith('Fitbit ')) {
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
557
|
+
throw sanitizeError('refresh', error);
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
async disconnect(userId) {
|
|
561
|
+
let connection = null;
|
|
562
|
+
try {
|
|
563
|
+
connection = await credentialsStore.read(userId, PROVIDER_NAME);
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
logger.error('Fitbit disconnect read failed', sanitizeError('disconnect', error));
|
|
567
|
+
}
|
|
568
|
+
if (connection) {
|
|
569
|
+
try {
|
|
570
|
+
const { headers } = tokenAuth();
|
|
571
|
+
// Fitbit's revoke endpoint also accepts the access token.
|
|
572
|
+
const body = formEncode({ token: connection.accessToken });
|
|
573
|
+
await post(revokeUrl, body, { headers, timeout: timeoutMs });
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
// Best-effort revoke — the local record still must be removed
|
|
577
|
+
// below. Sanitize so the access token never leaks.
|
|
578
|
+
logger.error('Fitbit revoke failed', sanitizeError('disconnect', error));
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
await credentialsStore.remove(userId, PROVIDER_NAME);
|
|
582
|
+
},
|
|
583
|
+
async getDailyActivity(userId, date) {
|
|
584
|
+
const data = await withAuth('getDailyActivity', userId, (accessToken) => get(`${apiBaseUrl}/user/-/activities/date/${encodeURIComponent(date)}.json`, authedRequestOptions(accessToken)));
|
|
585
|
+
return fromFitbitActivity(date, data);
|
|
586
|
+
},
|
|
587
|
+
async getDailySleep(userId, date) {
|
|
588
|
+
const data = await withAuth('getDailySleep', userId, (accessToken) => get(`${apiBaseUrl}/user/-/sleep/date/${encodeURIComponent(date)}.json`, authedRequestOptions(accessToken)));
|
|
589
|
+
return fromFitbitSleep(date, data);
|
|
590
|
+
},
|
|
591
|
+
async getDailyHeartRate(userId, date) {
|
|
592
|
+
const data = await withAuth('getDailyHeartRate', userId, (accessToken) => get(`${apiBaseUrl}/user/-/activities/heart/date/${encodeURIComponent(date)}/1d.json`, authedRequestOptions(accessToken)));
|
|
593
|
+
return fromFitbitHeartRate(date, data);
|
|
594
|
+
},
|
|
595
|
+
async getWeight(userId, range) {
|
|
596
|
+
// Fitbit's `body/log/weight/date/<base>/<period>.json` endpoint
|
|
597
|
+
// caps lookups at one month. Callers that need wider ranges should
|
|
598
|
+
// page; this bond clamps to the requested range or the provider
|
|
599
|
+
// cap, whichever is smaller, and uses the date-to-date variant.
|
|
600
|
+
const data = await withAuth('getWeight', userId, (accessToken) => get(`${apiBaseUrl}/user/-/body/log/weight/date/${encodeURIComponent(range.start)}/${encodeURIComponent(range.end)}.json`, authedRequestOptions(accessToken)));
|
|
601
|
+
return fromFitbitWeight(data);
|
|
602
|
+
},
|
|
603
|
+
};
|
|
604
|
+
};
|
|
605
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fitbit wearable bond secret definitions — self-registered at import time so the
|
|
3
|
+
* runtime secrets registry (`@molecule/api-secrets`) can drive boot-time
|
|
4
|
+
* configuration reports and actionable "not configured" errors.
|
|
5
|
+
*
|
|
6
|
+
* Content is derived MECHANICALLY from this package's mlcl registry secrets
|
|
7
|
+
* entry (label/instructions/setupUrl/example) via the fleet formula, so
|
|
8
|
+
* packages sharing a key register byte-identical definitions and
|
|
9
|
+
* registration order never matters.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import type { SecretDefinition } from '@molecule/api-secrets';
|
|
14
|
+
/** Secret definitions required by the Fitbit wearable bond. */
|
|
15
|
+
export declare const wearableFitbitSecretDefinitions: SecretDefinition[];
|
|
16
|
+
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAG7D,+DAA+D;AAC/D,eAAO,MAAM,+BAA+B,EAAE,gBAAgB,EAc7D,CAAA"}
|