@dlwiest/ts-tonal-client 0.2.0 → 0.3.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/README.md +40 -1
- package/dist/{index.js → index.cjs} +140 -72
- package/dist/index.d.ts +13 -7
- package/dist/index.esm.js +140 -72
- package/package.json +6 -5
- package/dist/examples/create-workout.d.ts +0 -1
- package/dist/examples/debug-daily-lifts.d.ts +0 -1
- package/dist/examples/delete-workout.d.ts +0 -1
- package/dist/examples/edit-workout.d.ts +0 -1
- package/dist/examples/estimate-workout.d.ts +0 -1
- package/dist/examples/get-achievement-stats.d.ts +0 -2
- package/dist/examples/get-achievements.d.ts +0 -2
- package/dist/examples/get-activity-summaries.d.ts +0 -2
- package/dist/examples/get-current-streak.d.ts +0 -1
- package/dist/examples/get-daily-lifts.d.ts +0 -1
- package/dist/examples/get-daily-metrics.d.ts +0 -1
- package/dist/examples/get-goal-metrics.d.ts +0 -1
- package/dist/examples/get-goals.d.ts +0 -1
- package/dist/examples/get-home-calendar.d.ts +0 -2
- package/dist/examples/get-metric-scores.d.ts +0 -1
- package/dist/examples/get-movements.d.ts +0 -1
- package/dist/examples/get-muscle-readiness.d.ts +0 -1
- package/dist/examples/get-program-by-id.d.ts +0 -1
- package/dist/examples/get-target-scores.d.ts +0 -1
- package/dist/examples/get-training-effect-goals.d.ts +0 -1
- package/dist/examples/get-training-types.d.ts +0 -1
- package/dist/examples/get-user-info.d.ts +0 -1
- package/dist/examples/get-user-settings.d.ts +0 -1
- package/dist/examples/get-user-statistics.d.ts +0 -2
- package/dist/examples/get-user-workouts.d.ts +0 -1
- package/dist/examples/get-workout-by-id.d.ts +0 -1
- package/dist/examples/get-workout-by-share-url.d.ts +0 -1
- package/dist/examples/raw-daily-lifts-test.d.ts +0 -1
- package/dist/src/auth/auth-manager.d.ts +0 -15
- package/dist/src/client.d.ts +0 -40
- package/dist/src/http/http-client.d.ts +0 -12
- package/dist/src/index.d.ts +0 -4
- package/dist/src/services/movement-service.d.ts +0 -9
- package/dist/src/services/user-service.d.ts +0 -51
- package/dist/src/services/workout-service.d.ts +0 -14
- package/dist/src/types/auth.d.ts +0 -18
- package/dist/src/types/common.d.ts +0 -1
- package/dist/src/types/index.d.ts +0 -6
- package/dist/src/types/movements.d.ts +0 -30
- package/dist/src/types/programs.d.ts +0 -46
- package/dist/src/types/users.d.ts +0 -418
- package/dist/src/types/workouts.d.ts +0 -120
- package/dist/src/types.d.ts +0 -125
- package/dist/src/utils/cache-manager.d.ts +0 -11
package/dist/index.esm.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
|
|
4
5
|
class TonalClientError extends Error {
|
|
@@ -15,9 +16,11 @@ class AuthManager {
|
|
|
15
16
|
this.idToken = '';
|
|
16
17
|
this.refreshToken = '';
|
|
17
18
|
this.tokenExpiresAt = 0;
|
|
18
|
-
this.
|
|
19
|
+
this.authPromise = null;
|
|
20
|
+
this.refreshPromise = null;
|
|
19
21
|
this.authUrl = 'https://tonal.auth0.com/oauth/token';
|
|
20
22
|
this.clientId = 'ERCyexW-xoVG_Yy3RDe-eV4xsOnRHP6L';
|
|
23
|
+
this.authTimeout = 30000;
|
|
21
24
|
this.username = username;
|
|
22
25
|
this.password = password;
|
|
23
26
|
}
|
|
@@ -25,6 +28,18 @@ class AuthManager {
|
|
|
25
28
|
if (this.isTokenValid()) {
|
|
26
29
|
return this.idToken;
|
|
27
30
|
}
|
|
31
|
+
const activeAuth = this.authPromise ?? this.authenticateWithPassword();
|
|
32
|
+
this.authPromise = activeAuth;
|
|
33
|
+
try {
|
|
34
|
+
return await activeAuth;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
if (this.authPromise === activeAuth) {
|
|
38
|
+
this.authPromise = null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async authenticateWithPassword() {
|
|
28
43
|
const response = await fetch(this.authUrl, {
|
|
29
44
|
method: 'POST',
|
|
30
45
|
headers: {
|
|
@@ -37,6 +52,7 @@ class AuthManager {
|
|
|
37
52
|
grant_type: 'password',
|
|
38
53
|
scope: 'offline_access',
|
|
39
54
|
}),
|
|
55
|
+
signal: AbortSignal.timeout(this.authTimeout),
|
|
40
56
|
});
|
|
41
57
|
if (!response.ok) {
|
|
42
58
|
const errorText = await response.text();
|
|
@@ -51,69 +67,69 @@ class AuthManager {
|
|
|
51
67
|
}
|
|
52
68
|
const tokenData = await response.json();
|
|
53
69
|
this.idToken = tokenData.id_token;
|
|
54
|
-
this.refreshToken = tokenData.refresh_token;
|
|
70
|
+
this.refreshToken = tokenData.refresh_token ?? '';
|
|
55
71
|
this.tokenExpiresAt = Date.now() + (tokenData.expires_in * 1000);
|
|
56
72
|
return this.idToken;
|
|
57
73
|
}
|
|
74
|
+
invalidateToken() {
|
|
75
|
+
this.tokenExpiresAt = 0;
|
|
76
|
+
}
|
|
58
77
|
async getValidToken() {
|
|
59
78
|
if (this.isTokenValid()) {
|
|
60
79
|
return this.idToken;
|
|
61
80
|
}
|
|
62
|
-
if (this.refreshToken) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
if (!this.refreshToken) {
|
|
82
|
+
return this.authenticate();
|
|
83
|
+
}
|
|
84
|
+
const activeRefresh = this.refreshPromise ?? this.refreshTokens();
|
|
85
|
+
this.refreshPromise = activeRefresh;
|
|
86
|
+
try {
|
|
87
|
+
await activeRefresh;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return this.authenticate();
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
if (this.refreshPromise === activeRefresh) {
|
|
94
|
+
this.refreshPromise = null;
|
|
74
95
|
}
|
|
75
96
|
}
|
|
76
|
-
|
|
97
|
+
if (this.isTokenValid()) {
|
|
98
|
+
return this.idToken;
|
|
99
|
+
}
|
|
100
|
+
return this.authenticate();
|
|
77
101
|
}
|
|
78
102
|
isTokenValid() {
|
|
79
103
|
return !!this.idToken && Date.now() < this.tokenExpiresAt - 60000; // 1 minute buffer
|
|
80
104
|
}
|
|
81
105
|
async refreshTokens() {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const errorText = await response.text();
|
|
100
|
-
let errorData;
|
|
101
|
-
try {
|
|
102
|
-
errorData = JSON.parse(errorText);
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
errorData = { error: errorText };
|
|
106
|
-
}
|
|
107
|
-
throw new TonalClientError(errorData.error_description || errorData.error || 'Token refresh failed', response.status, errorData);
|
|
106
|
+
const response = await fetch(this.authUrl, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: {
|
|
109
|
+
'Content-Type': 'application/json',
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
client_id: this.clientId,
|
|
113
|
+
grant_type: 'refresh_token',
|
|
114
|
+
refresh_token: this.refreshToken,
|
|
115
|
+
}),
|
|
116
|
+
signal: AbortSignal.timeout(this.authTimeout),
|
|
117
|
+
});
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
const errorText = await response.text();
|
|
120
|
+
let errorData;
|
|
121
|
+
try {
|
|
122
|
+
errorData = JSON.parse(errorText);
|
|
108
123
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
finally {
|
|
115
|
-
this.isRefreshing = false;
|
|
124
|
+
catch {
|
|
125
|
+
errorData = { error: errorText };
|
|
126
|
+
}
|
|
127
|
+
throw new TonalClientError(errorData.error_description || errorData.error || 'Token refresh failed', response.status, errorData);
|
|
116
128
|
}
|
|
129
|
+
const tokenData = await response.json();
|
|
130
|
+
this.idToken = tokenData.id_token;
|
|
131
|
+
this.refreshToken = tokenData.refresh_token ?? this.refreshToken;
|
|
132
|
+
this.tokenExpiresAt = Date.now() + (tokenData.expires_in * 1000);
|
|
117
133
|
}
|
|
118
134
|
}
|
|
119
135
|
|
|
@@ -129,10 +145,11 @@ class HttpClient {
|
|
|
129
145
|
return this.makeRequestWithRetry(url, options, expectsBody);
|
|
130
146
|
}
|
|
131
147
|
async makeRequest(url, options = {}, expectsBody = true) {
|
|
132
|
-
|
|
133
|
-
const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
|
|
148
|
+
let timeoutId;
|
|
134
149
|
try {
|
|
135
150
|
const token = await this.authManager.getValidToken();
|
|
151
|
+
const controller = new AbortController();
|
|
152
|
+
timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
|
|
136
153
|
const response = await fetch(url, {
|
|
137
154
|
...options,
|
|
138
155
|
headers: {
|
|
@@ -143,6 +160,7 @@ class HttpClient {
|
|
|
143
160
|
signal: controller.signal,
|
|
144
161
|
});
|
|
145
162
|
clearTimeout(timeoutId);
|
|
163
|
+
timeoutId = undefined;
|
|
146
164
|
if (!response.ok) {
|
|
147
165
|
const errorText = await response.text();
|
|
148
166
|
let errorData;
|
|
@@ -169,28 +187,35 @@ class HttpClient {
|
|
|
169
187
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
170
188
|
throw new TonalClientError('Request timeout', undefined, error);
|
|
171
189
|
}
|
|
172
|
-
throw new TonalClientError('Request failed', undefined, error);
|
|
190
|
+
throw new TonalClientError(error instanceof Error ? error.message : 'Request failed', undefined, error);
|
|
173
191
|
}
|
|
174
192
|
}
|
|
175
193
|
async makeRequestWithRetry(url, options = {}, expectsBody = true) {
|
|
176
194
|
let lastError;
|
|
195
|
+
const canRetry = (options.method ?? 'GET').toUpperCase() === 'GET';
|
|
177
196
|
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
|
178
197
|
try {
|
|
179
198
|
return await this.makeRequest(url, options, expectsBody);
|
|
180
199
|
}
|
|
181
200
|
catch (error) {
|
|
182
201
|
lastError = error instanceof TonalClientError ? error : new TonalClientError('Unknown error', undefined, error);
|
|
183
|
-
// If it's an auth error on first attempt,
|
|
184
|
-
if (attempt === 1 &&
|
|
202
|
+
// If it's an auth error on first attempt, invalidate the token and retry once
|
|
203
|
+
if (attempt === 1 && (lastError.statusCode === 401 || lastError.statusCode === 403)) {
|
|
185
204
|
try {
|
|
186
|
-
|
|
187
|
-
|
|
205
|
+
this.authManager.invalidateToken();
|
|
206
|
+
await this.authManager.getValidToken();
|
|
207
|
+
continue;
|
|
188
208
|
}
|
|
189
|
-
catch
|
|
209
|
+
catch {
|
|
190
210
|
// If refresh fails, continue with normal retry logic
|
|
191
211
|
}
|
|
192
212
|
}
|
|
193
|
-
|
|
213
|
+
const isAbort = lastError.originalError instanceof Error &&
|
|
214
|
+
lastError.originalError.name === 'AbortError';
|
|
215
|
+
if (attempt === this.maxRetries ||
|
|
216
|
+
!canRetry ||
|
|
217
|
+
(lastError.statusCode !== undefined && lastError.statusCode < 500) ||
|
|
218
|
+
(lastError.statusCode === undefined && !isAbort)) {
|
|
194
219
|
throw lastError;
|
|
195
220
|
}
|
|
196
221
|
const delay = Math.pow(2, attempt - 1) * 1000;
|
|
@@ -219,6 +244,9 @@ class WorkoutService {
|
|
|
219
244
|
}
|
|
220
245
|
async getDailyLifts(userInfo, timeZone) {
|
|
221
246
|
const device = userInfo.recentMobileDevice;
|
|
247
|
+
if (!device) {
|
|
248
|
+
throw new TonalClientError('Recent mobile device information is unavailable; daily lifts require a mobile device');
|
|
249
|
+
}
|
|
222
250
|
const userAgent = device.platform === 'ios'
|
|
223
251
|
? `Tonal/3004226 CFNetwork/3860.100.1 Darwin/${device.osVersion}`
|
|
224
252
|
: `Tonal/${device.appVersion}`;
|
|
@@ -255,8 +283,8 @@ class WorkoutService {
|
|
|
255
283
|
if (!shareUrl?.trim()) {
|
|
256
284
|
throw new TonalClientError('Share URL is required');
|
|
257
285
|
}
|
|
258
|
-
const urlPattern =
|
|
259
|
-
const match = shareUrl.match(urlPattern);
|
|
286
|
+
const urlPattern = /^https:\/\/share\.tonal\.com\/workout\/([a-fA-F0-9-]+)(?:[?#]\S*)?$/;
|
|
287
|
+
const match = shareUrl.trim().match(urlPattern);
|
|
260
288
|
if (!match) {
|
|
261
289
|
throw new TonalClientError('Invalid share URL format. Expected: https://share.tonal.com/workout/{id}');
|
|
262
290
|
}
|
|
@@ -301,9 +329,16 @@ class WorkoutService {
|
|
|
301
329
|
if (!workoutData.sets?.length) {
|
|
302
330
|
throw new TonalClientError('At least one set is required');
|
|
303
331
|
}
|
|
332
|
+
// Tonal's PUT behavior is unverified. Omitting an absent shortDescription is equivalent
|
|
333
|
+
// under full-replace semantics and avoids an unintended clear under merge semantics;
|
|
334
|
+
// an explicit empty string intentionally clears it. `!= null` also covers null, which
|
|
335
|
+
// sibling response fields are known to return despite their non-optional types.
|
|
304
336
|
const requestBody = {
|
|
305
337
|
id: workoutData.id,
|
|
306
338
|
title: workoutData.title,
|
|
339
|
+
...(workoutData.shortDescription != null
|
|
340
|
+
? { shortDescription: workoutData.shortDescription }
|
|
341
|
+
: {}),
|
|
307
342
|
description: workoutData.description || '',
|
|
308
343
|
coachId: workoutData.coachId || '00000000-0000-0000-0000-000000000000',
|
|
309
344
|
sets: workoutData.sets,
|
|
@@ -327,10 +362,11 @@ class WorkoutService {
|
|
|
327
362
|
}
|
|
328
363
|
|
|
329
364
|
class CacheManager {
|
|
330
|
-
constructor(cacheDir =
|
|
365
|
+
constructor(cacheDir = process.env.XDG_CACHE_HOME
|
|
366
|
+
? path.join(process.env.XDG_CACHE_HOME, 'ts-tonal-client')
|
|
367
|
+
: path.join(os.homedir(), '.cache', 'ts-tonal-client'), defaultTTL = 24 * 60 * 60 * 1000) {
|
|
331
368
|
this.cacheDir = cacheDir;
|
|
332
369
|
this.defaultTTL = defaultTTL;
|
|
333
|
-
this.ensureCacheDir();
|
|
334
370
|
}
|
|
335
371
|
ensureCacheDir() {
|
|
336
372
|
if (!fs.existsSync(this.cacheDir)) {
|
|
@@ -351,8 +387,7 @@ class CacheManager {
|
|
|
351
387
|
const cachedAt = new Date(entry.cachedAt).getTime();
|
|
352
388
|
const now = Date.now();
|
|
353
389
|
const age = now - cachedAt;
|
|
354
|
-
if (age > entry.ttl) {
|
|
355
|
-
// Cache expired
|
|
390
|
+
if (!Number.isFinite(cachedAt) || typeof entry.ttl !== 'number' || age > entry.ttl) {
|
|
356
391
|
return null;
|
|
357
392
|
}
|
|
358
393
|
return entry.data;
|
|
@@ -364,12 +399,15 @@ class CacheManager {
|
|
|
364
399
|
}
|
|
365
400
|
async set(key, data, ttl) {
|
|
366
401
|
const cachePath = this.getCachePath(key);
|
|
402
|
+
this.ensureCacheDir();
|
|
367
403
|
const entry = {
|
|
368
404
|
cachedAt: new Date().toISOString(),
|
|
369
405
|
ttl: ttl || this.defaultTTL,
|
|
370
406
|
data,
|
|
371
407
|
};
|
|
372
|
-
|
|
408
|
+
const tempPath = `${cachePath}.tmp`;
|
|
409
|
+
fs.writeFileSync(tempPath, JSON.stringify(entry, null, 2), 'utf-8');
|
|
410
|
+
fs.renameSync(tempPath, cachePath);
|
|
373
411
|
}
|
|
374
412
|
async invalidate(key) {
|
|
375
413
|
const cachePath = this.getCachePath(key);
|
|
@@ -379,7 +417,7 @@ class CacheManager {
|
|
|
379
417
|
}
|
|
380
418
|
async clear() {
|
|
381
419
|
if (fs.existsSync(this.cacheDir)) {
|
|
382
|
-
const files = fs.readdirSync(this.cacheDir);
|
|
420
|
+
const files = fs.readdirSync(this.cacheDir).filter(file => file.endsWith('.json') || file.endsWith('.json.tmp'));
|
|
383
421
|
for (const file of files) {
|
|
384
422
|
fs.unlinkSync(path.join(this.cacheDir, file));
|
|
385
423
|
}
|
|
@@ -388,9 +426,9 @@ class CacheManager {
|
|
|
388
426
|
}
|
|
389
427
|
|
|
390
428
|
class MovementService {
|
|
391
|
-
constructor(httpClient) {
|
|
429
|
+
constructor(httpClient, cacheDir) {
|
|
392
430
|
this.httpClient = httpClient;
|
|
393
|
-
this.cacheManager = new CacheManager();
|
|
431
|
+
this.cacheManager = new CacheManager(cacheDir);
|
|
394
432
|
}
|
|
395
433
|
async getMovements(useCache = true) {
|
|
396
434
|
if (useCache) {
|
|
@@ -400,7 +438,12 @@ class MovementService {
|
|
|
400
438
|
}
|
|
401
439
|
}
|
|
402
440
|
const movements = await this.httpClient.request('/movements');
|
|
403
|
-
|
|
441
|
+
try {
|
|
442
|
+
await this.cacheManager.set('movements', movements);
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
// Cache writes are best-effort and must not hide a successful API response.
|
|
446
|
+
}
|
|
404
447
|
return movements;
|
|
405
448
|
}
|
|
406
449
|
async invalidateMovementsCache() {
|
|
@@ -504,15 +547,15 @@ class UserService {
|
|
|
504
547
|
}
|
|
505
548
|
|
|
506
549
|
class TonalClient {
|
|
507
|
-
constructor(username, password) {
|
|
550
|
+
constructor(username, password, cacheDir) {
|
|
508
551
|
this.authManager = new AuthManager(username, password);
|
|
509
552
|
this.httpClient = new HttpClient(this.authManager);
|
|
510
553
|
this.workoutService = new WorkoutService(this.httpClient);
|
|
511
|
-
this.movementService = new MovementService(this.httpClient);
|
|
554
|
+
this.movementService = new MovementService(this.httpClient, cacheDir);
|
|
512
555
|
this.userService = new UserService(this.httpClient);
|
|
513
556
|
}
|
|
514
557
|
static async create(credentials) {
|
|
515
|
-
const client = new TonalClient(credentials.username, credentials.password);
|
|
558
|
+
const client = new TonalClient(credentials.username, credentials.password, credentials.cacheDir);
|
|
516
559
|
await client.authManager.authenticate();
|
|
517
560
|
return client;
|
|
518
561
|
}
|
|
@@ -525,7 +568,32 @@ class TonalClient {
|
|
|
525
568
|
}
|
|
526
569
|
// User operations
|
|
527
570
|
async getUserInfo() {
|
|
528
|
-
|
|
571
|
+
if (this.userInfo) {
|
|
572
|
+
return this.userInfo;
|
|
573
|
+
}
|
|
574
|
+
let request = this.userInfoPromise;
|
|
575
|
+
if (!request) {
|
|
576
|
+
request = this.userService.getUserInfo();
|
|
577
|
+
this.userInfoPromise = request;
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
const userInfo = await request;
|
|
581
|
+
if (this.userInfoPromise === request) {
|
|
582
|
+
this.userInfo = userInfo;
|
|
583
|
+
this.userInfoPromise = undefined;
|
|
584
|
+
}
|
|
585
|
+
return userInfo;
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
if (this.userInfoPromise === request) {
|
|
589
|
+
this.userInfoPromise = undefined;
|
|
590
|
+
}
|
|
591
|
+
throw error;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
invalidateUserInfo() {
|
|
595
|
+
this.userInfo = undefined;
|
|
596
|
+
this.userInfoPromise = undefined;
|
|
529
597
|
}
|
|
530
598
|
async getGoals() {
|
|
531
599
|
return this.userService.getGoals();
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dlwiest/ts-tonal-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "TypeScript client for Tonal API",
|
|
5
|
-
"main": "dist/index.
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"type": "module",
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
".": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.esm.js",
|
|
13
|
-
"require": "./dist/index.
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "rollup -c --bundleConfigAsCjs",
|
|
20
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && rollup -c --bundleConfigAsCjs",
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
22
|
"dev": "tsc --watch --noEmit",
|
|
23
23
|
"test": "jest",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"example:movements": "tsx examples/get-movements.ts",
|
|
28
28
|
"example:movements:save": "tsx examples/get-movements.ts --save",
|
|
29
29
|
"example:workout:id": "tsx examples/get-workout-by-id.ts",
|
|
30
|
+
"example:workout:name": "tsx examples/get-workout-by-name.ts",
|
|
30
31
|
"example:workout:share": "tsx examples/get-workout-by-share-url.ts",
|
|
31
32
|
"example:user": "tsx examples/get-user-info.ts",
|
|
32
33
|
"example:goals": "tsx examples/get-goals.ts",
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"client"
|
|
64
65
|
],
|
|
65
66
|
"author": "dlwiest",
|
|
66
|
-
"license": "
|
|
67
|
+
"license": "MIT",
|
|
67
68
|
"bugs": {
|
|
68
69
|
"url": "https://github.com/dlwiest/ts-tonal-client/issues"
|
|
69
70
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export declare class AuthManager {
|
|
2
|
-
private username;
|
|
3
|
-
private password;
|
|
4
|
-
private idToken;
|
|
5
|
-
private refreshToken;
|
|
6
|
-
private tokenExpiresAt;
|
|
7
|
-
private isRefreshing;
|
|
8
|
-
private readonly authUrl;
|
|
9
|
-
private readonly clientId;
|
|
10
|
-
constructor(username: string, password: string);
|
|
11
|
-
authenticate(): Promise<string>;
|
|
12
|
-
getValidToken(): Promise<string>;
|
|
13
|
-
private isTokenValid;
|
|
14
|
-
private refreshTokens;
|
|
15
|
-
}
|
package/dist/src/client.d.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { TonalMovement, TonalSharedWorkout, TonalWorkout, TonalUserInfo, TonalGoal, TonalTrainingEffectGoalsResponse, TonalTrainingType, TonalGoalMetric, TonalUserSettings, TonalDailyMetrics, TonalCurrentStreak, TonalActivitySummary, TonalUserStatistics, TonalAchievementStats, TonalEarnedAchievement, TonalHomeCalendar, TonalWorkoutEstimateSet, TonalWorkoutEstimateResponse, TonalWorkoutCreateRequest, TonalWorkoutUpdateRequest, TonalMuscleReadiness, TonalProgram, TonalTargetScoresResponse, TonalMetricScoresResponse } from './types';
|
|
2
|
-
export declare class TonalClient {
|
|
3
|
-
private authManager;
|
|
4
|
-
private httpClient;
|
|
5
|
-
private workoutService;
|
|
6
|
-
private movementService;
|
|
7
|
-
private userService;
|
|
8
|
-
private constructor();
|
|
9
|
-
static create(credentials: {
|
|
10
|
-
username: string;
|
|
11
|
-
password: string;
|
|
12
|
-
}): Promise<TonalClient>;
|
|
13
|
-
getMovements(useCache?: boolean): Promise<TonalMovement[]>;
|
|
14
|
-
invalidateMovementsCache(): Promise<void>;
|
|
15
|
-
getUserInfo(): Promise<TonalUserInfo>;
|
|
16
|
-
getGoals(): Promise<TonalGoal[]>;
|
|
17
|
-
getTrainingEffectGoals(): Promise<TonalTrainingEffectGoalsResponse>;
|
|
18
|
-
getTrainingTypes(): Promise<TonalTrainingType[]>;
|
|
19
|
-
getGoalMetrics(): Promise<TonalGoalMetric[]>;
|
|
20
|
-
getUserSettings(): Promise<TonalUserSettings>;
|
|
21
|
-
getDailyMetrics(days?: number): Promise<TonalDailyMetrics[]>;
|
|
22
|
-
getCurrentStreak(): Promise<TonalCurrentStreak>;
|
|
23
|
-
getActivitySummaries(): Promise<TonalActivitySummary[]>;
|
|
24
|
-
getUserStatistics(): Promise<TonalUserStatistics>;
|
|
25
|
-
getAchievementStats(): Promise<TonalAchievementStats>;
|
|
26
|
-
getAchievements(): Promise<TonalEarnedAchievement[]>;
|
|
27
|
-
getHomeCalendar(): Promise<TonalHomeCalendar>;
|
|
28
|
-
getMuscleReadiness(): Promise<TonalMuscleReadiness>;
|
|
29
|
-
getProgramById(programId: string): Promise<TonalProgram>;
|
|
30
|
-
getTargetScores(): Promise<TonalTargetScoresResponse>;
|
|
31
|
-
getMetricScores(startWeek?: number): Promise<TonalMetricScoresResponse>;
|
|
32
|
-
getUserWorkouts(offset?: number, limit?: number): Promise<TonalWorkout[]>;
|
|
33
|
-
getDailyLifts(timeZone?: string): Promise<TonalWorkout[]>;
|
|
34
|
-
getWorkoutById(workoutId: string): Promise<TonalWorkout>;
|
|
35
|
-
getWorkoutByShareUrl(shareUrl: string): Promise<TonalSharedWorkout>;
|
|
36
|
-
estimateWorkoutDuration(sets: TonalWorkoutEstimateSet[]): Promise<TonalWorkoutEstimateResponse>;
|
|
37
|
-
createWorkout(workoutData: TonalWorkoutCreateRequest): Promise<TonalWorkout>;
|
|
38
|
-
updateWorkout(workoutData: TonalWorkoutUpdateRequest): Promise<TonalWorkout>;
|
|
39
|
-
deleteWorkout(workoutId: string): Promise<void>;
|
|
40
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { AuthManager } from '../auth/auth-manager';
|
|
2
|
-
export declare class HttpClient {
|
|
3
|
-
private authManager;
|
|
4
|
-
private readonly baseUrl;
|
|
5
|
-
private readonly requestTimeout;
|
|
6
|
-
private readonly maxRetries;
|
|
7
|
-
constructor(authManager: AuthManager);
|
|
8
|
-
request<T>(endpoint: string, options?: RequestInit, expectsBody?: boolean): Promise<T>;
|
|
9
|
-
private makeRequest;
|
|
10
|
-
private makeRequestWithRetry;
|
|
11
|
-
private sleep;
|
|
12
|
-
}
|
package/dist/src/index.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { HttpClient } from '../http/http-client';
|
|
2
|
-
import { TonalMovement } from '../types';
|
|
3
|
-
export declare class MovementService {
|
|
4
|
-
private httpClient;
|
|
5
|
-
private cacheManager;
|
|
6
|
-
constructor(httpClient: HttpClient);
|
|
7
|
-
getMovements(useCache?: boolean): Promise<TonalMovement[]>;
|
|
8
|
-
invalidateMovementsCache(): Promise<void>;
|
|
9
|
-
}
|