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 +289 -122
- package/dist/client/adapters/vue/SignInButton.vue +260 -0
- package/dist/client/adapters/vue/SignInButton.vue.d.ts +53 -0
- package/dist/client/adapters/vue/index.d.ts +43 -0
- package/dist/client/adapters/vue/index.d.ts.map +1 -0
- package/dist/client/adapters/vue/index.ts +48 -0
- package/dist/client/adapters/vue/provider.d.ts +94 -0
- package/dist/client/adapters/vue/provider.d.ts.map +1 -0
- package/dist/client/adapters/vue/provider.ts +147 -0
- package/dist/index.js +257 -21
- package/dist/server/adapters/nuxt.d.ts +96 -0
- package/dist/server/adapters/nuxt.d.ts.map +1 -0
- package/dist/server/adapters/nuxt.js +663 -0
- package/dist/server/adapters/tanstack-start.d.ts +40 -0
- package/dist/server/adapters/tanstack-start.d.ts.map +1 -0
- package/dist/server/adapters/types.d.ts +68 -0
- package/dist/server/adapters/types.d.ts.map +1 -1
- package/package.json +14 -2
package/README.md
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
# Timeback SDK
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for integrating
|
|
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
|
-
##
|
|
32
|
+
## Quick Start
|
|
14
33
|
|
|
15
|
-
|
|
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
|
-
|
|
39
|
+
## Server Adapters
|
|
18
40
|
|
|
19
|
-
|
|
20
|
-
// app/lib/timeback.ts
|
|
21
|
-
import { createTimeback } from 'timeback'
|
|
41
|
+
All server adapters use the same core configuration:
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
|
|
43
|
+
```typescript
|
|
44
|
+
// lib/timeback.ts
|
|
45
|
+
import { createServer } from 'timeback'
|
|
25
46
|
|
|
26
|
-
|
|
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.
|
|
37
|
-
clientSecret: process.env.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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
|
|
79
|
+
export const { GET, POST } = toNextjsHandler(timeback)
|
|
60
80
|
```
|
|
61
81
|
|
|
62
|
-
|
|
82
|
+
### Nuxt
|
|
63
83
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
84
|
+
```typescript
|
|
85
|
+
// server/middleware/timeback.ts
|
|
86
|
+
import { nuxtHandler } from 'timeback/nuxt'
|
|
87
|
+
|
|
88
|
+
import { timeback } from '../lib/timeback'
|
|
68
89
|
|
|
69
|
-
|
|
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
|
-
###
|
|
99
|
+
### SvelteKit
|
|
72
100
|
|
|
73
101
|
```typescript
|
|
74
|
-
//
|
|
75
|
-
import {
|
|
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
|
-
|
|
78
|
-
export const timebackClient = createTimebackClient()
|
|
119
|
+
### SolidStart
|
|
79
120
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
176
|
+
'use client'
|
|
91
177
|
|
|
92
|
-
import {
|
|
178
|
+
import { TimebackProvider } from 'timeback/react'
|
|
93
179
|
|
|
94
|
-
export function Providers({ children }) {
|
|
95
|
-
return <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
|
-
|
|
186
|
+
// components/ActivityTracker.tsx
|
|
187
|
+
import { useEffect } from 'react'
|
|
188
|
+
import { SignInButton, useTimeback } from 'timeback/react'
|
|
103
189
|
|
|
104
|
-
function
|
|
105
|
-
const
|
|
190
|
+
function MyComponent() {
|
|
191
|
+
const timeback = useTimeback()
|
|
106
192
|
|
|
107
|
-
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (!timeback) return
|
|
108
195
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
196
|
+
const activity = timeback.activity
|
|
197
|
+
.new({ id: 'lesson-1', name: 'Intro', courseCode: 'MATH-101' })
|
|
198
|
+
.start()
|
|
112
199
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
200
|
+
return () => {
|
|
201
|
+
activity.end()
|
|
202
|
+
}
|
|
203
|
+
}, [timeback])
|
|
204
|
+
|
|
205
|
+
return <SignInButton size="lg" />
|
|
119
206
|
}
|
|
120
207
|
```
|
|
121
208
|
|
|
122
|
-
###
|
|
209
|
+
### Vue
|
|
123
210
|
|
|
124
|
-
|
|
211
|
+
```vue
|
|
212
|
+
<!-- app.vue -->
|
|
213
|
+
<script setup>
|
|
214
|
+
import { TimebackProvider } from 'timeback/vue'
|
|
215
|
+
</script>
|
|
125
216
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
303
|
+
```tsx
|
|
304
|
+
// components/ActivityTracker.tsx
|
|
305
|
+
import { onCleanup, onMount } from 'solid-js'
|
|
306
|
+
import { SignInButton, useTimeback } from 'timeback/solid'
|
|
146
307
|
|
|
147
|
-
|
|
148
|
-
|
|
308
|
+
function MyComponent() {
|
|
309
|
+
const timeback = useTimeback()
|
|
310
|
+
let activity
|
|
149
311
|
|
|
150
|
-
|
|
312
|
+
onMount(() => {
|
|
313
|
+
if (!timeback) return
|
|
151
314
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
160
|
-
activity.pause()
|
|
161
|
-
activity.resume()
|
|
320
|
+
onCleanup(() => activity?.end())
|
|
162
321
|
|
|
163
|
-
|
|
164
|
-
|
|
322
|
+
return <SignInButton size="lg" />
|
|
323
|
+
}
|
|
165
324
|
```
|
|
166
325
|
|
|
167
326
|
## Identity Modes
|
|
168
327
|
|
|
169
|
-
###
|
|
328
|
+
### SSO Mode
|
|
170
329
|
|
|
171
|
-
Uses Timeback as the identity provider via OIDC
|
|
330
|
+
Uses Timeback as the identity provider via OIDC:
|
|
172
331
|
|
|
173
332
|
```typescript
|
|
174
333
|
identity: {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
|
344
|
+
### Custom Mode
|
|
182
345
|
|
|
183
|
-
For apps with existing
|
|
346
|
+
For apps with existing auth (Clerk, Auth0, Supabase, etc.):
|
|
184
347
|
|
|
185
348
|
```typescript
|
|
186
349
|
identity: {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
##
|
|
201
|
-
|
|
202
|
-
### Server
|
|
359
|
+
## Activity Tracking
|
|
203
360
|
|
|
204
|
-
|
|
205
|
-
- `timeback.handlers()` - Get Next.js route handlers
|
|
361
|
+
Activities track time spent on learning content:
|
|
206
362
|
|
|
207
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
- `client.signOut()` - Sign out
|
|
213
|
-
- `client.fetchSession()` - Fetch current session
|
|
373
|
+
// Pause/resume
|
|
374
|
+
activity.pause()
|
|
375
|
+
activity.resume()
|
|
214
376
|
|
|
215
|
-
|
|
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
|
-
|
|
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.
|