@openlifelog/sdk 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/EXAMPLES.md +624 -0
- package/README.md +824 -0
- package/client.ts +190 -0
- package/config.ts +96 -0
- package/constants/metrics.ts +116 -0
- package/dist/index.d.mts +1101 -0
- package/dist/index.d.ts +1101 -0
- package/dist/index.js +2023 -0
- package/dist/index.mjs +1969 -0
- package/index.ts +49 -0
- package/package.json +53 -0
- package/resources/ai.ts +26 -0
- package/resources/auth.ts +98 -0
- package/resources/exercises.ts +112 -0
- package/resources/food-logs.ts +132 -0
- package/resources/foods.ts +185 -0
- package/resources/goals.ts +155 -0
- package/resources/metrics.ts +115 -0
- package/resources/programs.ts +123 -0
- package/resources/sessions.ts +142 -0
- package/resources/users.ts +132 -0
- package/resources/workouts.ts +147 -0
- package/tsconfig.json +27 -0
- package/types/ai.ts +55 -0
- package/types/common.ts +177 -0
- package/types/exercise.ts +75 -0
- package/types/food.ts +208 -0
- package/types/goal.ts +169 -0
- package/types/index.ts +17 -0
- package/types/metric.ts +108 -0
- package/types/program.ts +120 -0
- package/types/session.ts +196 -0
- package/types/user.ts +79 -0
- package/types/workout.ts +97 -0
- package/utils/errors.ts +159 -0
- package/utils/http.ts +313 -0
- package/utils/index.ts +8 -0
- package/utils/pagination.ts +106 -0
- package/utils/units.ts +279 -0
package/EXAMPLES.md
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
# OpenLifeLog SDK - Quick Examples
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Authentication](#authentication)
|
|
6
|
+
- [Food Logging](#food-logging)
|
|
7
|
+
- [Workout Tracking](#workout-tracking)
|
|
8
|
+
- [Goals & Progress](#goals--progress)
|
|
9
|
+
- [Metrics](#metrics)
|
|
10
|
+
- [External Auth Integration](#external-auth-integration)
|
|
11
|
+
|
|
12
|
+
## Authentication
|
|
13
|
+
|
|
14
|
+
### Using with JWT Token (External Auth)
|
|
15
|
+
|
|
16
|
+
If your app already handles authentication:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
20
|
+
|
|
21
|
+
// Simply pass your JWT token
|
|
22
|
+
const client = new OpenLifeLog({
|
|
23
|
+
apiKey: 'your-jwt-token-here',
|
|
24
|
+
baseUrl: 'http://localhost:8080',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Ready to use!
|
|
28
|
+
const user = await client.users.me();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Set Token Later
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
const client = new OpenLifeLog({
|
|
35
|
+
baseUrl: 'http://localhost:8080',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// After your auth flow completes
|
|
39
|
+
client.setApiKey('jwt-token-from-your-system');
|
|
40
|
+
|
|
41
|
+
// Now ready
|
|
42
|
+
await client.logFood({ ... });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Sign Up
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const { token, user } = await client.auth.signup({
|
|
49
|
+
name: 'John Doe',
|
|
50
|
+
email: 'john@example.com',
|
|
51
|
+
password: 'securePassword123',
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Login
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const { token, user } = await client.auth.login({
|
|
59
|
+
email: 'john@example.com',
|
|
60
|
+
password: 'securePassword123',
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Food Logging
|
|
65
|
+
|
|
66
|
+
### Simple Food Logging
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// Quick and easy
|
|
70
|
+
await client.logFood({
|
|
71
|
+
foodId: 'chicken-breast-id',
|
|
72
|
+
name: 'Chicken Breast',
|
|
73
|
+
quantity: 1.5,
|
|
74
|
+
unitGrams: 200,
|
|
75
|
+
mealType: 'lunch',
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Search and Log Food
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Search for food
|
|
83
|
+
const { data: foods } = await client.foods.search({
|
|
84
|
+
q: 'chicken breast',
|
|
85
|
+
limit: 5,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Log the first result
|
|
89
|
+
if (foods.length > 0) {
|
|
90
|
+
const food = foods[0];
|
|
91
|
+
await client.logFood({
|
|
92
|
+
foodId: food.id,
|
|
93
|
+
name: food.name,
|
|
94
|
+
servingSizeName: food.servingSizes[0].name,
|
|
95
|
+
quantity: 1,
|
|
96
|
+
unitGrams: food.servingSizes[0].grams,
|
|
97
|
+
mealType: 'lunch',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Get Daily Summary
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Today's nutrition
|
|
106
|
+
const summary = await client.getTodayNutrition();
|
|
107
|
+
console.log(`
|
|
108
|
+
Calories: ${summary.totalCalories}
|
|
109
|
+
Protein: ${summary.totalProtein}g
|
|
110
|
+
Carbs: ${summary.totalCarbs}g
|
|
111
|
+
Fat: ${summary.totalFat}g
|
|
112
|
+
`);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### List Today's Meals
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const today = new Date().toISOString().split('T')[0];
|
|
119
|
+
const { data: logs } = await client.foodLogs.list({
|
|
120
|
+
date: today,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
console.log('Meals:');
|
|
124
|
+
logs.forEach((log) => {
|
|
125
|
+
console.log(`${log.mealType}: ${log.name} - ${log.nutrients.calories} cal`);
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Workout Tracking
|
|
130
|
+
|
|
131
|
+
### Create and Start Workout Session
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// Option 1: From a workout template
|
|
135
|
+
const session = await client.sessions.start({
|
|
136
|
+
workoutId: 'my-workout-template-id',
|
|
137
|
+
userBodyweight: 82.5,
|
|
138
|
+
notes: 'Feeling strong',
|
|
139
|
+
mood: 'energetic',
|
|
140
|
+
location: 'Home gym',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Option 2: Quick workout without template
|
|
144
|
+
const session = await client.sessions.start({
|
|
145
|
+
name: 'Quick Upper Body',
|
|
146
|
+
userBodyweight: 82.5,
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Complete a Workout
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
await client.sessions.complete(session.id, 'Great workout! Hit new PR.');
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### View Workout History
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
const { data: sessions } = await client.sessions.list({
|
|
160
|
+
startDate: '2024-01-01',
|
|
161
|
+
endDate: '2024-01-31',
|
|
162
|
+
status: 'completed',
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
console.log(`Completed ${sessions.length} workouts this month`);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Get Personal Records
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const { data: prs } = await client.sessions.getPersonalRecords();
|
|
172
|
+
|
|
173
|
+
prs.forEach((pr) => {
|
|
174
|
+
console.log(`${pr.exerciseName}: ${pr.value} ${pr.unit} (${pr.achievedDate})`);
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Exercise Performance History
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
const history = await client.sessions.getExerciseHistory('bench-press-id');
|
|
182
|
+
|
|
183
|
+
console.log(`Bench Press History:`);
|
|
184
|
+
history.history.forEach((entry) => {
|
|
185
|
+
console.log(`${entry.date}: ${entry.totalVolume}kg total volume`);
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Goals & Progress
|
|
190
|
+
|
|
191
|
+
### Set Daily Nutrition Goals
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
await client.goals.createNutritionGoals({
|
|
195
|
+
effectiveDate: '2024-01-15',
|
|
196
|
+
goals: {
|
|
197
|
+
calories: { type: 'exact', value: 2200 },
|
|
198
|
+
protein: { type: 'min', value: 180 },
|
|
199
|
+
carbs: { type: 'range', min: 200, max: 300 },
|
|
200
|
+
fat: { type: 'range', min: 60, max: 80 },
|
|
201
|
+
},
|
|
202
|
+
notes: 'Maintenance phase',
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Check Today's Progress
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const progress = await client.getTodayNutritionProgress();
|
|
210
|
+
|
|
211
|
+
console.log(`Goal Progress:`);
|
|
212
|
+
progress.progress.forEach((p) => {
|
|
213
|
+
const emoji = p.status === 'on_track' ? '✅' : p.status === 'below' ? '⬇️' : '⬆️';
|
|
214
|
+
console.log(`${emoji} ${p.nutrientKey}: ${p.current}/${p.target || 'goal'}`);
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Set Body Weight Goal
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
await client.goals.create({
|
|
222
|
+
metricKey: 'body_weight',
|
|
223
|
+
targetValue: 75.0,
|
|
224
|
+
targetType: 'exact',
|
|
225
|
+
effectiveFrom: '2024-01-15',
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Metrics
|
|
230
|
+
|
|
231
|
+
### Log Body Weight
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
await client.metrics.log({
|
|
235
|
+
metricKey: 'body_weight',
|
|
236
|
+
value: 82.5,
|
|
237
|
+
metadata: {
|
|
238
|
+
time: 'morning',
|
|
239
|
+
conditions: 'fasted',
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Log Multiple Metrics at Once
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
await client.metrics.bulkCreateEvents({
|
|
248
|
+
metrics: [
|
|
249
|
+
{ metricKey: 'body_weight', value: 82.5 },
|
|
250
|
+
{ metricKey: 'body_fat_percentage', value: 15.2 },
|
|
251
|
+
{ metricKey: 'sleep_hours', value: 7.5 },
|
|
252
|
+
],
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### View Metric History
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
const { data: events } = await client.metrics.listEvents({
|
|
260
|
+
metricKey: 'body_weight',
|
|
261
|
+
startDate: '2024-01-01',
|
|
262
|
+
endDate: '2024-01-31',
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
console.log('Weight History:');
|
|
266
|
+
events.forEach((event) => {
|
|
267
|
+
console.log(`${event.loggedAt}: ${event.value}kg`);
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Complete Daily Routine Example
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
275
|
+
|
|
276
|
+
const client = new OpenLifeLog({
|
|
277
|
+
baseUrl: 'http://localhost:8080',
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
async function dailyRoutine() {
|
|
281
|
+
// Login
|
|
282
|
+
await client.auth.login({
|
|
283
|
+
email: 'athlete@example.com',
|
|
284
|
+
password: 'password123',
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Morning: Log weight
|
|
288
|
+
await client.metrics.log({
|
|
289
|
+
metricKey: 'body_weight',
|
|
290
|
+
value: 82.3,
|
|
291
|
+
metadata: { time: 'morning' },
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Breakfast
|
|
295
|
+
await client.logFood({
|
|
296
|
+
foodId: 'oatmeal-id',
|
|
297
|
+
name: 'Oatmeal',
|
|
298
|
+
quantity: 1,
|
|
299
|
+
unitGrams: 250,
|
|
300
|
+
mealType: 'breakfast',
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Morning workout
|
|
304
|
+
const session = await client.sessions.start({
|
|
305
|
+
workoutId: 'upper-body-id',
|
|
306
|
+
userBodyweight: 82.3,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// After workout
|
|
310
|
+
await client.sessions.complete(session.id);
|
|
311
|
+
|
|
312
|
+
// Post-workout shake
|
|
313
|
+
await client.logFood({
|
|
314
|
+
foodId: 'protein-shake-id',
|
|
315
|
+
name: 'Protein Shake',
|
|
316
|
+
quantity: 1,
|
|
317
|
+
unitGrams: 300,
|
|
318
|
+
mealType: 'snack',
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Check progress
|
|
322
|
+
const summary = await client.getTodayNutrition();
|
|
323
|
+
const progress = await client.getTodayNutritionProgress();
|
|
324
|
+
|
|
325
|
+
console.log('Daily Summary:', summary);
|
|
326
|
+
console.log('Goal Progress:', progress);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
dailyRoutine().catch(console.error);
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Pagination Examples
|
|
333
|
+
|
|
334
|
+
### Auto-pagination for All Foods
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
// Automatically handles pagination
|
|
338
|
+
for await (const food of client.foods.listAutoPaginate()) {
|
|
339
|
+
console.log(food.name);
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Manual Pagination
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
let cursor: string | undefined;
|
|
347
|
+
let hasMore = true;
|
|
348
|
+
|
|
349
|
+
while (hasMore) {
|
|
350
|
+
const { data, pageInfo } = await client.foods.list({
|
|
351
|
+
limit: 100,
|
|
352
|
+
cursor,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
data.forEach((food) => console.log(food.name));
|
|
356
|
+
|
|
357
|
+
cursor = pageInfo.nextCursor;
|
|
358
|
+
hasMore = pageInfo.hasMore;
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Error Handling Example
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
import {
|
|
366
|
+
OpenLifeLogError,
|
|
367
|
+
AuthenticationError,
|
|
368
|
+
ValidationError,
|
|
369
|
+
NotFoundError,
|
|
370
|
+
} from '@openlifelog/sdk';
|
|
371
|
+
|
|
372
|
+
async function safeApiCall() {
|
|
373
|
+
try {
|
|
374
|
+
const food = await client.foods.get('food-id');
|
|
375
|
+
return food;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (error instanceof NotFoundError) {
|
|
378
|
+
console.error('Food not found');
|
|
379
|
+
return null;
|
|
380
|
+
} else if (error instanceof AuthenticationError) {
|
|
381
|
+
console.error('Please login');
|
|
382
|
+
// Redirect to login
|
|
383
|
+
return null;
|
|
384
|
+
} else if (error instanceof ValidationError) {
|
|
385
|
+
console.error('Validation error:', error.validationErrors);
|
|
386
|
+
return null;
|
|
387
|
+
} else if (error instanceof OpenLifeLogError) {
|
|
388
|
+
console.error('API error:', error.message);
|
|
389
|
+
return null;
|
|
390
|
+
} else {
|
|
391
|
+
console.error('Unknown error:', error);
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Unit Conversion Examples
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
// Set measurement system
|
|
402
|
+
client.setMeasurementSystem('imperial');
|
|
403
|
+
|
|
404
|
+
// Get converter
|
|
405
|
+
const converter = client.getUnitConverter();
|
|
406
|
+
|
|
407
|
+
// Weight conversion
|
|
408
|
+
const kgToLbs = converter.weightFromMetric(82.5); // 181.88 lbs
|
|
409
|
+
const lbsToKg = converter.weightToMetric(180); // 81.65 kg
|
|
410
|
+
|
|
411
|
+
// Distance conversion
|
|
412
|
+
const metersToMiles = converter.distanceFromMetric(5000, true); // 3.11 miles
|
|
413
|
+
const milesToMeters = converter.distanceToMetric(3.1, true); // 4989.0 meters
|
|
414
|
+
|
|
415
|
+
// Get unit labels
|
|
416
|
+
console.log(converter.getWeightUnit()); // 'lbs'
|
|
417
|
+
console.log(converter.getDistanceUnit(true)); // 'mi'
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## External Auth Integration
|
|
421
|
+
|
|
422
|
+
### Using with Auth0
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import { useAuth0 } from '@auth0/auth0-react';
|
|
426
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
427
|
+
import { useMemo } from 'react';
|
|
428
|
+
|
|
429
|
+
function useOpenLifeLog() {
|
|
430
|
+
const { getAccessTokenSilently, isAuthenticated } = useAuth0();
|
|
431
|
+
|
|
432
|
+
return useMemo(() => {
|
|
433
|
+
if (!isAuthenticated) return null;
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
getClient: async () => {
|
|
437
|
+
const token = await getAccessTokenSilently();
|
|
438
|
+
return new OpenLifeLog({
|
|
439
|
+
apiKey: token,
|
|
440
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL!,
|
|
441
|
+
});
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
}, [isAuthenticated, getAccessTokenSilently]);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Usage in component
|
|
448
|
+
function MyComponent() {
|
|
449
|
+
const openlifelog = useOpenLifeLog();
|
|
450
|
+
|
|
451
|
+
const loadData = async () => {
|
|
452
|
+
if (!openlifelog) return;
|
|
453
|
+
|
|
454
|
+
const client = await openlifelog.getClient();
|
|
455
|
+
const user = await client.users.me();
|
|
456
|
+
const summary = await client.getTodayNutrition();
|
|
457
|
+
// ...
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
return <div>...</div>;
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Using with Clerk
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
import { useAuth } from '@clerk/nextjs';
|
|
468
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
469
|
+
|
|
470
|
+
function useOpenLifeLog() {
|
|
471
|
+
const { getToken } = useAuth();
|
|
472
|
+
|
|
473
|
+
const getClient = async () => {
|
|
474
|
+
const token = await getToken();
|
|
475
|
+
return new OpenLifeLog({
|
|
476
|
+
apiKey: token!,
|
|
477
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL!,
|
|
478
|
+
});
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
return { getClient };
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Using with NextAuth.js
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
// app/api/nutrition/route.ts
|
|
489
|
+
import { getServerSession } from 'next-auth/next';
|
|
490
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
491
|
+
import { authOptions } from '@/lib/auth';
|
|
492
|
+
|
|
493
|
+
export async function GET() {
|
|
494
|
+
const session = await getServerSession(authOptions);
|
|
495
|
+
|
|
496
|
+
if (!session?.accessToken) {
|
|
497
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const client = new OpenLifeLog({
|
|
501
|
+
apiKey: session.accessToken,
|
|
502
|
+
baseUrl: process.env.API_URL!,
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const summary = await client.getTodayNutrition();
|
|
506
|
+
return Response.json(summary);
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Using with Firebase Auth
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
import { getAuth } from 'firebase/auth';
|
|
514
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
515
|
+
|
|
516
|
+
async function getClient() {
|
|
517
|
+
const auth = getAuth();
|
|
518
|
+
const user = auth.currentUser;
|
|
519
|
+
|
|
520
|
+
if (!user) {
|
|
521
|
+
throw new Error('Not authenticated');
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const token = await user.getIdToken();
|
|
525
|
+
|
|
526
|
+
return new OpenLifeLog({
|
|
527
|
+
apiKey: token,
|
|
528
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL!,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Usage
|
|
533
|
+
const client = await getClient();
|
|
534
|
+
const user = await client.users.me();
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Using with Supabase Auth
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
import { createClient } from '@supabase/supabase-js';
|
|
541
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
542
|
+
|
|
543
|
+
const supabase = createClient(
|
|
544
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
545
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
async function getClient() {
|
|
549
|
+
const {
|
|
550
|
+
data: { session },
|
|
551
|
+
} = await supabase.auth.getSession();
|
|
552
|
+
|
|
553
|
+
if (!session) {
|
|
554
|
+
throw new Error('Not authenticated');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return new OpenLifeLog({
|
|
558
|
+
apiKey: session.access_token,
|
|
559
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL!,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Custom Token Refresh
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
568
|
+
|
|
569
|
+
class AuthenticatedOpenLifeLog {
|
|
570
|
+
private client: OpenLifeLog;
|
|
571
|
+
private refreshToken: string;
|
|
572
|
+
|
|
573
|
+
constructor(token: string, refreshToken: string) {
|
|
574
|
+
this.client = new OpenLifeLog({
|
|
575
|
+
apiKey: token,
|
|
576
|
+
baseUrl: process.env.API_URL!,
|
|
577
|
+
});
|
|
578
|
+
this.refreshToken = refreshToken;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async refreshAccessToken() {
|
|
582
|
+
// Your token refresh logic
|
|
583
|
+
const response = await fetch('/api/auth/refresh', {
|
|
584
|
+
method: 'POST',
|
|
585
|
+
body: JSON.stringify({ refreshToken: this.refreshToken }),
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
const { accessToken } = await response.json();
|
|
589
|
+
|
|
590
|
+
// Update client with new token
|
|
591
|
+
this.client.setApiKey(accessToken);
|
|
592
|
+
|
|
593
|
+
return accessToken;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async withRetry<T>(operation: () => Promise<T>): Promise<T> {
|
|
597
|
+
try {
|
|
598
|
+
return await operation();
|
|
599
|
+
} catch (error: any) {
|
|
600
|
+
// If auth error, try refreshing token once
|
|
601
|
+
if (error.statusCode === 401) {
|
|
602
|
+
await this.refreshAccessToken();
|
|
603
|
+
return await operation();
|
|
604
|
+
}
|
|
605
|
+
throw error;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Expose client methods with auto-refresh
|
|
610
|
+
async logFood(data: any) {
|
|
611
|
+
return this.withRetry(() => this.client.logFood(data));
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
async getTodayNutrition() {
|
|
615
|
+
return this.withRetry(() => this.client.getTodayNutrition());
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Add more methods as needed...
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Usage
|
|
622
|
+
const client = new AuthenticatedOpenLifeLog(accessToken, refreshToken);
|
|
623
|
+
await client.logFood({ ... }); // Automatically refreshes token if needed
|
|
624
|
+
```
|