timeback 0.1.0 → 0.1.1

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 CHANGED
@@ -1,6 +1,25 @@
1
1
  # Timeback SDK
2
2
 
3
- TypeScript SDK for integrating with Timeback, providing both server-side BFF logic and client-side React components.
3
+ TypeScript SDK for integrating Timeback into your application. Provides server-side route handlers and client-side components for activity tracking and SSO authentication.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Quick Start](#quick-start)
9
+ - [Server Adapters](#server-adapters)
10
+ - [Next.js](#nextjs)
11
+ - [Nuxt](#nuxt)
12
+ - [SvelteKit](#sveltekit)
13
+ - [SolidStart](#solidstart)
14
+ - [TanStack Start](#tanstack-start)
15
+ - [Express](#express)
16
+ - [Client Adapters](#client-adapters)
17
+ - [React](#react)
18
+ - [Vue](#vue)
19
+ - [Svelte](#svelte)
20
+ - [Solid](#solid)
21
+ - [Identity Modes](#identity-modes)
22
+ - [Activity Tracking](#activity-tracking)
4
23
 
5
24
  ## Installation
6
25
 
@@ -10,210 +29,358 @@ npm install timeback
10
29
  bun add timeback
11
30
  ```
12
31
 
13
- ## Server-Side Setup
32
+ ## Quick Start
14
33
 
15
- The server SDK auto-discovers your `timeback.config.ts` file and provides route handlers for Next.js.
34
+ 1. Create a server instance with your credentials
35
+ 2. Mount the route handlers for your framework
36
+ 3. Wrap your app with the client provider
37
+ 4. Use hooks/composables to track activities
16
38
 
17
- ### Configuration
39
+ ## Server Adapters
18
40
 
19
- ```typescript
20
- // app/lib/timeback.ts
21
- import { createTimeback } from 'timeback'
41
+ All server adapters use the same core configuration:
22
42
 
23
- export const timeback = await createTimeback({
24
- env: 'production', // 'development' | 'staging' | 'production'
43
+ ```typescript
44
+ // lib/timeback.ts
45
+ import { createServer } from 'timeback'
25
46
 
26
- // API credentials for Timeback API calls
47
+ export const timeback = await createServer({
48
+ env: 'staging', // 'development' | 'staging' | 'production'
27
49
  api: {
28
50
  clientId: process.env.TIMEBACK_API_CLIENT_ID!,
29
51
  clientSecret: process.env.TIMEBACK_API_CLIENT_SECRET!,
30
52
  },
31
-
32
- // Identity configuration
33
53
  identity: {
34
- // Option A: Timeback SSO
35
54
  mode: 'sso',
36
- clientId: process.env.TIMEBACK_SSO_CLIENT_ID!,
37
- clientSecret: process.env.TIMEBACK_SSO_CLIENT_SECRET!,
38
-
39
- // Option B: Custom identity provider (Clerk, Auth0, Supabase, etc.)
40
- mode: 'custom',
41
- getUser: async req => {
42
- const session = await getSession(req)
43
- return {
44
- id: session.userId,
45
- email: session.user.email,
46
- name: session.user.name,
47
- }
55
+ clientId: process.env.AWS_COGNITO_CLIENT_ID!,
56
+ clientSecret: process.env.AWS_COGNITO_CLIENT_SECRET!,
57
+ redirectUri: 'http://localhost:3000/api/auth/sso/callback/timeback',
58
+ onCallbackSuccess: ({ user, redirect }) => {
59
+ // Set session, then redirect
60
+ return redirect('/')
48
61
  },
62
+ onCallbackError: ({ error, redirect }) => {
63
+ console.error('SSO Error:', error)
64
+ return redirect('/?error=sso_failed')
65
+ },
66
+ getUser: () => getSession(), // Return current user or undefined
49
67
  },
50
68
  })
51
69
  ```
52
70
 
53
- ### Next.js Route Handlers
71
+ ### Next.js
54
72
 
55
73
  ```typescript
56
74
  // app/api/timeback/[...timeback]/route.ts
75
+ import { toNextjsHandler } from 'timeback/nextjs'
76
+
57
77
  import { timeback } from '@/lib/timeback'
58
78
 
59
- export const { GET, POST } = timeback.handlers()
79
+ export const { GET, POST } = toNextjsHandler(timeback)
60
80
  ```
61
81
 
62
- This exposes:
82
+ ### Nuxt
63
83
 
64
- - `POST /api/timeback/activity` - Receives activity events from client
65
- - `GET /api/timeback/identity/session` - Returns current user
66
- - `GET /api/timeback/identity/signin` - Initiates SSO flow (SSO mode only)
67
- - `GET /api/timeback/identity/callback` - SSO callback (SSO mode only)
84
+ ```typescript
85
+ // server/middleware/timeback.ts
86
+ import { nuxtHandler } from 'timeback/nuxt'
87
+
88
+ import { timeback } from '../lib/timeback'
68
89
 
69
- ## Client-Side Setup
90
+ export default defineEventHandler(async event => {
91
+ const response = await nuxtHandler({
92
+ timeback,
93
+ event,
94
+ })
95
+ if (response) return response
96
+ })
97
+ ```
70
98
 
71
- ### Configuration
99
+ ### SvelteKit
72
100
 
73
101
  ```typescript
74
- // app/lib/timeback-client.ts
75
- import { createTimebackClient } from 'timeback/client/react'
102
+ // src/hooks.server.ts
103
+ import { building } from '$app/environment'
104
+ import { timeback } from '$lib/timeback'
105
+ import { svelteKitHandler } from 'timeback/svelte-kit'
106
+
107
+ import type { Handle } from '@sveltejs/kit'
108
+
109
+ export const handle: Handle = ({ event, resolve }) => {
110
+ return svelteKitHandler({
111
+ timeback,
112
+ event,
113
+ resolve,
114
+ building,
115
+ })
116
+ }
117
+ ```
76
118
 
77
- // Zero config - baseURL defaults to window.location.origin + '/api/timeback'
78
- export const timebackClient = createTimebackClient()
119
+ ### SolidStart
79
120
 
80
- // Or with explicit URL
81
- export const timebackClient = createTimebackClient({
82
- baseURL: 'https://myapp.com/api/timeback',
121
+ ```typescript
122
+ // src/middleware.ts
123
+ import { createMiddleware } from '@solidjs/start/middleware'
124
+ import { timeback } from '~/lib/timeback'
125
+ import { solidStartHandler } from 'timeback/solid-start'
126
+
127
+ export default createMiddleware({
128
+ onRequest: [
129
+ async event => {
130
+ const response = await solidStartHandler({
131
+ timeback,
132
+ event,
133
+ })
134
+ if (response) return response
135
+ },
136
+ ],
83
137
  })
84
138
  ```
85
139
 
86
- ### React Provider
140
+ ### TanStack Start
141
+
142
+ ```typescript
143
+ // src/routes/api/timeback/$.ts
144
+ import { createFileRoute } from '@tanstack/react-router'
145
+ import { toTanStackStartHandler } from 'timeback/tanstack-start'
146
+
147
+ import { timeback } from '@/lib/timeback'
148
+
149
+ const handlers = toTanStackStartHandler(timeback)
150
+
151
+ export const Route = createFileRoute('/api/timeback/$')({
152
+ server: { handlers },
153
+ })
154
+ ```
155
+
156
+ ### Express
157
+
158
+ ```typescript
159
+ // server.ts
160
+ import express from 'express'
161
+ import { toExpressMiddleware } from 'timeback/express'
162
+
163
+ import { timeback } from './lib/timeback'
164
+
165
+ const app = express()
166
+ app.use(express.json())
167
+ app.use('/api/timeback', toExpressMiddleware(timeback))
168
+ ```
169
+
170
+ ## Client Adapters
171
+
172
+ ### React
87
173
 
88
174
  ```tsx
89
175
  // app/providers.tsx
90
- import { TimebackProvider } from 'timeback/client/react'
176
+ 'use client'
91
177
 
92
- import { timebackClient } from '@/lib/timeback-client'
178
+ import { TimebackProvider } from 'timeback/react'
93
179
 
94
- export function Providers({ children }) {
95
- return <TimebackProvider client={timebackClient}>{children}</TimebackProvider>
180
+ export function Providers({ children }: { children: React.ReactNode }) {
181
+ return <TimebackProvider>{children}</TimebackProvider>
96
182
  }
97
183
  ```
98
184
 
99
- ### Hooks
100
-
101
185
  ```tsx
102
- import { useActivity, useTimeback } from 'timeback/client/react'
186
+ // components/ActivityTracker.tsx
187
+ import { useEffect } from 'react'
188
+ import { SignInButton, useTimeback } from 'timeback/react'
103
189
 
104
- function UserMenu() {
105
- const { user, isLoading, identityMode, signIn, signOut } = useTimeback()
190
+ function MyComponent() {
191
+ const timeback = useTimeback()
106
192
 
107
- if (isLoading) return <Spinner />
193
+ useEffect(() => {
194
+ if (!timeback) return
108
195
 
109
- if (!user) {
110
- return <button onClick={signIn}>Sign In</button>
111
- }
196
+ const activity = timeback.activity
197
+ .new({ id: 'lesson-1', name: 'Intro', courseCode: 'MATH-101' })
198
+ .start()
112
199
 
113
- return (
114
- <div>
115
- <span>{user.name}</span>
116
- <button onClick={signOut}>Sign Out</button>
117
- </div>
118
- )
200
+ return () => {
201
+ activity.end()
202
+ }
203
+ }, [timeback])
204
+
205
+ return <SignInButton size="lg" />
119
206
  }
120
207
  ```
121
208
 
122
- ### Activity Tracking
209
+ ### Vue
123
210
 
124
- #### React Hook
211
+ ```vue
212
+ <!-- app.vue -->
213
+ <script setup>
214
+ import { TimebackProvider } from 'timeback/vue'
215
+ </script>
125
216
 
126
- ```tsx
127
- function LessonPlayer({ lessonId, courseCode }) {
128
- // Starts on mount, ends on unmount, restarts if deps change
129
- const activity = useActivity({
130
- type: 'lesson',
131
- objectId: lessonId,
132
- courseCode,
217
+ <template>
218
+ <TimebackProvider>
219
+ <NuxtPage />
220
+ </TimebackProvider>
221
+ </template>
222
+ ```
223
+
224
+ ```vue
225
+ <!-- components/ActivityTracker.vue -->
226
+ <script setup>
227
+ import { SignInButton, useTimeback } from 'timeback/vue'
228
+ import { onMounted, onUnmounted } from 'vue'
229
+
230
+ const timeback = useTimeback()
231
+ let activity
232
+
233
+ onMounted(() => {
234
+ if (timeback.value) {
235
+ activity = timeback.value.activity
236
+ .new({ id: 'lesson-1', name: 'Intro', courseCode: 'MATH-101' })
237
+ .start()
238
+ }
239
+ })
240
+
241
+ onUnmounted(() => activity?.end())
242
+ </script>
243
+
244
+ <template>
245
+ <SignInButton size="lg" />
246
+ </template>
247
+ ```
248
+
249
+ ### Svelte
250
+
251
+ ```svelte
252
+ <!-- +layout.svelte -->
253
+ <script>
254
+ import { initTimeback } from 'timeback/svelte'
255
+
256
+ initTimeback()
257
+
258
+ let { children } = $props()
259
+ </script>
260
+
261
+ {@render children()}
262
+ ```
263
+
264
+ ```svelte
265
+ <!-- +page.svelte -->
266
+ <script>
267
+ import { onMount, onDestroy } from 'svelte'
268
+ import { SignInButton, timeback } from 'timeback/svelte'
269
+
270
+ let activity
271
+
272
+ onMount(() => {
273
+ if ($timeback) {
274
+ activity = $timeback.activity
275
+ .new({ id: 'lesson-1', name: 'Intro', courseCode: 'MATH-101' })
276
+ .start()
277
+ }
133
278
  })
134
279
 
280
+ onDestroy(() => activity?.end())
281
+ </script>
282
+
283
+ <SignInButton size="lg" />
284
+ ```
285
+
286
+ ### Solid
287
+
288
+ ```tsx
289
+ // app.tsx
290
+ import { TimebackProvider } from 'timeback/solid'
291
+
292
+ export default function App() {
135
293
  return (
136
- <>
137
- <button onClick={() => activity.pause()}>Pause</button>
138
- <button onClick={() => activity.resume()}>Resume</button>
139
- <span>Time: {activity.elapsedMs}ms</span>
140
- </>
294
+ <TimebackProvider>
295
+ <Router root={props => <Suspense>{props.children}</Suspense>}>
296
+ <FileRoutes />
297
+ </Router>
298
+ </TimebackProvider>
141
299
  )
142
300
  }
143
301
  ```
144
302
 
145
- #### Vanilla API
303
+ ```tsx
304
+ // components/ActivityTracker.tsx
305
+ import { onCleanup, onMount } from 'solid-js'
306
+ import { SignInButton, useTimeback } from 'timeback/solid'
146
307
 
147
- ```typescript
148
- import { createTimebackClient } from 'timeback/client'
308
+ function MyComponent() {
309
+ const timeback = useTimeback()
310
+ let activity
149
311
 
150
- const client = createTimebackClient()
312
+ onMount(() => {
313
+ if (!timeback) return
151
314
 
152
- // Start tracking
153
- const activity = client.startActivity({
154
- type: 'lesson',
155
- objectId: 'lesson-123',
156
- courseCode: 'FASTMATH-1',
157
- })
315
+ activity = timeback.activity
316
+ .new({ id: 'lesson-1', name: 'Intro', courseCode: 'MATH-101' })
317
+ .start()
318
+ })
158
319
 
159
- // Pause/resume as needed
160
- activity.pause()
161
- activity.resume()
320
+ onCleanup(() => activity?.end())
162
321
 
163
- // End tracking - sends ActivityCompletedEvent + TimeSpentEvent to server
164
- await activity.end()
322
+ return <SignInButton size="lg" />
323
+ }
165
324
  ```
166
325
 
167
326
  ## Identity Modes
168
327
 
169
- ### Timeback SSO
328
+ ### SSO Mode
170
329
 
171
- Uses Timeback as the identity provider via OIDC. Users sign in through Timeback's authentication flow.
330
+ Uses Timeback as the identity provider via OIDC:
172
331
 
173
332
  ```typescript
174
333
  identity: {
175
- mode: 'sso',
176
- clientId: process.env.TIMEBACK_SSO_CLIENT_ID!,
177
- clientSecret: process.env.TIMEBACK_SSO_CLIENT_SECRET!,
334
+ mode: 'sso',
335
+ clientId: process.env.AWS_COGNITO_CLIENT_ID!,
336
+ clientSecret: process.env.AWS_COGNITO_CLIENT_SECRET!,
337
+ redirectUri: 'http://localhost:3000/api/auth/sso/callback/timeback',
338
+ onCallbackSuccess: ({ user, state, redirect }) => redirect('/'),
339
+ onCallbackError: ({ error, redirect }) => redirect('/?error=sso_failed'),
340
+ getUser: () => getCurrentSession(),
178
341
  }
179
342
  ```
180
343
 
181
- ### Custom Identity
344
+ ### Custom Mode
182
345
 
183
- For apps with existing authentication (Clerk, Auth0, Supabase, etc.), provide a `getUser` function that returns the current user.
346
+ For apps with existing auth (Clerk, Auth0, Supabase, etc.):
184
347
 
185
348
  ```typescript
186
349
  identity: {
187
- mode: 'custom',
188
- getUser: async (req) => {
189
- // Use your existing auth system
190
- const session = await clerk.getSession(req)
191
- return {
192
- id: session.userId,
193
- email: session.user.email,
194
- name: session.user.name,
195
- }
196
- },
350
+ mode: 'custom',
351
+ getUser: async (req) => {
352
+ const session = await getSession(req)
353
+ if (!session) return undefined
354
+ return { id: session.userId, email: session.email, name: session.name }
355
+ },
197
356
  }
198
357
  ```
199
358
 
200
- ## API Reference
201
-
202
- ### Server
359
+ ## Activity Tracking
203
360
 
204
- - `createTimeback(config)` - Create a Timeback server instance
205
- - `timeback.handlers()` - Get Next.js route handlers
361
+ Activities track time spent on learning content:
206
362
 
207
- ### Client
363
+ ```typescript
364
+ // Start an activity
365
+ const activity = timeback.activity
366
+ .new({
367
+ id: 'lesson-123',
368
+ name: 'Introduction to Fractions',
369
+ courseCode: 'MATH-101',
370
+ })
371
+ .start()
208
372
 
209
- - `createTimebackClient(config?)` - Create a Timeback client
210
- - `client.startActivity(params)` - Start tracking an activity
211
- - `client.signIn()` - Initiate SSO sign-in
212
- - `client.signOut()` - Sign out
213
- - `client.fetchSession()` - Fetch current session
373
+ // Pause/resume
374
+ activity.pause()
375
+ activity.resume()
214
376
 
215
- ### React
377
+ // End with metrics
378
+ await activity.end({
379
+ totalQuestions: 10,
380
+ correctQuestions: 8,
381
+ xpEarned: 80,
382
+ masteredUnits: 1,
383
+ })
384
+ ```
216
385
 
217
- - `TimebackProvider` - Context provider
218
- - `useTimeback()` - Hook for identity state and actions
219
- - `useActivity(params)` - Hook for activity tracking with lifecycle management
386
+ The SDK automatically sends activity data to your server, which forwards it to the Timeback API.