@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.
@@ -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"}