whoopper 0.1.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.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +176 -0
  3. package/dist/auth/auth-server.d.ts +15 -0
  4. package/dist/auth/auth-server.d.ts.map +1 -0
  5. package/dist/auth/auth-server.js +127 -0
  6. package/dist/auth/auth-server.js.map +1 -0
  7. package/dist/auth/oauth-provider.d.ts +17 -0
  8. package/dist/auth/oauth-provider.d.ts.map +1 -0
  9. package/dist/auth/oauth-provider.js +139 -0
  10. package/dist/auth/oauth-provider.js.map +1 -0
  11. package/dist/auth/token-info.d.ts +21 -0
  12. package/dist/auth/token-info.d.ts.map +1 -0
  13. package/dist/auth/token-info.js +35 -0
  14. package/dist/auth/token-info.js.map +1 -0
  15. package/dist/auth/token-store.d.ts +21 -0
  16. package/dist/auth/token-store.d.ts.map +1 -0
  17. package/dist/auth/token-store.js +75 -0
  18. package/dist/auth/token-store.js.map +1 -0
  19. package/dist/auth/types.d.ts +42 -0
  20. package/dist/auth/types.d.ts.map +1 -0
  21. package/dist/auth/types.js +2 -0
  22. package/dist/auth/types.js.map +1 -0
  23. package/dist/client.d.ts +27 -0
  24. package/dist/client.d.ts.map +1 -0
  25. package/dist/client.js +68 -0
  26. package/dist/client.js.map +1 -0
  27. package/dist/errors/auth.d.ts +11 -0
  28. package/dist/errors/auth.d.ts.map +1 -0
  29. package/dist/errors/auth.js +20 -0
  30. package/dist/errors/auth.js.map +1 -0
  31. package/dist/errors/base.d.ts +5 -0
  32. package/dist/errors/base.d.ts.map +1 -0
  33. package/dist/errors/base.js +9 -0
  34. package/dist/errors/base.js.map +1 -0
  35. package/dist/errors/config.d.ts +5 -0
  36. package/dist/errors/config.d.ts.map +1 -0
  37. package/dist/errors/config.js +8 -0
  38. package/dist/errors/config.js.map +1 -0
  39. package/dist/errors/http.d.ts +19 -0
  40. package/dist/errors/http.d.ts.map +1 -0
  41. package/dist/errors/http.js +36 -0
  42. package/dist/errors/http.js.map +1 -0
  43. package/dist/errors/index.d.ts +5 -0
  44. package/dist/errors/index.d.ts.map +1 -0
  45. package/dist/errors/index.js +5 -0
  46. package/dist/errors/index.js.map +1 -0
  47. package/dist/http/errors.d.ts +3 -0
  48. package/dist/http/errors.d.ts.map +1 -0
  49. package/dist/http/errors.js +31 -0
  50. package/dist/http/errors.js.map +1 -0
  51. package/dist/http/fetch-client.d.ts +31 -0
  52. package/dist/http/fetch-client.d.ts.map +1 -0
  53. package/dist/http/fetch-client.js +76 -0
  54. package/dist/http/fetch-client.js.map +1 -0
  55. package/dist/http/retry.d.ts +7 -0
  56. package/dist/http/retry.d.ts.map +1 -0
  57. package/dist/http/retry.js +36 -0
  58. package/dist/http/retry.js.map +1 -0
  59. package/dist/http/throttle.d.ts +16 -0
  60. package/dist/http/throttle.d.ts.map +1 -0
  61. package/dist/http/throttle.js +51 -0
  62. package/dist/http/throttle.js.map +1 -0
  63. package/dist/index.d.ts +19 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +27 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/models/achievements.d.ts +13 -0
  68. package/dist/models/achievements.d.ts.map +1 -0
  69. package/dist/models/achievements.js +2 -0
  70. package/dist/models/achievements.js.map +1 -0
  71. package/dist/models/common.d.ts +16 -0
  72. package/dist/models/common.d.ts.map +1 -0
  73. package/dist/models/common.js +7 -0
  74. package/dist/models/common.js.map +1 -0
  75. package/dist/models/cycle.d.ts +19 -0
  76. package/dist/models/cycle.d.ts.map +1 -0
  77. package/dist/models/cycle.js +2 -0
  78. package/dist/models/cycle.js.map +1 -0
  79. package/dist/models/heart-rate.d.ts +10 -0
  80. package/dist/models/heart-rate.d.ts.map +1 -0
  81. package/dist/models/heart-rate.js +2 -0
  82. package/dist/models/heart-rate.js.map +1 -0
  83. package/dist/models/index.d.ts +13 -0
  84. package/dist/models/index.d.ts.map +1 -0
  85. package/dist/models/index.js +3 -0
  86. package/dist/models/index.js.map +1 -0
  87. package/dist/models/internal.d.ts +78 -0
  88. package/dist/models/internal.d.ts.map +1 -0
  89. package/dist/models/internal.js +2 -0
  90. package/dist/models/internal.js.map +1 -0
  91. package/dist/models/recovery.d.ts +19 -0
  92. package/dist/models/recovery.d.ts.map +1 -0
  93. package/dist/models/recovery.js +2 -0
  94. package/dist/models/recovery.js.map +1 -0
  95. package/dist/models/sleep.d.ts +38 -0
  96. package/dist/models/sleep.d.ts.map +1 -0
  97. package/dist/models/sleep.js +2 -0
  98. package/dist/models/sleep.js.map +1 -0
  99. package/dist/models/sports.d.ts +6 -0
  100. package/dist/models/sports.d.ts.map +1 -0
  101. package/dist/models/sports.js +190 -0
  102. package/dist/models/sports.js.map +1 -0
  103. package/dist/models/stress.d.ts +6 -0
  104. package/dist/models/stress.d.ts.map +1 -0
  105. package/dist/models/stress.js +2 -0
  106. package/dist/models/stress.js.map +1 -0
  107. package/dist/models/trends.d.ts +10 -0
  108. package/dist/models/trends.d.ts.map +1 -0
  109. package/dist/models/trends.js +2 -0
  110. package/dist/models/trends.js.map +1 -0
  111. package/dist/models/user.d.ts +12 -0
  112. package/dist/models/user.d.ts.map +1 -0
  113. package/dist/models/user.js +2 -0
  114. package/dist/models/user.js.map +1 -0
  115. package/dist/models/workout.d.ts +33 -0
  116. package/dist/models/workout.d.ts.map +1 -0
  117. package/dist/models/workout.js +2 -0
  118. package/dist/models/workout.js.map +1 -0
  119. package/dist/pagination/paginator.d.ts +11 -0
  120. package/dist/pagination/paginator.d.ts.map +1 -0
  121. package/dist/pagination/paginator.js +38 -0
  122. package/dist/pagination/paginator.js.map +1 -0
  123. package/dist/resources/base.d.ts +17 -0
  124. package/dist/resources/base.d.ts.map +1 -0
  125. package/dist/resources/base.js +37 -0
  126. package/dist/resources/base.js.map +1 -0
  127. package/dist/resources/official/cycle.d.ts +10 -0
  128. package/dist/resources/official/cycle.d.ts.map +1 -0
  129. package/dist/resources/official/cycle.js +13 -0
  130. package/dist/resources/official/cycle.js.map +1 -0
  131. package/dist/resources/official/recovery.d.ts +6 -0
  132. package/dist/resources/official/recovery.d.ts.map +1 -0
  133. package/dist/resources/official/recovery.js +7 -0
  134. package/dist/resources/official/recovery.js.map +1 -0
  135. package/dist/resources/official/sleep.d.ts +6 -0
  136. package/dist/resources/official/sleep.d.ts.map +1 -0
  137. package/dist/resources/official/sleep.js +7 -0
  138. package/dist/resources/official/sleep.js.map +1 -0
  139. package/dist/resources/official/user.d.ts +8 -0
  140. package/dist/resources/official/user.d.ts.map +1 -0
  141. package/dist/resources/official/user.js +13 -0
  142. package/dist/resources/official/user.js.map +1 -0
  143. package/dist/resources/official/workout.d.ts +6 -0
  144. package/dist/resources/official/workout.d.ts.map +1 -0
  145. package/dist/resources/official/workout.js +7 -0
  146. package/dist/resources/official/workout.js.map +1 -0
  147. package/dist/result/result.d.ts +11 -0
  148. package/dist/result/result.d.ts.map +1 -0
  149. package/dist/result/result.js +19 -0
  150. package/dist/result/result.js.map +1 -0
  151. package/dist/utils/conversions.d.ts +4 -0
  152. package/dist/utils/conversions.d.ts.map +1 -0
  153. package/dist/utils/conversions.js +10 -0
  154. package/dist/utils/conversions.js.map +1 -0
  155. package/dist/utils/date.d.ts +6 -0
  156. package/dist/utils/date.d.ts.map +1 -0
  157. package/dist/utils/date.js +19 -0
  158. package/dist/utils/date.js.map +1 -0
  159. package/dist/utils/headers.d.ts +2 -0
  160. package/dist/utils/headers.d.ts.map +1 -0
  161. package/dist/utils/headers.js +8 -0
  162. package/dist/utils/headers.js.map +1 -0
  163. package/dist/utils/index.d.ts +5 -0
  164. package/dist/utils/index.d.ts.map +1 -0
  165. package/dist/utils/index.js +5 -0
  166. package/dist/utils/index.js.map +1 -0
  167. package/dist/utils/sleep-helpers.d.ts +7 -0
  168. package/dist/utils/sleep-helpers.d.ts.map +1 -0
  169. package/dist/utils/sleep-helpers.js +49 -0
  170. package/dist/utils/sleep-helpers.js.map +1 -0
  171. package/package.json +61 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 car1os
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # whoopper
2
+
3
+ The definitive WHOOP API client. Zero runtime dependencies. Full TypeScript types. Every endpoint.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install whoopper
9
+ ```
10
+
11
+ Requires Node.js 18+.
12
+
13
+ ## Quick Start
14
+
15
+ ### With OAuth (Official API)
16
+
17
+ ```typescript
18
+ import { WhooopperClient } from 'whoopper';
19
+
20
+ const client = WhooopperClient.withOAuth({
21
+ clientId: process.env.WHOOP_CLIENT_ID!,
22
+ clientSecret: process.env.WHOOP_CLIENT_SECRET!,
23
+ });
24
+
25
+ await client.authenticate(); // opens browser for OAuth
26
+
27
+ const profile = await client.user.getProfile();
28
+ console.log(`Hello, ${profile.first_name}!`);
29
+ ```
30
+
31
+ ### With Pre-existing Tokens
32
+
33
+ ```typescript
34
+ const client = WhooopperClient.withTokens({
35
+ official: {
36
+ accessToken: 'your-access-token',
37
+ refreshToken: 'your-refresh-token',
38
+ },
39
+ });
40
+
41
+ const cycles = await client.cycle.getAll({ start: '2024-01-01' });
42
+ ```
43
+
44
+ ## Resources
45
+
46
+ All official WHOOP API v2 endpoints are supported:
47
+
48
+ ```typescript
49
+ // User
50
+ await client.user.getProfile();
51
+ await client.user.getBodyMeasurement();
52
+
53
+ // Cycles
54
+ await client.cycle.list({ start: '2024-01-01', end: '2024-02-01' });
55
+ await client.cycle.getById(12345);
56
+ await client.cycle.getRecovery(12345);
57
+ await client.cycle.getSleep(12345);
58
+
59
+ // Recovery
60
+ await client.recovery.list({ start: '2024-01-01' });
61
+
62
+ // Sleep
63
+ await client.sleep.list({ start: '2024-01-01' });
64
+ await client.sleep.getById(12345);
65
+
66
+ // Workouts
67
+ await client.workout.list({ start: '2024-01-01' });
68
+ await client.workout.getById(12345);
69
+ ```
70
+
71
+ ## Pagination
72
+
73
+ Every collection resource supports four pagination strategies:
74
+
75
+ ```typescript
76
+ // Get a single page
77
+ const page = await client.cycle.list({ start: '2024-01-01' });
78
+
79
+ // Get all records (auto-paginates)
80
+ const all = await client.cycle.getAll({ start: '2024-01-01' });
81
+
82
+ // Async iterator (memory-efficient)
83
+ for await (const cycle of client.cycle.iterate({ start: '2024-01-01' })) {
84
+ console.log(cycle.id);
85
+ }
86
+
87
+ // Page-level iterator
88
+ for await (const page of client.cycle.paginator().iteratePages()) {
89
+ console.log(`Got ${page.records.length} records`);
90
+ }
91
+ ```
92
+
93
+ ## Token Storage
94
+
95
+ By default, tokens are stored in memory. For persistence across sessions:
96
+
97
+ ```typescript
98
+ import { WhooopperClient, FileTokenStore } from 'whoopper';
99
+
100
+ const client = WhooopperClient.withOAuth(
101
+ { clientId: '...', clientSecret: '...' },
102
+ { tokenStore: new FileTokenStore('./tokens.json') },
103
+ );
104
+ ```
105
+
106
+ `FileTokenStore` writes with `0600` permissions and warns if they're looser.
107
+
108
+ You can also implement the `TokenStore` interface for custom storage (Redis, database, etc.).
109
+
110
+ ## Utilities
111
+
112
+ Standalone helpers that work on plain API responses:
113
+
114
+ ```typescript
115
+ import { kJToCalories, msToHours, totalSleepTime, sleepEfficiency } from 'whoopper/utils';
116
+
117
+ kJToCalories(1000); // 239
118
+ msToHours(27_000_000); // 7.5
119
+
120
+ // Pass a SleepScore from the API
121
+ totalSleepTime(sleep.score); // total ms of actual sleep
122
+ sleepEfficiency(sleep.score); // percentage
123
+ ```
124
+
125
+ ## Subpath Exports
126
+
127
+ ```typescript
128
+ import { ... } from 'whoopper'; // everything
129
+ import { ... } from 'whoopper/models'; // type interfaces only
130
+ import { ... } from 'whoopper/errors'; // error classes only
131
+ import { ... } from 'whoopper/utils'; // utility functions only
132
+ ```
133
+
134
+ ## Error Handling
135
+
136
+ All errors extend `WhoopError`:
137
+
138
+ | HTTP Status | Error Class | Notes |
139
+ |-------------|-------------|-------|
140
+ | 400 | `ValidationError` | Bad request parameters |
141
+ | 401 | `TokenExpiredError` | Token needs refresh |
142
+ | 403 | `AuthenticationError` | Insufficient permissions |
143
+ | 404 | `NotFoundError` | Resource doesn't exist |
144
+ | 429 | `RateLimitError` | Has `.retryAfter` (seconds) |
145
+ | 5xx | `ServerError` | Has `.statusCode` |
146
+
147
+ Retries are automatic for 429 and 5xx (exponential backoff, respects `Retry-After` header, max 5 attempts).
148
+
149
+ ### Optional Result Type
150
+
151
+ For those who prefer explicit error handling over try/catch:
152
+
153
+ ```typescript
154
+ import { tryCatch } from 'whoopper';
155
+
156
+ const result = await tryCatch(() => client.user.getProfile());
157
+ if (result.ok) {
158
+ console.log(result.value.first_name);
159
+ } else {
160
+ console.error(result.error);
161
+ }
162
+ ```
163
+
164
+ ## Configuration
165
+
166
+ ```typescript
167
+ WhooopperClient.withOAuth(config, {
168
+ tokenStore: new FileTokenStore('./tokens.json'),
169
+ retry: { maxAttempts: 3, baseDelayMs: 2000 },
170
+ throttle: { maxConcurrent: 5, minDelayMs: 100 },
171
+ });
172
+ ```
173
+
174
+ ## License
175
+
176
+ MIT
@@ -0,0 +1,15 @@
1
+ interface AuthServerResult {
2
+ port: number;
3
+ waitForCode: () => Promise<{
4
+ code: string;
5
+ state: string;
6
+ }>;
7
+ waitForTokens: () => Promise<{
8
+ accessToken: string;
9
+ expiresIn: number;
10
+ }>;
11
+ close: () => void;
12
+ }
13
+ export declare function startAuthServer(): Promise<AuthServerResult>;
14
+ export {};
15
+ //# sourceMappingURL=auth-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-server.d.ts","sourceRoot":"","sources":["../../src/auth/auth-server.ts"],"names":[],"mappings":"AAEA,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,aAAa,EAAE,MAAM,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzE,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AA2DD,wBAAgB,eAAe,IAAI,OAAO,CAAC,gBAAgB,CAAC,CA4E3D"}
@@ -0,0 +1,127 @@
1
+ import { createServer } from 'node:http';
2
+ const SUCCESS_HTML = `<!DOCTYPE html>
3
+ <html><head><title>Authentication Successful</title></head>
4
+ <body style="font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#0a0a0a;color:#fff">
5
+ <div style="text-align:center">
6
+ <h1 style="color:#00d1b2">Authentication Successful</h1>
7
+ <p>You can close this tab and return to the terminal.</p>
8
+ </div></body></html>`;
9
+ const LOGIN_HTML = `<!DOCTYPE html>
10
+ <html><head><title>WHOOP Login</title></head>
11
+ <body style="font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#0a0a0a;color:#fff">
12
+ <div style="max-width:400px;width:100%;padding:2rem">
13
+ <h1 style="color:#00d1b2;text-align:center">WHOOP Login</h1>
14
+ <p style="color:#999;text-align:center;font-size:0.9rem">Your password stays in this browser — it is never sent to the library.</p>
15
+ <form id="loginForm" style="display:flex;flex-direction:column;gap:1rem">
16
+ <input name="email" type="email" placeholder="Email" required style="padding:0.75rem;border:1px solid #333;border-radius:8px;background:#1a1a1a;color:#fff;font-size:1rem">
17
+ <input name="password" type="password" placeholder="Password" required style="padding:0.75rem;border:1px solid #333;border-radius:8px;background:#1a1a1a;color:#fff;font-size:1rem">
18
+ <button type="submit" style="padding:0.75rem;border:none;border-radius:8px;background:#00d1b2;color:#000;font-size:1rem;font-weight:bold;cursor:pointer">Log In</button>
19
+ <div id="error" style="color:#ff6b6b;text-align:center;display:none"></div>
20
+ </form>
21
+ </div>
22
+ <script>
23
+ const COGNITO_URL = 'https://api.prod.whoop.com/auth-service/v3/whoop';
24
+ document.getElementById('loginForm').addEventListener('submit', async (e) => {
25
+ e.preventDefault();
26
+ const form = e.target;
27
+ const errorEl = document.getElementById('error');
28
+ errorEl.style.display = 'none';
29
+ try {
30
+ const resp = await fetch(COGNITO_URL, {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/json' },
33
+ body: JSON.stringify({
34
+ username: form.email.value,
35
+ password: form.password.value,
36
+ grant_type: 'password',
37
+ issueRefresh: false,
38
+ }),
39
+ });
40
+ if (!resp.ok) throw new Error('Login failed: ' + resp.status);
41
+ const data = await resp.json();
42
+ await fetch('/token-callback', {
43
+ method: 'POST',
44
+ headers: { 'Content-Type': 'application/json' },
45
+ body: JSON.stringify({
46
+ access_token: data.access_token,
47
+ expires_in: data.expires_in || 3600,
48
+ }),
49
+ });
50
+ document.body.innerHTML = \`${SUCCESS_HTML.replace(/`/g, '\\`').replace(/<\/?html>|<\/?head>|<\/?body>|<title>.*?<\/title>/g, '').trim()}\`;
51
+ } catch (err) {
52
+ errorEl.textContent = err.message;
53
+ errorEl.style.display = 'block';
54
+ }
55
+ });
56
+ </script></body></html>`;
57
+ export function startAuthServer() {
58
+ return new Promise((resolve, reject) => {
59
+ let codeResolve = null;
60
+ let tokenResolve = null;
61
+ const server = createServer((req, res) => {
62
+ const url = new URL(req.url ?? '/', `http://localhost`);
63
+ if (req.method === 'GET' && url.pathname === '/callback') {
64
+ const code = url.searchParams.get('code');
65
+ const state = url.searchParams.get('state');
66
+ if (code && state && codeResolve) {
67
+ res.writeHead(200, { 'Content-Type': 'text/html' });
68
+ res.end(SUCCESS_HTML);
69
+ codeResolve({ code, state });
70
+ }
71
+ else {
72
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
73
+ res.end('Missing code or state parameter');
74
+ }
75
+ return;
76
+ }
77
+ if (req.method === 'GET' && url.pathname === '/login') {
78
+ res.writeHead(200, { 'Content-Type': 'text/html' });
79
+ res.end(LOGIN_HTML);
80
+ return;
81
+ }
82
+ if (req.method === 'POST' && url.pathname === '/token-callback') {
83
+ let body = '';
84
+ req.on('data', (chunk) => { body += chunk.toString(); });
85
+ req.on('end', () => {
86
+ try {
87
+ const data = JSON.parse(body);
88
+ res.writeHead(200, { 'Content-Type': 'application/json' });
89
+ res.end(JSON.stringify({ ok: true }));
90
+ if (tokenResolve) {
91
+ tokenResolve({
92
+ accessToken: data.access_token,
93
+ expiresIn: data.expires_in,
94
+ });
95
+ }
96
+ }
97
+ catch {
98
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
99
+ res.end('Invalid JSON');
100
+ }
101
+ });
102
+ return;
103
+ }
104
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
105
+ res.end('Not found');
106
+ });
107
+ server.listen(0, '127.0.0.1', () => {
108
+ const address = server.address();
109
+ if (!address || typeof address === 'string') {
110
+ reject(new Error('Failed to start auth server'));
111
+ return;
112
+ }
113
+ resolve({
114
+ port: address.port,
115
+ waitForCode: () => new Promise(res => {
116
+ codeResolve = res;
117
+ }),
118
+ waitForTokens: () => new Promise(res => {
119
+ tokenResolve = res;
120
+ }),
121
+ close: () => server.close(),
122
+ });
123
+ });
124
+ server.on('error', reject);
125
+ });
126
+ }
127
+ //# sourceMappingURL=auth-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-server.js","sourceRoot":"","sources":["../../src/auth/auth-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { OAuthConfig } from './types.js';
2
+ import { TokenInfo } from './token-info.js';
3
+ import type { TokenStore } from './token-store.js';
4
+ export declare class OAuthProvider {
5
+ private readonly config;
6
+ private readonly store;
7
+ private tokenInfo;
8
+ constructor(config: OAuthConfig, store: TokenStore);
9
+ authenticate(): Promise<TokenInfo>;
10
+ getAccessToken(): Promise<string>;
11
+ setTokenInfo(tokenInfo: TokenInfo): void;
12
+ private browserAuth;
13
+ private exchangeCode;
14
+ private refresh;
15
+ revoke(): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=oauth-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-provider.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAiBnD,qBAAa,aAAa;IAItB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAJxB,OAAO,CAAC,SAAS,CAA0B;gBAGxB,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,UAAU;IAG9B,YAAY,IAAI,OAAO,CAAC,SAAS,CAAC;IAqBlC,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAgBvC,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;YAI1B,WAAW;YAwCX,YAAY;YA2BZ,OAAO;IAyBf,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAI9B"}
@@ -0,0 +1,139 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { TokenInfo } from './token-info.js';
3
+ import { AuthenticationError, RefreshTokenError } from '../errors/auth.js';
4
+ import { startAuthServer } from './auth-server.js';
5
+ const AUTH_URL = 'https://api.prod.whoop.com/oauth/oauth2/auth';
6
+ const TOKEN_URL = 'https://api.prod.whoop.com/oauth/oauth2/token';
7
+ const STORE_KEY = 'official';
8
+ const DEFAULT_SCOPES = [
9
+ 'read:recovery',
10
+ 'read:cycles',
11
+ 'read:sleep',
12
+ 'read:workout',
13
+ 'read:profile',
14
+ 'read:body_measurement',
15
+ ];
16
+ export class OAuthProvider {
17
+ config;
18
+ store;
19
+ tokenInfo = null;
20
+ constructor(config, store) {
21
+ this.config = config;
22
+ this.store = store;
23
+ }
24
+ async authenticate() {
25
+ // Try loading from store first
26
+ const stored = await this.store.load(STORE_KEY);
27
+ if (stored && !stored.isExpired) {
28
+ this.tokenInfo = stored;
29
+ return stored;
30
+ }
31
+ // Try refresh if we have a refresh token
32
+ if (stored?.refreshToken) {
33
+ try {
34
+ return await this.refresh(stored.refreshToken);
35
+ }
36
+ catch {
37
+ // Refresh failed, fall through to full auth
38
+ }
39
+ }
40
+ // Full browser-based OAuth flow
41
+ return this.browserAuth();
42
+ }
43
+ async getAccessToken() {
44
+ if (!this.tokenInfo) {
45
+ throw new AuthenticationError('Not authenticated. Call authenticate() first.');
46
+ }
47
+ if (this.tokenInfo.isExpired) {
48
+ if (this.tokenInfo.refreshToken) {
49
+ this.tokenInfo = await this.refresh(this.tokenInfo.refreshToken);
50
+ }
51
+ else {
52
+ throw new AuthenticationError('Token expired and no refresh token available.');
53
+ }
54
+ }
55
+ return this.tokenInfo.accessToken;
56
+ }
57
+ setTokenInfo(tokenInfo) {
58
+ this.tokenInfo = tokenInfo;
59
+ }
60
+ async browserAuth() {
61
+ const state = randomBytes(16).toString('hex');
62
+ const scopes = this.config.scopes ?? DEFAULT_SCOPES;
63
+ const { port, waitForCode, close } = await startAuthServer();
64
+ const redirectUri = this.config.redirectUri ?? `http://localhost:${port}/callback`;
65
+ const authUrl = new URL(AUTH_URL);
66
+ authUrl.searchParams.set('client_id', this.config.clientId);
67
+ authUrl.searchParams.set('redirect_uri', redirectUri);
68
+ authUrl.searchParams.set('response_type', 'code');
69
+ authUrl.searchParams.set('scope', scopes.join(' '));
70
+ authUrl.searchParams.set('state', state);
71
+ // Open browser
72
+ const { exec } = await import('node:child_process');
73
+ const openCmd = process.platform === 'darwin'
74
+ ? 'open'
75
+ : process.platform === 'win32'
76
+ ? 'start'
77
+ : 'xdg-open';
78
+ exec(`${openCmd} "${authUrl.toString()}"`);
79
+ try {
80
+ const { code, state: returnedState } = await waitForCode();
81
+ if (returnedState !== state) {
82
+ throw new AuthenticationError('OAuth state mismatch');
83
+ }
84
+ const tokenInfo = await this.exchangeCode(code, redirectUri);
85
+ this.tokenInfo = tokenInfo;
86
+ await this.store.save(STORE_KEY, tokenInfo);
87
+ return tokenInfo;
88
+ }
89
+ finally {
90
+ close();
91
+ }
92
+ }
93
+ async exchangeCode(code, redirectUri) {
94
+ const body = new URLSearchParams({
95
+ grant_type: 'authorization_code',
96
+ code,
97
+ redirect_uri: redirectUri,
98
+ client_id: this.config.clientId,
99
+ client_secret: this.config.clientSecret,
100
+ });
101
+ const response = await fetch(TOKEN_URL, {
102
+ method: 'POST',
103
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
104
+ body: body.toString(),
105
+ });
106
+ if (!response.ok) {
107
+ const error = await response.text();
108
+ throw new AuthenticationError(`Token exchange failed: ${error}`);
109
+ }
110
+ const tokenResponse = (await response.json());
111
+ return TokenInfo.fromTokenResponse(tokenResponse);
112
+ }
113
+ async refresh(refreshToken) {
114
+ const body = new URLSearchParams({
115
+ grant_type: 'refresh_token',
116
+ refresh_token: refreshToken,
117
+ client_id: this.config.clientId,
118
+ client_secret: this.config.clientSecret,
119
+ });
120
+ const response = await fetch(TOKEN_URL, {
121
+ method: 'POST',
122
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
123
+ body: body.toString(),
124
+ });
125
+ if (!response.ok) {
126
+ throw new RefreshTokenError(`Token refresh failed: ${response.status}`);
127
+ }
128
+ const tokenResponse = (await response.json());
129
+ const tokenInfo = TokenInfo.fromTokenResponse(tokenResponse);
130
+ this.tokenInfo = tokenInfo;
131
+ await this.store.save(STORE_KEY, tokenInfo);
132
+ return tokenInfo;
133
+ }
134
+ async revoke() {
135
+ await this.store.clear(STORE_KEY);
136
+ this.tokenInfo = null;
137
+ }
138
+ }
139
+ //# sourceMappingURL=oauth-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-provider.js","sourceRoot":"","sources":["../../src/auth/oauth-provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export interface TokenInfoData {
2
+ accessToken: string;
3
+ refreshToken?: string;
4
+ expiresAt: number;
5
+ }
6
+ export declare class TokenInfo {
7
+ readonly accessToken: string;
8
+ readonly refreshToken: string | undefined;
9
+ readonly expiresAt: number;
10
+ constructor(data: TokenInfoData);
11
+ static fromTokenResponse(response: {
12
+ access_token: string;
13
+ refresh_token?: string;
14
+ expires_in: number;
15
+ }): TokenInfo;
16
+ get isExpired(): boolean;
17
+ get timeUntilExpiry(): number;
18
+ toJSON(): TokenInfoData;
19
+ static fromJSON(data: TokenInfoData): TokenInfo;
20
+ }
21
+ //# sourceMappingURL=token-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-info.d.ts","sourceRoot":"","sources":["../../src/auth/token-info.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,SAAS;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,IAAI,EAAE,aAAa;IAM/B,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE;QACjC,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,SAAS;IAQb,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED,MAAM,IAAI,aAAa;IAQvB,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS;CAGhD"}
@@ -0,0 +1,35 @@
1
+ const EXPIRY_BUFFER_MS = 60_000;
2
+ export class TokenInfo {
3
+ accessToken;
4
+ refreshToken;
5
+ expiresAt;
6
+ constructor(data) {
7
+ this.accessToken = data.accessToken;
8
+ this.refreshToken = data.refreshToken;
9
+ this.expiresAt = data.expiresAt;
10
+ }
11
+ static fromTokenResponse(response) {
12
+ return new TokenInfo({
13
+ accessToken: response.access_token,
14
+ refreshToken: response.refresh_token,
15
+ expiresAt: Date.now() + response.expires_in * 1000,
16
+ });
17
+ }
18
+ get isExpired() {
19
+ return Date.now() >= this.expiresAt - EXPIRY_BUFFER_MS;
20
+ }
21
+ get timeUntilExpiry() {
22
+ return Math.max(0, this.expiresAt - Date.now());
23
+ }
24
+ toJSON() {
25
+ return {
26
+ accessToken: this.accessToken,
27
+ refreshToken: this.refreshToken,
28
+ expiresAt: this.expiresAt,
29
+ };
30
+ }
31
+ static fromJSON(data) {
32
+ return new TokenInfo(data);
33
+ }
34
+ }
35
+ //# sourceMappingURL=token-info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-info.js","sourceRoot":"","sources":["../../src/auth/token-info.ts"],"names":[],"mappings":"AAAA,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAQhC,MAAM,OAAO,SAAS;IACX,WAAW,CAAS;IACpB,YAAY,CAAqB;IACjC,SAAS,CAAS;IAE3B,YAAY,IAAmB;QAC7B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACpC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QACtC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAClC,CAAC;IAED,MAAM,CAAC,iBAAiB,CAAC,QAIxB;QACC,OAAO,IAAI,SAAS,CAAC;YACnB,WAAW,EAAE,QAAQ,CAAC,YAAY;YAClC,YAAY,EAAE,QAAQ,CAAC,aAAa;YACpC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,UAAU,GAAG,IAAI;SACnD,CAAC,CAAC;IACL,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,SAAS,GAAG,gBAAgB,CAAC;IACzD,CAAC;IAED,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,MAAM;QACJ,OAAO;YACL,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,IAAmB;QACjC,OAAO,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;CACF"}
@@ -0,0 +1,21 @@
1
+ import { TokenInfo } from './token-info.js';
2
+ export interface TokenStore {
3
+ load(key: string): Promise<TokenInfo | null>;
4
+ save(key: string, token: TokenInfo): Promise<void>;
5
+ clear(key: string): Promise<void>;
6
+ }
7
+ export declare class MemoryTokenStore implements TokenStore {
8
+ private tokens;
9
+ load(key: string): Promise<TokenInfo | null>;
10
+ save(key: string, token: TokenInfo): Promise<void>;
11
+ clear(key: string): Promise<void>;
12
+ }
13
+ export declare class FileTokenStore implements TokenStore {
14
+ private readonly filePath;
15
+ constructor(filePath: string);
16
+ load(key: string): Promise<TokenInfo | null>;
17
+ save(key: string, token: TokenInfo): Promise<void>;
18
+ clear(key: string): Promise<void>;
19
+ private checkPermissions;
20
+ }
21
+ //# sourceMappingURL=token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAsB,MAAM,iBAAiB,CAAC;AAEhE,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED,qBAAa,gBAAiB,YAAW,UAAU;IACjD,OAAO,CAAC,MAAM,CAAgC;IAExC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAI5C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGxC;AAED,qBAAa,cAAe,YAAW,UAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAEvC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAgB5C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAalD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAWzB,gBAAgB;CAc/B"}