@timeback/sdk 0.1.4

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 (136) hide show
  1. package/README.md +612 -0
  2. package/dist/client/adapters/react/SignInButton.d.ts +60 -0
  3. package/dist/client/adapters/react/SignInButton.d.ts.map +1 -0
  4. package/dist/client/adapters/react/index.d.ts +47 -0
  5. package/dist/client/adapters/react/index.d.ts.map +1 -0
  6. package/dist/client/adapters/react/index.js +478 -0
  7. package/dist/client/adapters/react/provider.d.ts +78 -0
  8. package/dist/client/adapters/react/provider.d.ts.map +1 -0
  9. package/dist/client/adapters/solid/SignInButton.d.ts +52 -0
  10. package/dist/client/adapters/solid/SignInButton.d.ts.map +1 -0
  11. package/dist/client/adapters/solid/SignInButton.tsx +321 -0
  12. package/dist/client/adapters/solid/context.d.ts +73 -0
  13. package/dist/client/adapters/solid/context.d.ts.map +1 -0
  14. package/dist/client/adapters/solid/context.tsx +91 -0
  15. package/dist/client/adapters/solid/index.d.ts +46 -0
  16. package/dist/client/adapters/solid/index.d.ts.map +1 -0
  17. package/dist/client/adapters/solid/index.ts +50 -0
  18. package/dist/client/adapters/svelte/SignInButton.svelte +234 -0
  19. package/dist/client/adapters/svelte/SignInButton.svelte.d.ts +24 -0
  20. package/dist/client/adapters/svelte/index.d.ts +37 -0
  21. package/dist/client/adapters/svelte/index.d.ts.map +1 -0
  22. package/dist/client/adapters/svelte/index.ts +42 -0
  23. package/dist/client/adapters/svelte/stores.d.ts +66 -0
  24. package/dist/client/adapters/svelte/stores.d.ts.map +1 -0
  25. package/dist/client/adapters/svelte/stores.ts +143 -0
  26. package/dist/client/adapters/vue/SignInButton.vue +260 -0
  27. package/dist/client/adapters/vue/SignInButton.vue.d.ts +53 -0
  28. package/dist/client/adapters/vue/index.d.ts +43 -0
  29. package/dist/client/adapters/vue/index.d.ts.map +1 -0
  30. package/dist/client/adapters/vue/index.ts +48 -0
  31. package/dist/client/adapters/vue/provider.d.ts +94 -0
  32. package/dist/client/adapters/vue/provider.d.ts.map +1 -0
  33. package/dist/client/adapters/vue/provider.ts +147 -0
  34. package/dist/client/index.d.ts +9 -0
  35. package/dist/client/index.d.ts.map +1 -0
  36. package/dist/client/lib/activity/activity.class.d.ts +73 -0
  37. package/dist/client/lib/activity/activity.class.d.ts.map +1 -0
  38. package/dist/client/lib/activity/activity.d.ts +16 -0
  39. package/dist/client/lib/activity/activity.d.ts.map +1 -0
  40. package/dist/client/lib/activity/index.d.ts +6 -0
  41. package/dist/client/lib/activity/index.d.ts.map +1 -0
  42. package/dist/client/lib/utils.d.ts +20 -0
  43. package/dist/client/lib/utils.d.ts.map +1 -0
  44. package/dist/client/namespaces/activity.d.ts +41 -0
  45. package/dist/client/namespaces/activity.d.ts.map +1 -0
  46. package/dist/client/namespaces/auth.d.ts +33 -0
  47. package/dist/client/namespaces/auth.d.ts.map +1 -0
  48. package/dist/client/namespaces/index.d.ts +7 -0
  49. package/dist/client/namespaces/index.d.ts.map +1 -0
  50. package/dist/client/namespaces/user.d.ts +29 -0
  51. package/dist/client/namespaces/user.d.ts.map +1 -0
  52. package/dist/client/timeback-client.class.d.ts +37 -0
  53. package/dist/client/timeback-client.class.d.ts.map +1 -0
  54. package/dist/client/timeback-client.d.ts +29 -0
  55. package/dist/client/timeback-client.d.ts.map +1 -0
  56. package/dist/client.d.ts +30 -0
  57. package/dist/client.d.ts.map +1 -0
  58. package/dist/client.js +198 -0
  59. package/dist/config.d.ts +20 -0
  60. package/dist/config.d.ts.map +1 -0
  61. package/dist/config.js +0 -0
  62. package/dist/edge.d.ts +13 -0
  63. package/dist/edge.d.ts.map +1 -0
  64. package/dist/edge.js +1149 -0
  65. package/dist/identity.d.ts +14 -0
  66. package/dist/identity.d.ts.map +1 -0
  67. package/dist/identity.js +1019 -0
  68. package/dist/index.d.ts +48 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +84921 -0
  71. package/dist/server/adapters/express.d.ts +66 -0
  72. package/dist/server/adapters/express.d.ts.map +1 -0
  73. package/dist/server/adapters/express.js +67332 -0
  74. package/dist/server/adapters/native.d.ts +47 -0
  75. package/dist/server/adapters/native.d.ts.map +1 -0
  76. package/dist/server/adapters/native.js +190 -0
  77. package/dist/server/adapters/nextjs.d.ts +32 -0
  78. package/dist/server/adapters/nextjs.d.ts.map +1 -0
  79. package/dist/server/adapters/nextjs.js +202 -0
  80. package/dist/server/adapters/nuxt.d.ts +98 -0
  81. package/dist/server/adapters/nuxt.d.ts.map +1 -0
  82. package/dist/server/adapters/nuxt.js +67401 -0
  83. package/dist/server/adapters/solid-start.d.ts +63 -0
  84. package/dist/server/adapters/solid-start.d.ts.map +1 -0
  85. package/dist/server/adapters/solid-start.js +67300 -0
  86. package/dist/server/adapters/svelte-kit.d.ts +84 -0
  87. package/dist/server/adapters/svelte-kit.d.ts.map +1 -0
  88. package/dist/server/adapters/svelte-kit.js +243 -0
  89. package/dist/server/adapters/tanstack-start.d.ts +42 -0
  90. package/dist/server/adapters/tanstack-start.d.ts.map +1 -0
  91. package/dist/server/adapters/tanstack-start.js +67278 -0
  92. package/dist/server/adapters/types.d.ts +294 -0
  93. package/dist/server/adapters/types.d.ts.map +1 -0
  94. package/dist/server/adapters/utils.d.ts +76 -0
  95. package/dist/server/adapters/utils.d.ts.map +1 -0
  96. package/dist/server/handlers/activity.d.ts +28 -0
  97. package/dist/server/handlers/activity.d.ts.map +1 -0
  98. package/dist/server/handlers/identity-full.d.ts +28 -0
  99. package/dist/server/handlers/identity-full.d.ts.map +1 -0
  100. package/dist/server/handlers/identity-only.d.ts +22 -0
  101. package/dist/server/handlers/identity-only.d.ts.map +1 -0
  102. package/dist/server/handlers/index.d.ts +9 -0
  103. package/dist/server/handlers/index.d.ts.map +1 -0
  104. package/dist/server/handlers/user.d.ts +31 -0
  105. package/dist/server/handlers/user.d.ts.map +1 -0
  106. package/dist/server/index.d.ts +9 -0
  107. package/dist/server/index.d.ts.map +1 -0
  108. package/dist/server/lib/build-activity-events.d.ts +39 -0
  109. package/dist/server/lib/build-activity-events.d.ts.map +1 -0
  110. package/dist/server/lib/build-user-profile.d.ts +62 -0
  111. package/dist/server/lib/build-user-profile.d.ts.map +1 -0
  112. package/dist/server/lib/index.d.ts +14 -0
  113. package/dist/server/lib/index.d.ts.map +1 -0
  114. package/dist/server/lib/logger.d.ts +21 -0
  115. package/dist/server/lib/logger.d.ts.map +1 -0
  116. package/dist/server/lib/oidc.d.ts +76 -0
  117. package/dist/server/lib/oidc.d.ts.map +1 -0
  118. package/dist/server/lib/resolve-activity-course.d.ts +22 -0
  119. package/dist/server/lib/resolve-activity-course.d.ts.map +1 -0
  120. package/dist/server/lib/resolve-timeback-id.d.ts +28 -0
  121. package/dist/server/lib/resolve-timeback-id.d.ts.map +1 -0
  122. package/dist/server/lib/resolve-timeback-user.d.ts +42 -0
  123. package/dist/server/lib/resolve-timeback-user.d.ts.map +1 -0
  124. package/dist/server/lib/utils.d.ts +54 -0
  125. package/dist/server/lib/utils.d.ts.map +1 -0
  126. package/dist/server/timeback-identity.d.ts +19 -0
  127. package/dist/server/timeback-identity.d.ts.map +1 -0
  128. package/dist/server/timeback.d.ts +68 -0
  129. package/dist/server/timeback.d.ts.map +1 -0
  130. package/dist/server/types.d.ts +421 -0
  131. package/dist/server/types.d.ts.map +1 -0
  132. package/dist/shared/constants.d.ts +18 -0
  133. package/dist/shared/constants.d.ts.map +1 -0
  134. package/dist/shared/types.d.ts +159 -0
  135. package/dist/shared/types.d.ts.map +1 -0
  136. package/package.json +119 -0
