@timeback/sdk 0.1.9 → 0.1.11

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 (163) hide show
  1. package/README.md +10 -8
  2. package/dist/chunk-1cqa51je.js +2 -0
  3. package/dist/chunk-3ew9vn2d.js +2 -0
  4. package/dist/chunk-6b0ppq9d.js +2 -0
  5. package/dist/chunk-92nnwa7t.js +2 -0
  6. package/dist/chunk-bavxzt1k.js +2 -0
  7. package/dist/chunk-c8pw96sp.js +10 -0
  8. package/dist/chunk-edk3nfj7.js +2 -0
  9. package/dist/chunk-g67efaph.js +4 -0
  10. package/dist/chunk-pd91g539.js +1 -0
  11. package/dist/chunk-sgcwg4j6.js +1 -0
  12. package/dist/client/adapters/react/hooks/types.d.ts +2 -29
  13. package/dist/client/adapters/react/hooks/types.d.ts.map +1 -1
  14. package/dist/client/adapters/react/hooks/useTimebackVerification.d.ts.map +1 -1
  15. package/dist/client/adapters/react/index.js +2 -2
  16. package/dist/client/adapters/solid/types.d.ts +2 -29
  17. package/dist/client/adapters/solid/types.d.ts.map +1 -1
  18. package/dist/client/adapters/solid/types.ts +2 -18
  19. package/dist/client/adapters/svelte/stores/client.d.ts.map +1 -1
  20. package/dist/client/adapters/svelte/stores/client.ts +2 -9
  21. package/dist/client/adapters/svelte/stores/profile.d.ts +1 -1
  22. package/dist/client/adapters/svelte/stores/profile.d.ts.map +1 -1
  23. package/dist/client/adapters/svelte/stores/profile.ts +4 -11
  24. package/dist/client/adapters/svelte/stores/verification.d.ts.map +1 -1
  25. package/dist/client/adapters/svelte/stores/verification.ts +1 -10
  26. package/dist/client/adapters/svelte/types.d.ts +1 -29
  27. package/dist/client/adapters/svelte/types.d.ts.map +1 -1
  28. package/dist/client/adapters/vue/provider.d.ts.map +1 -1
  29. package/dist/client/adapters/vue/provider.ts +4 -11
  30. package/dist/client/adapters/vue/types.d.ts +2 -29
  31. package/dist/client/adapters/vue/types.d.ts.map +1 -1
  32. package/dist/client/adapters/vue/types.ts +2 -18
  33. package/dist/client/auth/types.d.ts +1 -1
  34. package/dist/client/index.d.ts +1 -1
  35. package/dist/client/lib/activity/activity.class.d.ts +130 -22
  36. package/dist/client/lib/activity/activity.class.d.ts.map +1 -1
  37. package/dist/client/lib/activity/transport.d.ts +15 -0
  38. package/dist/client/lib/activity/transport.d.ts.map +1 -0
  39. package/dist/client/lib/activity/types.d.ts +53 -0
  40. package/dist/client/lib/activity/types.d.ts.map +1 -0
  41. package/dist/client/lib/utils.d.ts +18 -0
  42. package/dist/client/lib/utils.d.ts.map +1 -1
  43. package/dist/client/lib/utils.ts +109 -0
  44. package/dist/client/namespaces/activity.d.ts +49 -7
  45. package/dist/client/namespaces/activity.d.ts.map +1 -1
  46. package/dist/client/timeback-client.class.d.ts +7 -1
  47. package/dist/client/timeback-client.class.d.ts.map +1 -1
  48. package/dist/client.d.ts +1 -1
  49. package/dist/client.js +1 -1
  50. package/dist/identity.d.ts +2 -5
  51. package/dist/identity.d.ts.map +1 -1
  52. package/dist/identity.js +1 -1
  53. package/dist/index.d.ts +7 -3
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +30 -21
  56. package/dist/server/adapters/express.d.ts.map +1 -1
  57. package/dist/server/adapters/express.js +1 -1
  58. package/dist/server/adapters/native.d.ts.map +1 -1
  59. package/dist/server/adapters/native.js +2 -2
  60. package/dist/server/adapters/nextjs.js +1 -1
  61. package/dist/server/adapters/nuxt.d.ts.map +1 -1
  62. package/dist/server/adapters/nuxt.js +1 -1
  63. package/dist/server/adapters/solid-start.d.ts.map +1 -1
  64. package/dist/server/adapters/solid-start.js +1 -1
  65. package/dist/server/adapters/svelte-kit.d.ts.map +1 -1
  66. package/dist/server/adapters/svelte-kit.js +1 -1
  67. package/dist/server/adapters/tanstack-start.d.ts.map +1 -1
  68. package/dist/server/adapters/tanstack-start.js +1 -1
  69. package/dist/server/adapters/types.d.ts +16 -4
  70. package/dist/server/adapters/types.d.ts.map +1 -1
  71. package/dist/server/adapters/utils.d.ts +1 -1
  72. package/dist/server/adapters/utils.d.ts.map +1 -1
  73. package/dist/server/handlers/activity/attempts.d.ts +1 -1
  74. package/dist/server/handlers/activity/attempts.d.ts.map +1 -1
  75. package/dist/server/handlers/activity/caliper.d.ts +54 -16
  76. package/dist/server/handlers/activity/caliper.d.ts.map +1 -1
  77. package/dist/server/handlers/activity/heartbeat-handler.d.ts +15 -0
  78. package/dist/server/handlers/activity/heartbeat-handler.d.ts.map +1 -0
  79. package/dist/server/handlers/activity/index.d.ts +5 -3
  80. package/dist/server/handlers/activity/index.d.ts.map +1 -1
  81. package/dist/server/handlers/activity/progress.d.ts +2 -2
  82. package/dist/server/handlers/activity/progress.d.ts.map +1 -1
  83. package/dist/server/handlers/activity/schema.d.ts +40 -6
  84. package/dist/server/handlers/activity/schema.d.ts.map +1 -1
  85. package/dist/server/handlers/activity/submit-handler.d.ts +29 -0
  86. package/dist/server/handlers/activity/submit-handler.d.ts.map +1 -0
  87. package/dist/server/handlers/activity/submit.d.ts +44 -0
  88. package/dist/server/handlers/activity/submit.d.ts.map +1 -0
  89. package/dist/server/handlers/activity/types.d.ts +126 -5
  90. package/dist/server/handlers/activity/types.d.ts.map +1 -1
  91. package/dist/server/handlers/identity/handler.d.ts +23 -4
  92. package/dist/server/handlers/identity/handler.d.ts.map +1 -1
  93. package/dist/server/handlers/identity/index.d.ts +2 -2
  94. package/dist/server/handlers/identity/index.d.ts.map +1 -1
  95. package/dist/server/handlers/identity/oidc.d.ts.map +1 -1
  96. package/dist/server/handlers/identity/types.d.ts +0 -6
  97. package/dist/server/handlers/identity/types.d.ts.map +1 -1
  98. package/dist/server/handlers/index.d.ts +3 -3
  99. package/dist/server/handlers/index.d.ts.map +1 -1
  100. package/dist/server/handlers/user/handler.d.ts.map +1 -1
  101. package/dist/server/handlers/user/profile.d.ts.map +1 -1
  102. package/dist/server/handlers/user/types.d.ts +3 -0
  103. package/dist/server/handlers/user/types.d.ts.map +1 -1
  104. package/dist/server/handlers/user/verify.d.ts.map +1 -1
  105. package/dist/server/index.d.ts +1 -1
  106. package/dist/server/index.d.ts.map +1 -1
  107. package/dist/server/lib/hooks.d.ts +20 -0
  108. package/dist/server/lib/hooks.d.ts.map +1 -0
  109. package/dist/server/lib/index.d.ts +4 -2
  110. package/dist/server/lib/index.d.ts.map +1 -1
  111. package/dist/server/lib/logger.d.ts +36 -9
  112. package/dist/server/lib/logger.d.ts.map +1 -1
  113. package/dist/server/lib/resolve.d.ts +1 -1
  114. package/dist/server/lib/resolve.d.ts.map +1 -1
  115. package/dist/server/lib/utils.d.ts +23 -2
  116. package/dist/server/lib/utils.d.ts.map +1 -1
  117. package/dist/server/lib/validation.d.ts +55 -0
  118. package/dist/server/lib/validation.d.ts.map +1 -0
  119. package/dist/server/namespaces/activity/index.d.ts +8 -0
  120. package/dist/server/namespaces/activity/index.d.ts.map +1 -0
  121. package/dist/server/namespaces/activity/record.d.ts +49 -0
  122. package/dist/server/namespaces/activity/record.d.ts.map +1 -0
  123. package/dist/server/namespaces/activity/schema.d.ts +50 -0
  124. package/dist/server/namespaces/activity/schema.d.ts.map +1 -0
  125. package/dist/server/namespaces/user/get-profile.d.ts +32 -0
  126. package/dist/server/namespaces/user/get-profile.d.ts.map +1 -0
  127. package/dist/server/namespaces/user/index.d.ts +8 -0
  128. package/dist/server/namespaces/user/index.d.ts.map +1 -0
  129. package/dist/server/namespaces/user/verify.d.ts +28 -0
  130. package/dist/server/namespaces/user/verify.d.ts.map +1 -0
  131. package/dist/server/timeback-identity.d.ts +3 -3
  132. package/dist/server/timeback.d.ts +5 -3
  133. package/dist/server/timeback.d.ts.map +1 -1
  134. package/dist/server/types.d.ts +407 -14
  135. package/dist/server/types.d.ts.map +1 -1
  136. package/dist/shared/constants.d.ts +7 -0
  137. package/dist/shared/constants.d.ts.map +1 -1
  138. package/dist/shared/constants.ts +51 -0
  139. package/dist/shared/index.d.ts +9 -0
  140. package/dist/shared/index.d.ts.map +1 -0
  141. package/dist/shared/schemas.d.ts +57 -0
  142. package/dist/shared/schemas.d.ts.map +1 -0
  143. package/dist/shared/types.d.ts +287 -18
  144. package/dist/shared/types.d.ts.map +1 -1
  145. package/dist/shared/types.ts +636 -0
  146. package/package.json +7 -10
  147. package/dist/chunk-07j8zre9.js +0 -2
  148. package/dist/chunk-5171mkp2.js +0 -2
  149. package/dist/chunk-63afdp3y.js +0 -8
  150. package/dist/chunk-8gg8n8v9.js +0 -2
  151. package/dist/chunk-9se82640.js +0 -1
  152. package/dist/chunk-agpf1x3g.js +0 -16
  153. package/dist/chunk-hnf0tart.js +0 -2
  154. package/dist/chunk-qr0bbnsr.js +0 -1
  155. package/dist/chunk-whc53e0y.js +0 -11
  156. package/dist/chunk-x9gvef7q.js +0 -1
  157. package/dist/edge.d.ts +0 -13
  158. package/dist/edge.d.ts.map +0 -1
  159. package/dist/edge.js +0 -1
  160. package/dist/server/handlers/activity/handler.d.ts +0 -32
  161. package/dist/server/handlers/activity/handler.d.ts.map +0 -1
  162. package/dist/shared/xp-calculator.d.ts +0 -25
  163. package/dist/shared/xp-calculator.d.ts.map +0 -1