package/README.md ADDED
@@ -0,0 +1,612 @@
1
+ # Timeback SDK
2
+
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
+ - [Identity-Only Integration](#identity-only-integration)
23
+ - [Activity Tracking](#activity-tracking)
24
+ - [Advanced: Direct API Access](#advanced-direct-api-access)
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install timeback
30
+ # or
31
+ bun add timeback
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ 1. Create a server instance with your credentials
37
+ 2. Mount the route handlers for your framework
38
+ 3. Wrap your app with the client provider
39
+ 4. Use hooks/composables to track activities
40
+
41
+ ## Server Adapters
42
+
43
+ All server adapters use the same core configuration:
44
+
45
+ ```typescript
46
+ // lib/timeback.ts
47
+ import { createTimeback } from '@timeback/sdk'
48
+
49
+ export const timeback = await createTimeback({
50
+ env: 'staging', // 'local' | 'staging' | 'production'
51
+ api: {
52
+ clientId: process.env.TIMEBACK_API_CLIENT_ID!,
53
+ clientSecret: process.env.TIMEBACK_API_CLIENT_SECRET!,
54
+ },
55
+ identity: {
56
+ mode: 'sso',
57
+ clientId: process.env.AWS_COGNITO_CLIENT_ID!,
58
+ clientSecret: process.env.AWS_COGNITO_CLIENT_SECRET!,
59
+ redirectUri: 'http://localhost:3000/api/auth/sso/callback/timeback',
60
+ onCallbackSuccess: async ({ user, state, redirect }) => {
61
+ // user.id is the timebackId (canonical stable identifier)
62
+ await setSession({ id: user.id, email: user.email })
63
+ return redirect(state?.returnTo ?? '/')
64
+ },
65
+ onCallbackError: ({ error, redirect }) => {
66
+ console.error('SSO Error:', error)
67
+ return redirect('/?error=sso_failed')
68
+ },
69
+ getUser: () => getSession(), // Return current user or undefined
70
+ },
71
+ })
72
+ ```
73
+
74
+ ### Next.js
75
+
76
+ ```typescript
77
+ // app/api/timeback/[...timeback]/route.ts
78
+ import { toNextjsHandler } from '@timeback/sdk/nextjs'
79
+
80
+ import { timeback } from '@/lib/timeback'
81
+
82
+ export const { GET, POST } = toNextjsHandler(timeback)
83
+ ```
84
+
85
+ ### Nuxt
86
+
87
+ ```typescript
88
+ // server/middleware/timeback.ts
89
+ import { nuxtHandler } from '@timeback/sdk/nuxt'
90
+
91
+ import { timeback } from '../lib/timeback'
92
+
93
+ export default defineEventHandler(async event => {
94
+ const response = await nuxtHandler({
95
+ timeback,
96
+ event,
97
+ })
98
+ if (response) return response
99
+ })
100
+ ```
101
+
102
+ ### SvelteKit
103
+
104
+ ```typescript
105
+ // src/hooks.server.ts
106
+ import { building } from '$app/environment'
107
+ import { timeback } from '$lib/timeback'
108
+ import { svelteKitHandler } from '@timeback/sdk/svelte-kit'
109
+
110
+ import type { Handle } from '@sveltejs/kit'
111
+
112
+ export const handle: Handle = ({ event, resolve }) => {
113
+ return svelteKitHandler({
114
+ timeback,
115
+ event,
116
+ resolve,
117
+ building,
118
+ })
119
+ }
120
+ ```
121
+
122
+ ### SolidStart
123
+
124
+ ```typescript
125
+ // src/middleware.ts
126
+ import { createMiddleware } from '@solidjs/start/middleware'
127
+ import { timeback } from '~/lib/timeback'
128
+ import { solidStartHandler } from '@timeback/sdk/solid-start'
129
+
130
+ export default createMiddleware({
131
+ onRequest: [
132
+ async event => {
133
+ const response = await solidStartHandler({
134
+ timeback,
135
+ event,
136
+ })
137
+ if (response) return response
138
+ },
139
+ ],
140
+ })
141
+ ```
142
+
143
+ ### TanStack Start
144
+
145
+ ```typescript
146
+ // src/routes/api/timeback/$.ts
147
+ import { createFileRoute } from '@tanstack/react-router'
148
+ import { toTanStackStartHandler } from '@timeback/sdk/tanstack-start'
149
+
150
+ import { timeback } from '@/lib/timeback'
151
+
152
+ const handlers = toTanStackStartHandler(timeback)
153
+
154
+ export const Route = createFileRoute('/api/timeback/$')({
155
+ server: { handlers },
156
+ })
157
+ ```
158
+
159
+ ### Express
160
+
161
+ ```typescript
162
+ // server.ts
163
+ import express from 'express'
164
+ import { toExpressMiddleware } from '@timeback/sdk/express'
165
+
166
+ import { timeback } from './lib/timeback'
167
+
168
+ const app = express()
169
+ app.use(express.json())
170
+ app.use('/api/timeback', toExpressMiddleware(timeback))
171
+ ```
172
+
173
+ ## Client Adapters
174
+
175
+ ### React
176
+
177
+ ```tsx
178
+ // app/providers.tsx
179
+ 'use client'
180
+
181
+ import { TimebackProvider } from '@timeback/sdk/react'
182
+
183
+ export function Providers({ children }: { children: React.ReactNode }) {
184
+ return <TimebackProvider>{children}</TimebackProvider>
185
+ }
186
+ ```
187
+
188
+ ```tsx
189
+ // components/ActivityTracker.tsx
190
+ import { useEffect } from 'react'
191
+ import { SignInButton, useTimeback } from '@timeback/sdk/react'
192
+
193
+ function MyComponent() {
194
+ const timeback = useTimeback()
195
+
196
+ useEffect(() => {
197
+ if (!timeback) return
198
+
199
+ const activity = timeback.activity
200
+ .new({ id: 'lesson-1', name: 'Intro', course: { subject: 'Math', grade: 3 } })
201
+ .start()
202
+
203
+ return () => {
204
+ activity.end()
205
+ }
206
+ }, [timeback])
207
+
208
+ return <SignInButton size="lg" />
209
+ }
210
+ ```
211
+
212
+ ### Vue
213
+
214
+ ```vue
215
+ <!-- app.vue -->
216
+ <script setup>
217
+ import { TimebackProvider } from '@timeback/sdk/vue'
218
+ </script>
219
+
220
+ <template>
221
+ <TimebackProvider>
222
+ <NuxtPage />
223
+ </TimebackProvider>
224
+ </template>
225
+ ```
226
+
227
+ ```vue
228
+ <!-- components/ActivityTracker.vue -->
229
+ <script setup>
230
+ import { SignInButton, useTimeback } from '@timeback/sdk/vue'
231
+ import { onMounted, onUnmounted } from 'vue'
232
+
233
+ const timeback = useTimeback()
234
+ let activity
235
+
236
+ onMounted(() => {
237
+ if (timeback.value) {
238
+ activity = timeback.value.activity
239
+ .new({ id: 'lesson-1', name: 'Intro', course: { subject: 'Math', grade: 3 } })
240
+ .start()
241
+ }
242
+ })
243
+
244
+ onUnmounted(() => activity?.end())
245
+ </script>
246
+
247
+ <template>
248
+ <SignInButton size="lg" />
249
+ </template>
250
+ ```
251
+
252
+ ### Svelte
253
+
254
+ ```svelte
255
+ <!-- +layout.svelte -->
256
+ <script>
257
+ import { initTimeback } from '@timeback/sdk/svelte'
258
+
259
+ initTimeback()
260
+
261
+ let { children } = $props()
262
+ </script>
263
+
264
+ {@render children()}
265
+ ```
266
+
267
+ ```svelte
268
+ <!-- +page.svelte -->
269
+ <script>
270
+ import { onMount, onDestroy } from 'svelte'
271
+ import { SignInButton, timeback } from '@timeback/sdk/svelte'
272
+
273
+ let activity
274
+
275
+ onMount(() => {
276
+ if ($timeback) {
277
+ activity = $timeback.activity
278
+ .new({ id: 'lesson-1', name: 'Intro', course: { subject: 'Math', grade: 3 } })
279
+ .start()
280
+ }
281
+ })
282
+
283
+ onDestroy(() => activity?.end())
284
+ </script>
285
+
286
+ <SignInButton size="lg" />
287
+ ```
288
+
289
+ ### Solid
290
+
291
+ ```tsx
292
+ // app.tsx
293
+ import { TimebackProvider } from '@timeback/sdk/solid'
294
+
295
+ export default function App() {
296
+ return (
297
+ <TimebackProvider>
298
+ <Router root={props => <Suspense>{props.children}</Suspense>}>
299
+ <FileRoutes />
300
+ </Router>
301
+ </TimebackProvider>
302
+ )
303
+ }
304
+ ```
305
+
306
+ ```tsx
307
+ // components/ActivityTracker.tsx
308
+ import { onCleanup, onMount } from 'solid-js'
309
+ import { SignInButton, useTimeback } from '@timeback/sdk/solid'
310
+
311
+ function MyComponent() {
312
+ const timeback = useTimeback()
313
+ let activity
314
+
315
+ onMount(() => {
316
+ if (!timeback) return
317
+
318
+ activity = timeback.activity
319
+ .new({ id: 'lesson-1', name: 'Intro', course: { subject: 'Math', grade: 3 } })
320
+ .start()
321
+ })
322
+
323
+ onCleanup(() => activity?.end())
324
+
325
+ return <SignInButton size="lg" />
326
+ }
327
+ ```
328
+
329
+ ## Identity Modes
330
+
331
+ ### SSO Mode
332
+
333
+ Uses Timeback as the identity provider via OIDC. When using `createTimeback()`, the SDK automatically resolves the Timeback user by email and returns an enriched `TimebackAuthUser` with `user.id` being the canonical `timebackId` (stable identifier).
334
+
335
+ ```typescript
336
+ identity: {
337
+ mode: 'sso',
338
+ clientId: process.env.AWS_COGNITO_CLIENT_ID!,
339
+ clientSecret: process.env.AWS_COGNITO_CLIENT_SECRET!,
340
+ redirectUri: 'http://localhost:3000/api/auth/sso/callback/timeback',
341
+ onCallbackSuccess: async ({ user, idp, state, redirect }) => {
342
+ // user.id is the timebackId (canonical stable identifier)
343
+ // user.email, user.name come from Timeback profile
344
+ // user.claims contains IdP data (sub, firstName, lastName, pictureUrl)
345
+ // idp.tokens and idp.userInfo contain raw OIDC data if needed
346
+ await setSession({ id: user.id, email: user.email })
347
+ return redirect(state?.returnTo ?? '/')
348
+ },
349
+ onCallbackError: ({ error, errorCode, redirect }) => {
350
+ // errorCode may be: missing_email, timeback_user_not_found,
351
+ // timeback_user_ambiguous, timeback_user_lookup_failed
352
+ console.error('SSO Error:', errorCode, error.message)
353
+ return redirect('/?error=sso_failed')
354
+ },
355
+ getUser: () => getCurrentSession(),
356
+ }
357
+ ```
358
+
359
+ #### TimebackAuthUser Shape
360
+
361
+ The `user` in `onCallbackSuccess` is a `TimebackAuthUser` with this structure:
362
+
363
+ ```typescript
364
+ interface TimebackAuthUser {
365
+ id: string // Timeback user ID
366
+ email: string
367
+ name?: string
368
+ school?: { id: string; name: string }
369
+ grade?: number
370
+ claims: {
371
+ sub: string // OIDC subject identifier
372
+ email: string
373
+ firstName?: string
374
+ lastName?: string
375
+ pictureUrl?: string
376
+ }
377
+ }
378
+ ```
379
+
380
+ #### Session Storage Guidance
381
+
382
+ For cookie-only sessions, store only the minimal payload to avoid cookie size limits:
383
+
384
+ ```typescript
385
+ // Recommended: store minimal session
386
+ await setSession({ id: user.id, email: user.email })
387
+
388
+ // Then in getUser, return what you stored:
389
+ getUser: req => {
390
+ const session = getSessionFromCookie(req)
391
+ return session ? { id: session.id, email: session.email } : undefined
392
+ }
393
+ ```
394
+
395
+ For DB-backed sessions, you can store the full `TimebackAuthUser` if desired.
396
+
397
+ ### Custom Mode
398
+
399
+ For apps with existing auth (Clerk, Auth0, Supabase, etc.):
400
+
401
+ ```typescript
402
+ identity: {
403
+ mode: 'custom',
404
+ getUser: async (req) => {
405
+ const session = await getSession(req)
406
+ if (!session) return undefined
407
+ // Return user with timebackId as the id
408
+ return { id: session.timebackId, email: session.email, name: session.name }
409
+ },
410
+ }
411
+ ```
412
+
413
+ ## Identity-Only Integration
414
+
415
+ If you only need Timeback SSO authentication without activity tracking or Timeback API integration, use `createTimebackIdentity()`. This is a lightweight alternative that:
416
+
417
+ - Does not require Timeback API credentials
418
+ - Does not require `timeback.config.ts`
419
+ - Only exposes identity routes (sign-in, callback, sign-out)
420
+ - Returns raw OIDC user info (no Timeback profile enrichment)
421
+
422
+ **Note:** Unlike `createTimeback()`, the identity-only callback returns raw OIDC user info (`sub`, `email`, `name`, etc.) without resolving a Timeback user. Use `createTimeback()` if you need the canonical `timebackId`.
423
+
424
+ ### Cloudflare Workers / workerd compatibility
425
+
426
+ `createTimebackIdentity()` is runtime-agnostic, but the main `timeback` entrypoint also includes
427
+ Node-oriented functionality (notably config loading via `jiti`). Some edge runtimes
428
+ (Cloudflare Workers / workerd) do not support the Node modules that `jiti` depends on.
429
+
430
+ If you're deploying identity-only SSO on Workers/workerd, import from the worker-safe entrypoint:
431
+
432
+ ```ts
433
+ import { createTimebackIdentity, toNativeHandler } from '@timeback/sdk/edge'
434
+ ```
435
+
436
+ ### Server Setup
437
+
438
+ ```typescript
439
+ // lib/timeback.ts
440
+ import { createTimebackIdentity } from '@timeback/sdk'
441
+
442
+ export const timeback = createTimebackIdentity({
443
+ env: 'production',
444
+ identity: {
445
+ mode: 'sso',
446
+ clientId: process.env.AWS_COGNITO_CLIENT_ID!,
447
+ clientSecret: process.env.AWS_COGNITO_CLIENT_SECRET!,
448
+ onCallbackSuccess: async ({ user, tokens, redirect }) => {
449
+ // user is raw OIDC userInfo (sub, email, name, picture, etc.)
450
+ // No Timeback profile enrichment in identity-only mode
451
+ await createSession({
452
+ sub: user.sub,
453
+ email: user.email,
454
+ name: user.name,
455
+ })
456
+ return redirect('/')
457
+ },
458
+ onCallbackError: ({ error, redirect }) => {
459
+ console.error('SSO Error:', error)
460
+ return redirect('/login?error=sso_failed')
461
+ },
462
+ getUser: req => getSessionFromRequest(req),
463
+ },
464
+ })
465
+ ```
466
+
467
+ ### Next.js
468
+
469
+ ```typescript
470
+ // app/api/timeback/[...timeback]/route.ts
471
+ import { toNextjsHandler } from '@timeback/sdk/nextjs'
472
+
473
+ import { timeback } from '@/lib/timeback'
474
+
475
+ export const { GET, POST } = toNextjsHandler(timeback)
476
+ ```
477
+
478
+ ### Express
479
+
480
+ ```typescript
481
+ // server.ts
482
+ import express from 'express'
483
+ import { toExpressMiddleware } from '@timeback/sdk/express'
484
+
485
+ import { timeback } from './lib/timeback'
486
+
487
+ const app = express()
488
+ app.use('/api/timeback', toExpressMiddleware(timeback))
489
+ ```
490
+
491
+ All other framework adapters (Nuxt, SvelteKit, SolidStart, TanStack Start) work identically with identity-only instances.
492
+
493
+ ## User Profile
494
+
495
+ Use `timeback.user.fetch()` to retrieve the enriched Timeback profile for the
496
+ current user (identity + school/grade + courses + goals + XP):
497
+
498
+ ```typescript
499
+ const profile = await timeback.user.fetch()
500
+ console.log(profile.school?.name)
501
+ console.log(profile.xp?.today, profile.xp?.all)
502
+ ```
503
+
504
+ `profile.xp` has the shape `{ today, all }`, where:
505
+
506
+ - `today` is computed over the current **UTC day**.
507
+ - `all` is computed over a long-range analytics window (starting `2000-01-01`).
508
+
509
+ ## Activity Tracking
510
+
511
+ Activities track time spent on learning content:
512
+
513
+ ```typescript
514
+ // Start an activity
515
+ const activity = timeback.activity
516
+ .new({
517
+ id: 'lesson-123',
518
+ name: 'Introduction to Fractions',
519
+ course: { subject: 'FastMath', grade: 3 },
520
+ })
521
+ .start()
522
+
523
+ // Pause/resume
524
+ activity.pause()
525
+ activity.resume()
526
+
527
+ // End with metrics
528
+ await activity.end({
529
+ totalQuestions: 10,
530
+ correctQuestions: 8,
531
+ xpEarned: 80,
532
+ masteredUnits: 1,
533
+ })
534
+ ```
535
+
536
+ The SDK automatically sends activity data to your server, which forwards it to the Timeback API.
537
+
538
+ **Note:** Activity ingestion requires **exactly one** Caliper sensor URL in `timeback.config.ts`:
539
+
540
+ ```typescript
541
+ export default {
542
+ name: 'My App',
543
+ courses: [
544
+ /* ... */
545
+ ],
546
+ sensors: ['https://my-app.example.com/sensors/main'],
547
+ }
548
+ ```
549
+
550
+ ## Advanced: Direct API Access
551
+
552
+ For advanced use cases that need to call Timeback services (OneRoster, Edubridge, Caliper, QTI) beyond what the SDK handlers provide, use `timeback.api`:
553
+
554
+ ```typescript
555
+ // Access via the timeback instance (lazy-initialized on first access)
556
+ const { data: users } = await timeback.api.oneroster.users.list({
557
+ limit: 10,
558
+ where: { role: 'student' },
559
+ })
560
+
561
+ // Access any Timeback service
562
+ const { data: orgs } = await timeback.api.oneroster.orgs.list()
563
+ await timeback.api.caliper.emit(caliperEvent)
564
+ ```
565
+
566
+ ### Access Patterns
567
+
568
+ ```typescript
569
+ // lib/timeback.ts
570
+ import { createTimeback } from '@timeback/sdk'
571
+
572
+ export const timeback = await createTimeback({
573
+ env: 'staging',
574
+ api: {
575
+ clientId: process.env.TIMEBACK_API_CLIENT_ID!,
576
+ clientSecret: process.env.TIMEBACK_API_CLIENT_SECRET!,
577
+ },
578
+ identity: { mode: 'custom', getUser: () => getSession() },
579
+ })
580
+
581
+ // Access the API client via timeback.api
582
+ const { data: users } = await timeback.api.oneroster.users.list()
583
+ ```
584
+
585
+ ```typescript
586
+ // Elsewhere in your app
587
+ import { timeback } from './lib/timeback'
588
+
589
+ // The client is available at timeback.api
590
+ const { data: orgs } = await timeback.api.oneroster.orgs.list()
591
+ ```
592
+
593
+ ### Environment Mapping
594
+
595
+ The SDK's `env` config controls runtime mode, but for outbound API calls:
596
+
597
+ | SDK `env` | API calls use |
598
+ | ------------ | ------------- |
599
+ | `local` | `staging` |
600
+ | `staging` | `staging` |
601
+ | `production` | `production` |
602
+
603
+ This means `env: 'local'` uses staging Timeback services, so you can develop locally against real (staging) data without additional configuration.
604
+
605
+ ### When to Use
606
+
607
+ - Fetching data not exposed by SDK handlers (e.g., listing orgs, courses, enrollments)
608
+ - Emitting custom Caliper events
609
+ - Building admin dashboards or reporting tools
610
+ - Any direct Timeback API integration
611
+
612
+ **Note:** `timeback.api` is only available on the full SDK (`createTimeback()`), not on identity-only instances (`createTimebackIdentity()`).
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Sign In with Timeback Button
3
+ *
4
+ * A pre-styled button for Timeback SSO authentication.
5
+ * Fully customizable via CSS variables.
6
+ */
7
+ import * as React from 'react';
8
+ /**
9
+ * Props for SignInButton component.
10
+ */
11
+ export interface SignInButtonProps {
12
+ /** Button text (default: "Sign in with Timeback") */
13
+ children?: React.ReactNode;
14
+ /** Additional CSS class names */
15
+ className?: string;
16
+ /** Inline styles */
17
+ style?: React.CSSProperties;
18
+ /** Disabled state */
19
+ disabled?: boolean;
20
+ /** Show loading spinner when clicked */
21
+ showLoading?: boolean;
22
+ /** Called before redirect (can prevent with e.preventDefault()) */
23
+ onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
24
+ /** Button variant */
25
+ variant?: 'default' | 'outline' | 'minimal';
26
+ /** Button size */
27
+ size?: 'sm' | 'md' | 'lg';
28
+ /** Show Timeback logo */
29
+ showLogo?: boolean;
30
+ }
31
+ /**
32
+ * Sign In with Timeback button component.
33
+ *
34
+ * Triggers SSO authentication flow when clicked.
35
+ *
36
+ * @param props - Component props
37
+ * @returns Button element
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * // Basic usage
42
+ * <SignInButton />
43
+ *
44
+ * // Custom text
45
+ * <SignInButton>Continue with Timeback</SignInButton>
46
+ *
47
+ * // Outline variant
48
+ * <SignInButton variant="outline" />
49
+ *
50
+ * // Custom styling via CSS variables
51
+ * <SignInButton
52
+ * style={{
53
+ * '--tb-btn-bg': '#6366f1',
54
+ * '--tb-btn-bg-hover': '#4f46e5',
55
+ * } as React.CSSProperties}
56
+ * />
57
+ * ```
58
+ */
59
+ export declare function SignInButton(props: SignInButtonProps): React.ReactElement;
60
+ //# sourceMappingURL=SignInButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignInButton.d.ts","sourceRoot":"","sources":["../../../../src/client/adapters/react/SignInButton.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,qDAAqD;IACrD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAA;IAC3B,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,wCAAwC;IACxC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,mEAAmE;IACnE,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAA;IAC1D,qBAAqB;IACrB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;IAC3C,kBAAkB;IAClB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;CAClB;AA8MD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,CAAC,YAAY,CA4DzE"}