@@ -0,0 +1,636 @@
1
+ /**
2
+ * Shared Types
3
+ *
4
+ * Types shared between client and server.
5
+ */
6
+
7
+ import type { TimebackGrade, TimebackSubject } from '@timeback/types'
8
+
9
+ /**
10
+ * User identity returned from SSO.
11
+ */
12
+ export interface TimebackIdentity {
13
+ id: string
14
+ email: string
15
+ name?: string
16
+ }
17
+
18
+ /**
19
+ * Timeback user profile with enriched data from the Timeback API.
20
+ */
21
+ export interface TimebackProfile {
22
+ /** Timeback user ID */
23
+ id: string
24
+ /** User's email address */
25
+ email: string
26
+ /** User's display name */
27
+ name?: string
28
+
29
+ /** School information */
30
+ school?: {
31
+ id: string
32
+ name: string
33
+ }
34
+
35
+ /** Grade level */
36
+ grade?: number
37
+
38
+ /** XP earned on this app */
39
+ xp?: {
40
+ /** XP earned today (UTC day range) */
41
+ today: number
42
+ /** XP earned across all time (computed from analytics) */
43
+ all: number
44
+ }
45
+
46
+ /** Enrolled courses */
47
+ courses?: Array<{
48
+ id: string
49
+ code: string
50
+ name: string
51
+ }>
52
+
53
+ /** Goals and progress */
54
+ goals?: {
55
+ dailyXp?: number
56
+ dailyLessons?: number
57
+ dailyActiveMinutes?: number
58
+ dailyAccuracy?: number
59
+ dailyMasteredUnits?: number
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Recommended minimal user payload to persist in a session.
65
+ *
66
+ * **User-facing note:** this type is part of the SDK’s developer experience.
67
+ * It exists to give you a “safe default” session shape that works well for
68
+ * cookie-based sessions (small payload) while still carrying enough Timeback
69
+ * context to power common UI affordances (e.g. showing a school name or grade).
70
+ *
71
+ * **What it is:** a minimal, serializable subset of `TimebackProfile`.
72
+ * **What it isn’t:** a guarantee that you’ll always have the full Timeback
73
+ * profile (courses, goals, xp, etc.) in-session. If you need richer data, fetch
74
+ * it from the API (e.g. `timeback.user.fetch()`) and cache/store it according
75
+ * to your app’s needs.
76
+ *
77
+ * **Stability:** this is still early and we’re actively iterating on identity
78
+ * and session ergonomics. We may rename, restructure, or remove this type from
79
+ * the public surface as the SDK evolves.
80
+ */
81
+ export type TimebackSessionUser = Pick<
82
+ TimebackProfile,
83
+ 'id' | 'email' | 'name' | 'school' | 'grade'
84
+ >
85
+
86
+ /**
87
+ * Claims from the identity provider (IdP).
88
+ *
89
+ * Normalized subset of OIDC UserInfo claims.
90
+ */
91
+ export interface IdentityClaims {
92
+ /** Subject identifier (unique user ID from IdP) */
93
+ sub: string
94
+ /** User's email address */
95
+ email: string
96
+ /** User's first/given name */
97
+ firstName?: string
98
+ /** User's last/family name */
99
+ lastName?: string
100
+ /** User's profile picture URL */
101
+ pictureUrl?: string
102
+ }
103
+
104
+ /**
105
+ * Authenticated user with Timeback profile and IdP claims.
106
+ *
107
+ * This is the primary user object returned during SSO callback when using
108
+ * `createTimeback()`. The `id` field is the canonical `timebackId` (stable identifier).
109
+ */
110
+ export interface TimebackAuthUser extends TimebackProfile {
111
+ /** IdP claims (raw identity provider data) */
112
+ claims: IdentityClaims
113
+ }
114
+
115
+ /**
116
+ * Course selector by subject and grade (grade-based apps).
117
+ *
118
+ * Use this for traditional K-12 apps where courses are identified by subject + grade.
119
+ */
120
+ export interface SubjectGradeCourseRef {
121
+ subject: TimebackSubject
122
+ grade: TimebackGrade
123
+ }
124
+
125
+ /**
126
+ * Course selector by code (grade-less apps).
127
+ *
128
+ * Use this for apps without grade levels (e.g., CS platforms) where courses
129
+ * are identified by a unique course code.
130
+ */
131
+ export interface CourseCodeRef {
132
+ code: string
133
+ }
134
+
135
+ /**
136
+ * Course selector for activity tracking.
137
+ *
138
+ * This should correspond to a unique course entry in `timeback.config.json`.
139
+ *
140
+ * Two selector modes are supported:
141
+ * - **Grade-based**: `{ subject, grade }` — K-12 style
142
+ * - **Grade-less**: `{ code }` — CS/skill-based
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * // Grade-based
147
+ * { subject: 'Math', grade: 3 }
148
+ * ```
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * // Grade-less
153
+ * { code: 'CS-101' }
154
+ * ```
155
+ */
156
+ export type ActivityCourseRef = SubjectGradeCourseRef | CourseCodeRef
157
+
158
+ /**
159
+ * Type guard: Check if a course ref uses subject+grade identity.
160
+ *
161
+ * @param ref - Course reference to check
162
+ * @returns True if grade-based selector
163
+ */
164
+ export function isSubjectGradeCourseRef(ref: ActivityCourseRef): ref is SubjectGradeCourseRef {
165
+ return 'grade' in ref && ref.grade !== undefined
166
+ }
167
+
168
+ /**
169
+ * Time tracking configuration options.
170
+ *
171
+ * Controls how the SDK tracks and reports time-spent data via heartbeats.
172
+ */
173
+ export interface TimeTrackingOptions {
174
+ /**
175
+ * Interval in milliseconds between automatic heartbeat flushes.
176
+ * @default 15000 (15 seconds)
177
+ */
178
+ flushIntervalMs?: number
179
+ /**
180
+ * Whether to pause time tracking when the tab is not visible.
181
+ * @default true
182
+ */
183
+ visibilityAware?: boolean
184
+ /**
185
+ * Whether to flush accumulated time when the tab becomes hidden.
186
+ * @default true
187
+ */
188
+ flushOnVisibilityHidden?: boolean
189
+ /**
190
+ * Whether to attempt a best-effort flush on page unload (pagehide event).
191
+ *
192
+ * Implementation notes:
193
+ * - Prefer `navigator.sendBeacon()` when available and safe to use
194
+ * - Fall back to `fetch(..., { keepalive: true })` otherwise
195
+ *
196
+ * sendBeacon cannot set arbitrary headers (e.g. Authorization), so
197
+ * integrations that rely on bearer tokens typically use the keepalive
198
+ * fetch fallback.
199
+ * @default true
200
+ */
201
+ flushOnPageHide?: boolean
202
+ /**
203
+ * Timeout in milliseconds after which hidden time stops being tracked.
204
+ *
205
+ * When the tab is hidden for longer than this duration, heartbeats stop
206
+ * and the hidden time is not counted. When the user returns, tracking
207
+ * resumes fresh without counting the extended absence.
208
+ *
209
+ * Set to `null` or `Infinity` to disable (always track hidden time).
210
+ * @default 600000 (10 minutes)
211
+ */
212
+ hiddenTimeoutMs?: number | null
213
+ /**
214
+ * Number of retry attempts for failed heartbeat sends.
215
+ *
216
+ * Set to `0` (default) for no retries. Retries use exponential backoff
217
+ * with delays configured by `retryDelaysMs`.
218
+ *
219
+ * @default 0
220
+ */
221
+ retryAttempts?: number
222
+ /**
223
+ * Delay schedule (in milliseconds) between retry attempts.
224
+ *
225
+ * Each index corresponds to the delay before that retry attempt.
226
+ * If more attempts are made than there are entries, the last value is reused.
227
+ *
228
+ * @default [100, 300, 1000]
229
+ */
230
+ retryDelaysMs?: number[]
231
+ }
232
+
233
+ /**
234
+ * Activity start parameters.
235
+ *
236
+ * @remarks
237
+ * The `id` field is treated as a **slug** (not a URL). The SDK derives the
238
+ * canonical activity URL (`event.object.id`) from:
239
+ * - The configured sensor URL
240
+ * - The course selector (subject + grade or code)
241
+ * - This slug (URI-encoded)
242
+ *
243
+ * This allows upstream systems to process activities without requiring
244
+ * pre-synced OneRoster component resources.
245
+ *
246
+ * @example
247
+ * ```typescript
248
+ * // Grade-based course
249
+ * timeback.activity.start({
250
+ * id: 'fractions-with-like-denominators', // slug
251
+ * name: 'Fractions with Like Denominators', // human-readable
252
+ * course: { subject: 'Math', grade: 3 },
253
+ * })
254
+ * // => object.id: https://sensor.example.com/activities/Math/g3/fractions-with-like-denominators
255
+ *
256
+ * // Grade-less course
257
+ * timeback.activity.start({
258
+ * id: 'intro-to-loops',
259
+ * name: 'Introduction to Loops',
260
+ * course: { code: 'CS-101' },
261
+ * })
262
+ * // => object.id: https://sensor.example.com/activities/CS-101/intro-to-loops
263
+ * ```
264
+ */
265
+ export interface ActivityParams {
266
+ /**
267
+ * Activity slug (stable identifier for the learning object).
268
+ *
269
+ * This is used to construct the canonical activity URL sent to Caliper.
270
+ * Use a short, URL-safe slug like `"fractions-with-like-denominators"` or
271
+ * `"lesson-1"`. Special characters will be URI-encoded.
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * // 'fractions-with-like-denominators'
276
+ * ```
277
+ * @example
278
+ * ```typescript
279
+ * // 'ccss.math.content.3.nf.a.1'
280
+ * ```
281
+ * @example
282
+ * ```typescript
283
+ * // 'lesson-1'
284
+ * ```
285
+ */
286
+ id: string
287
+ /**
288
+ * Human-readable display name of the activity.
289
+ *
290
+ * This is sent as `object.activity.name` in Caliper events.
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * // 'Fractions with Like Denominators'
295
+ * ```
296
+ */
297
+ name: string
298
+ /** Course selector (must match a unique course in timeback.config.json) */
299
+ course: ActivityCourseRef
300
+ /**
301
+ * Optional run identifier for correlating events across sessions.
302
+ *
303
+ * When resuming an activity, provide the same `runId` from the previous session
304
+ * to correlate time-spent events with the eventual completion event.
305
+ *
306
+ * If not provided, the SDK generates a new UUID.
307
+ */
308
+ runId?: string
309
+ /**
310
+ * Time tracking configuration options.
311
+ *
312
+ * Heartbeats are enabled by default. Use this to customize flush intervals
313
+ * or disable visibility-aware tracking.
314
+ *
315
+ * Set to `false` to disable client-side time tracking entirely. When
316
+ * disabled, no heartbeats are sent, no visibility handlers are registered,
317
+ * and `end()` skips the final time flush. Use this when time is managed
318
+ * server-side (e.g. via `timeback.activity.record()`).
319
+ */
320
+ time?: TimeTrackingOptions | false
321
+
322
+ /**
323
+ * Called when a heartbeat or submission fails.
324
+ *
325
+ * Heartbeat errors are non-fatal — the SDK continues tracking time
326
+ * regardless. Submit errors (from `end()`) are also surfaced here
327
+ * before being re-thrown.
328
+ *
329
+ * @param error - The error that occurred
330
+ * @param context - Details about the failed operation
331
+ */
332
+ onError?: (error: Error, context: ActivityErrorContext) => void
333
+
334
+ /**
335
+ * Called when the activity is paused (via `pause()` or visibility timeout).
336
+ */
337
+ onPause?: () => void
338
+
339
+ /**
340
+ * Called when the activity resumes after being paused.
341
+ */
342
+ onResume?: () => void
343
+
344
+ /**
345
+ * Called after each successful heartbeat flush.
346
+ *
347
+ * @param elapsedMs - Active milliseconds reported in this flush
348
+ */
349
+ onFlush?: (elapsedMs: number) => void
350
+ }
351
+
352
+ /**
353
+ * Context passed to the `onError` callback.
354
+ */
355
+ export interface ActivityErrorContext {
356
+ /** Which operation failed — aligns with Caliper event types. */
357
+ type: 'timeSpent' | 'completion'
358
+ /** The activity slug passed to `activity.start()`. */
359
+ activityId: string
360
+ /** The `runId` for this activity instance. */
361
+ runId: string
362
+ }
363
+
364
+ /**
365
+ * Activity metrics (optional performance data).
366
+ */
367
+ export interface ActivityMetrics {
368
+ /** Total questions attempted */
369
+ totalQuestions?: number
370
+ /** Number of correct answers */
371
+ correctQuestions?: number
372
+ /** XP earned from this activity */
373
+ xpEarned?: number
374
+ /** Number of units mastered */
375
+ masteredUnits?: number
376
+ }
377
+
378
+ /**
379
+ * Question count metrics for activity completion.
380
+ *
381
+ * @example
382
+ * ```typescript
383
+ * // Basic usage
384
+ * await activity.end({
385
+ * xpEarned: 100,
386
+ * totalQuestions: 10,
387
+ * correctQuestions: 8,
388
+ * })
389
+ * ```
390
+ *
391
+ * @example
392
+ * ```typescript
393
+ * // With time override
394
+ * await activity.end({
395
+ * xpEarned: 100,
396
+ * totalQuestions: 10,
397
+ * correctQuestions: 8,
398
+ * time: { active: 42000, inactive: 3000 },
399
+ * })
400
+ * ```
401
+ */
402
+ type QuestionCountMetrics =
403
+ | {
404
+ /**
405
+ * Total questions attempted.
406
+ *
407
+ * If provided, `correctQuestions` is required too.
408
+ */
409
+ totalQuestions: number
410
+ /**
411
+ * Number of correct answers.
412
+ *
413
+ * Must be provided when `totalQuestions` is provided.
414
+ */
415
+ correctQuestions: number
416
+ }
417
+ | {
418
+ /** Omit question counts entirely. */
419
+ totalQuestions?: undefined
420
+ /** Omit question counts entirely. */
421
+ correctQuestions?: undefined
422
+ }
423
+
424
+ /** Optional time override for activity.end() */
425
+ interface ActivityEndTimeOverride {
426
+ /** Active time in milliseconds */
427
+ active: number
428
+ /** Inactive/paused time in milliseconds (defaults to 0) */
429
+ inactive?: number
430
+ }
431
+
432
+ /**
433
+ * Time-only end: final flush without completion event.
434
+ *
435
+ * Call `activity.end()` with no args or just a time override to flush
436
+ * accumulated time without sending an ActivityCompletedEvent.
437
+ */
438
+ type ActivityEndTimeOnly = {
439
+ totalQuestions?: undefined
440
+ correctQuestions?: undefined
441
+ xpEarned?: undefined
442
+ masteredUnits?: undefined
443
+ pctComplete?: undefined
444
+ time?: ActivityEndTimeOverride
445
+ }
446
+
447
+ /** Completion end: flush + ActivityCompletedEvent. */
448
+ type ActivityEndCompletion = QuestionCountMetrics & {
449
+ /** XP earned from this activity. */
450
+ xpEarned: number
451
+ /** Number of units mastered */
452
+ masteredUnits?: number
453
+ /**
454
+ * App-reported course progress (0–100). Values outside range are clamped.
455
+ */
456
+ pctComplete?: number
457
+ /**
458
+ * Optional time override. When provided, the SDK uses these values instead
459
+ * of the internal timer. Values are clamped to >= 0 and rounded to integers.
460
+ */
461
+ time?: ActivityEndTimeOverride
462
+ }
463
+
464
+ /**
465
+ * Data for ending an activity.
466
+ *
467
+ * Two modes:
468
+ * - **Time-only**: `activity.end()` or `activity.end({ time: {...} })` — final
469
+ * flush only, no completion event
470
+ * - **Completion**: `activity.end({ xpEarned, ... })` — final flush + completion event
471
+ */
472
+ export type ActivityEndData = ActivityEndTimeOnly | ActivityEndCompletion
473
+
474
+ /**
475
+ * Activity state sent to the server when ending.
476
+ *
477
+ * @see {@link ActivityParams} for documentation on `id` and `name` semantics.
478
+ */
479
+ export interface ActivityEndPayload {
480
+ /**
481
+ * Activity slug (stable identifier for the learning object).
482
+ *
483
+ * @see {@link ActivityParams.id}
484
+ */
485
+ id: string
486
+ /**
487
+ * Human-readable display name of the activity.
488
+ *
489
+ * @see {@link ActivityParams.name}
490
+ */
491
+ name: string
492
+ /** Course selector (must match a unique course in timeback.config.json) */
493
+ course: ActivityCourseRef
494
+ /** ISO 8601 timestamp when activity started */
495
+ startedAt: string
496
+ /** ISO 8601 timestamp when activity ended */
497
+ endedAt: string
498
+ /** Active time in milliseconds (excluding paused time) */
499
+ elapsedMs: number
500
+ /** Total paused time in milliseconds */
501
+ pausedMs: number
502
+ /** Activity metrics */
503
+ metrics: ActivityMetrics
504
+ /**
505
+ * App-reported course progress (per enrollment), as a percentage from 0–100.
506
+ *
507
+ * This is forwarded to Caliper events as `generated.extensions.pctCompleteApp`.
508
+ *
509
+ * @remarks
510
+ * - Scale: 0 to 100 (not 0 to 1)
511
+ * - Values outside 0–100 are clamped
512
+ */
513
+ pctComplete?: number
514
+ }
515
+
516
+ /**
517
+ * Activity heartbeat payload for time-spent tracking.
518
+ *
519
+ * Sent periodically to report accumulated active time for a time window.
520
+ * The server builds a `TimeSpentEvent` from this data.
521
+ */
522
+ export interface ActivityHeartbeatPayload {
523
+ /**
524
+ * Activity slug (stable identifier for the learning object).
525
+ */
526
+ id: string
527
+ /**
528
+ * Human-readable display name of the activity.
529
+ */
530
+ name: string
531
+ /** Course selector (must match a unique course in timeback.config.json) */
532
+ course: ActivityCourseRef
533
+ /**
534
+ * Run identifier for correlating heartbeats with the completion event.
535
+ */
536
+ runId: string
537
+ /**
538
+ * ISO 8601 timestamp when this time window started.
539
+ */
540
+ startedAt: string
541
+ /**
542
+ * ISO 8601 timestamp when this time window ended.
543
+ */
544
+ endedAt: string
545
+ /**
546
+ * Active time in this window in milliseconds.
547
+ */
548
+ elapsedMs: number
549
+ /**
550
+ * Paused time in this window in milliseconds.
551
+ */
552
+ pausedMs: number
553
+ }
554
+
555
+ /**
556
+ * Activity submit payload for completion events.
557
+ *
558
+ * Sent when the activity completes with metrics.
559
+ * The server builds an `ActivityCompletedEvent` from this data.
560
+ */
561
+ export interface ActivitySubmitPayload {
562
+ /**
563
+ * Activity slug (stable identifier for the learning object).
564
+ */
565
+ id: string
566
+ /**
567
+ * Human-readable display name of the activity.
568
+ */
569
+ name: string
570
+ /** Course selector (must match a unique course in timeback.config.json) */
571
+ course: ActivityCourseRef
572
+ /**
573
+ * Run identifier for correlating with previous heartbeats.
574
+ */
575
+ runId: string
576
+ /**
577
+ * ISO 8601 timestamp when the activity ended.
578
+ */
579
+ endedAt: string
580
+ /** Activity metrics */
581
+ metrics: ActivityMetrics
582
+ /**
583
+ * App-reported course progress (per enrollment), as a percentage from 0–100.
584
+ */
585
+ pctComplete?: number
586
+ }
587
+
588
+ /**
589
+ * Activity submission response.
590
+ */
591
+ export interface ActivityResponse {
592
+ success: boolean
593
+ error?: string
594
+ }
595
+
596
+ /**
597
+ * Result of verifying a user's Timeback status.
598
+ *
599
+ * Used by partner apps to check if a user exists in Timeback before
600
+ * granting access to Timeback-gated features (e.g., free tier for Timeback users).
601
+ */
602
+ export type TimebackVerifyResult =
603
+ | {
604
+ /** User exists in Timeback */
605
+ verified: true
606
+ /** Timeback user ID */
607
+ timebackId: string
608
+ }
609
+ | {
610
+ /** User does not exist in Timeback */
611
+ verified: false
612
+ }
613
+
614
+ /**
615
+ * Verification state for the current user.
616
+ *
617
+ * Used by framework adapters (React, Vue, Svelte, Solid) to expose
618
+ * verification status as a state machine for UI consumption.
619
+ */
620
+ export type TimebackVerificationState =
621
+ | { status: 'loading' }
622
+ | { status: 'verified'; timebackId: string }
623
+ | { status: 'unverified' }
624
+ | { status: 'error'; message: string }
625
+
626
+ /**
627
+ * Profile state for the current user.
628
+ *
629
+ * Used by framework adapters (React, Vue, Svelte, Solid) to expose
630
+ * profile fetching status as a state machine for UI consumption.
631
+ */
632
+ export type TimebackProfileState =
633
+ | { status: 'idle' }
634
+ | { status: 'loading' }
635
+ | { status: 'loaded'; profile: TimebackProfile }
636
+ | { status: 'error'; message: string }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timeback/sdk",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Timeback SDK for frontend and backend integration",
5
5
  "type": "module",
6
6
  "exports": {
@@ -16,10 +16,6 @@
16
16
  "types": "./dist/identity.d.ts",
17
17
  "import": "./dist/identity.js"
18
18
  },
19
- "./edge": {
20
- "types": "./dist/edge.d.ts",
21
- "import": "./dist/edge.js"
22
- },
23
19
  "./react": {
24
20
  "types": "./dist/client/adapters/react/index.d.ts",
25
21
  "import": "./dist/client/adapters/react/index.js"
@@ -79,23 +75,24 @@
79
75
  "test:e2e": "bun test --no-env-file e2e.test.ts"
80
76
  },
81
77
  "dependencies": {
82
- "@timeback/core": "0.1.4",
78
+ "@timeback/core": "0.1.5",
83
79
  "c12": "^3.3.3",
84
80
  "zod": "^4.2.1"
85
81
  },
86
82
  "devDependencies": {
87
- "@timeback/caliper": "0.1.3",
88
- "@timeback/edubridge": "0.1.3",
83
+ "@timeback/caliper": "0.1.5",
84
+ "@timeback/edubridge": "0.1.4",
89
85
  "@timeback/internal-cli-infra": "0.0.0",
90
86
  "@timeback/internal-client-infra": "0.0.0",
91
87
  "@timeback/internal-logger": "0.0.0",
92
88
  "@timeback/internal-test": "0.0.0",
93
89
  "@timeback/internal-utils": "0.0.0",
94
- "@timeback/oneroster": "0.1.5",
90
+ "@timeback/oneroster": "0.1.6",
95
91
  "@timeback/types": "0.0.0",
96
92
  "@types/bun": "latest",
97
93
  "@types/express": "^5.0.6",
98
- "@types/react": "^19.1.8"
94
+ "@types/react": "^19.1.8",
95
+ "esbuild": "^0.27.3"
99
96
  },
100
97
  "peerDependencies": {
101
98
  "react": "^18 || ^19",
@@ -1,2 +0,0 @@
1
- import{createRequire as k}from"node:module";var g=Object.create;var{getPrototypeOf:h,defineProperty:f,getOwnPropertyNames:i}=Object;var j=Object.prototype.hasOwnProperty;var l=(a,b,c)=>{c=a!=null?g(h(a)):{};let d=b||!a||!a.__esModule?f(c,"default",{value:a,enumerable:!0}):c;for(let e of i(a))if(!j.call(d,e))f(d,e,{get:()=>a[e],enumerable:!0});return d};var m=(a,b)=>()=>(b||a((b={exports:{}}).exports,b),b.exports);var o=k(import.meta.url);
2
- export{l as M,m as N,o as O};
@@ -1,2 +0,0 @@
1
- import{C as F}from"./chunk-hnf0tart.js";function M(j){let g=j.trim();if(g==="")return"/";let A=g.indexOf("?");if(A===-1)return g;let L=g.slice(0,A);if(L==="")return"/";return L}function X(j){let g=M(j);if(g!==""&&!g.startsWith("/"))g=`/${g}`;if(g==="/"||g==="")return"";if(g.endsWith("/"))return g.slice(0,-1);return g}function Z(j){let g=j.method.toUpperCase(),A=M(j.pathname),L=j.callbackPath?M(j.callbackPath):void 0;if(L&&A===L)return g==="GET"?"identity.callback":null;let N=j.basePath,Q=N!==void 0,J=Q?X(N):void 0,V=(G)=>{if(g==="GET"){if(G===F.IDENTITY.SIGNIN)return"identity.signIn";if(G===F.IDENTITY.CALLBACK)return"identity.callback";if(G===F.IDENTITY.SIGNOUT)return"identity.signOut";if(G===F.USER.ME)return"user.me";if(G===F.USER.VERIFY)return"user.verify"}if(g==="POST"){if(G===F.ACTIVITY)return"activity"}return null};if(Q&&J!==void 0){if(J!==""&&A===J)return null;if(J!==""&&!A.startsWith(`${J}/`))return null;let G=J===""?A:A.slice(J.length),W=G.startsWith("/")?G:`/${G}`;return V(W)}if(g==="GET"){if(A.endsWith(F.IDENTITY.SIGNIN))return"identity.signIn";if(A.endsWith(F.IDENTITY.CALLBACK))return"identity.callback";if(A.endsWith(F.IDENTITY.SIGNOUT))return"identity.signOut";if(A.endsWith(F.USER.ME))return"user.me";if(A.endsWith(F.USER.VERIFY))return"user.verify"}if(g==="POST"){if(A.endsWith(F.ACTIVITY))return"activity"}return null}function _(j){return"handle"in j?j.handle:j}function $(j){return"activity"in j}function C(j){return"user"in j}
2
- export{M as x,Z as y,_ as z,$ as A,C as B};