@timeback/sdk 0.1.5 → 0.1.7

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 (123) hide show
  1. package/README.md +21 -22
  2. package/dist/client/adapters/react/hooks/types.d.ts +15 -0
  3. package/dist/client/adapters/react/hooks/types.d.ts.map +1 -0
  4. package/dist/client/adapters/react/hooks/useTimebackVerification.d.ts +18 -0
  5. package/dist/client/adapters/react/hooks/useTimebackVerification.d.ts.map +1 -0
  6. package/dist/client/adapters/react/index.d.ts +2 -0
  7. package/dist/client/adapters/react/index.d.ts.map +1 -1
  8. package/dist/client/adapters/react/index.js +139 -9
  9. package/dist/client/auth/bearer.d.ts +17 -0
  10. package/dist/client/auth/bearer.d.ts.map +1 -0
  11. package/dist/client/auth/index.d.ts +3 -0
  12. package/dist/client/auth/index.d.ts.map +1 -0
  13. package/dist/client/auth/types.d.ts +39 -0
  14. package/dist/client/auth/types.d.ts.map +1 -0
  15. package/dist/client/index.d.ts +2 -0
  16. package/dist/client/index.d.ts.map +1 -1
  17. package/dist/client/lib/fetch.d.ts +19 -0
  18. package/dist/client/lib/fetch.d.ts.map +1 -0
  19. package/dist/client/namespaces/user.d.ts +25 -2
  20. package/dist/client/namespaces/user.d.ts.map +1 -1
  21. package/dist/client/timeback-client.class.d.ts +15 -0
  22. package/dist/client/timeback-client.class.d.ts.map +1 -1
  23. package/dist/client/timeback-client.d.ts +3 -0
  24. package/dist/client/timeback-client.d.ts.map +1 -1
  25. package/dist/client.d.ts +2 -1
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +69 -6
  28. package/dist/edge.d.ts +1 -1
  29. package/dist/edge.js +85291 -169
  30. package/dist/identity.js +85186 -74
  31. package/dist/index.js +1289 -840
  32. package/dist/server/adapters/express.d.ts.map +1 -1
  33. package/dist/server/adapters/express.js +489 -388
  34. package/dist/server/adapters/native.d.ts.map +1 -1
  35. package/dist/server/adapters/native.js +32 -1
  36. package/dist/server/adapters/nextjs.js +32 -1
  37. package/dist/server/adapters/nuxt.d.ts.map +1 -1
  38. package/dist/server/adapters/nuxt.js +493 -388
  39. package/dist/server/adapters/solid-start.d.ts.map +1 -1
  40. package/dist/server/adapters/solid-start.js +493 -388
  41. package/dist/server/adapters/svelte-kit.d.ts.map +1 -1
  42. package/dist/server/adapters/svelte-kit.js +37 -1
  43. package/dist/server/adapters/tanstack-start.d.ts.map +1 -1
  44. package/dist/server/adapters/tanstack-start.js +488 -388
  45. package/dist/server/adapters/utils.d.ts +1 -1
  46. package/dist/server/adapters/utils.d.ts.map +1 -1
  47. package/dist/server/{lib/build-activity-events.d.ts → handlers/activity/caliper.d.ts} +29 -4
  48. package/dist/server/handlers/activity/caliper.d.ts.map +1 -0
  49. package/dist/server/handlers/activity/gradebook.d.ts +56 -0
  50. package/dist/server/handlers/activity/gradebook.d.ts.map +1 -0
  51. package/dist/server/handlers/activity/handler.d.ts +15 -0
  52. package/dist/server/handlers/activity/handler.d.ts.map +1 -0
  53. package/dist/server/handlers/activity/index.d.ts +9 -0
  54. package/dist/server/handlers/activity/index.d.ts.map +1 -0
  55. package/dist/server/handlers/activity/resolve.d.ts +39 -0
  56. package/dist/server/handlers/activity/resolve.d.ts.map +1 -0
  57. package/dist/server/handlers/activity/schema.d.ts +52 -0
  58. package/dist/server/handlers/activity/schema.d.ts.map +1 -0
  59. package/dist/server/handlers/activity/types.d.ts +52 -0
  60. package/dist/server/handlers/activity/types.d.ts.map +1 -0
  61. package/dist/server/handlers/identity/handler.d.ts +14 -0
  62. package/dist/server/handlers/identity/handler.d.ts.map +1 -0
  63. package/dist/server/handlers/identity/index.d.ts +8 -0
  64. package/dist/server/handlers/identity/index.d.ts.map +1 -0
  65. package/dist/server/handlers/identity/oidc.d.ts +43 -0
  66. package/dist/server/handlers/identity/oidc.d.ts.map +1 -0
  67. package/dist/server/handlers/identity/types.d.ts +24 -0
  68. package/dist/server/handlers/identity/types.d.ts.map +1 -0
  69. package/dist/server/handlers/identity-only/handler.d.ts +15 -0
  70. package/dist/server/handlers/identity-only/handler.d.ts.map +1 -0
  71. package/dist/server/handlers/identity-only/index.d.ts +8 -0
  72. package/dist/server/handlers/identity-only/index.d.ts.map +1 -0
  73. package/dist/server/handlers/identity-only/oidc.d.ts +26 -0
  74. package/dist/server/handlers/identity-only/oidc.d.ts.map +1 -0
  75. package/dist/server/handlers/identity-only/types.d.ts +19 -0
  76. package/dist/server/handlers/identity-only/types.d.ts.map +1 -0
  77. package/dist/server/handlers/index.d.ts +5 -2
  78. package/dist/server/handlers/index.d.ts.map +1 -1
  79. package/dist/server/{lib/build-user-profile.d.ts → handlers/user/enrollments.d.ts} +7 -2
  80. package/dist/server/handlers/user/enrollments.d.ts.map +1 -0
  81. package/dist/server/handlers/user/handler.d.ts +17 -0
  82. package/dist/server/handlers/user/handler.d.ts.map +1 -0
  83. package/dist/server/handlers/user/index.d.ts +10 -0
  84. package/dist/server/handlers/user/index.d.ts.map +1 -0
  85. package/dist/server/handlers/user/profile.d.ts +22 -0
  86. package/dist/server/handlers/user/profile.d.ts.map +1 -0
  87. package/dist/server/handlers/user/types.d.ts +35 -0
  88. package/dist/server/handlers/user/types.d.ts.map +1 -0
  89. package/dist/server/handlers/user/verify.d.ts +25 -0
  90. package/dist/server/handlers/user/verify.d.ts.map +1 -0
  91. package/dist/server/index.d.ts +1 -1
  92. package/dist/server/index.d.ts.map +1 -1
  93. package/dist/server/lib/index.d.ts +4 -5
  94. package/dist/server/lib/index.d.ts.map +1 -1
  95. package/dist/server/lib/resolve.d.ts +4 -42
  96. package/dist/server/lib/resolve.d.ts.map +1 -1
  97. package/dist/server/lib/sso.d.ts +86 -0
  98. package/dist/server/lib/sso.d.ts.map +1 -0
  99. package/dist/server/lib/utils.d.ts +32 -1
  100. package/dist/server/lib/utils.d.ts.map +1 -1
  101. package/dist/server/timeback-identity.d.ts +2 -2
  102. package/dist/server/timeback-identity.d.ts.map +1 -1
  103. package/dist/server/timeback.d.ts.map +1 -1
  104. package/dist/server/types.d.ts +19 -12
  105. package/dist/server/types.d.ts.map +1 -1
  106. package/dist/shared/constants.d.ts +1 -0
  107. package/dist/shared/constants.d.ts.map +1 -1
  108. package/dist/shared/types.d.ts +18 -3
  109. package/dist/shared/types.d.ts.map +1 -1
  110. package/package.json +7 -7
  111. package/dist/config.d.ts +0 -20
  112. package/dist/config.d.ts.map +0 -1
  113. package/dist/config.js +0 -0
  114. package/dist/server/handlers/activity.d.ts +0 -25
  115. package/dist/server/handlers/activity.d.ts.map +0 -1
  116. package/dist/server/handlers/identity-full.d.ts +0 -28
  117. package/dist/server/handlers/identity-full.d.ts.map +0 -1
  118. package/dist/server/handlers/identity-only.d.ts +0 -22
  119. package/dist/server/handlers/identity-only.d.ts.map +0 -1
  120. package/dist/server/handlers/user.d.ts +0 -31
  121. package/dist/server/handlers/user.d.ts.map +0 -1
  122. package/dist/server/lib/build-activity-events.d.ts.map +0 -1
  123. package/dist/server/lib/build-user-profile.d.ts.map +0 -1
package/README.md CHANGED
@@ -40,7 +40,7 @@ bun add timeback
40
40
 
41
41
  ## Server Adapters
42
42
 
43
- All server adapters use the same core configuration:
43
+ All server adapters use the same core configuration and require a `timeback.config.json` file:
44
44
 
45
45
  ```typescript
46
46
  // lib/timeback.ts
@@ -420,7 +420,7 @@ The SDK resolves the Timeback user by email. You only need to provide the authen
420
420
  If you only need Timeback SSO authentication without activity tracking or Timeback API integration, use `createTimebackIdentity()`. This is a lightweight alternative that:
421
421
 
422
422
  - Does not require Timeback API credentials
423
- - Does not require `timeback.config.ts`
423
+ - Does not require `timeback.config.json`
424
424
  - Only exposes identity routes (sign-in, callback, sign-out)
425
425
  - Returns raw OIDC user info (no Timeback profile enrichment)
426
426
 
@@ -429,8 +429,8 @@ If you only need Timeback SSO authentication without activity tracking or Timeba
429
429
  ### Cloudflare Workers / workerd compatibility
430
430
 
431
431
  `createTimebackIdentity()` is runtime-agnostic, but the main `timeback` entrypoint also includes
432
- Node-oriented functionality (notably config loading via `jiti`). Some edge runtimes
433
- (Cloudflare Workers / workerd) do not support the Node modules that `jiti` depends on.
432
+ Node-oriented functionality (notably config loading via `c12`). Some edge runtimes
433
+ (Cloudflare Workers / workerd) do not support the Node modules that `c12` depends on.
434
434
 
435
435
  If you're deploying identity-only SSO on Workers/workerd, import from the worker-safe entrypoint:
436
436
 
@@ -542,7 +542,7 @@ await activity.end({
542
542
 
543
543
  The SDK automatically sends activity data to your server, which forwards it to the Timeback API.
544
544
 
545
- **Note:** Activity ingestion requires an **effective Caliper sensor URL per course** in `timeback.config.ts`.
545
+ **Note:** Activity ingestion requires an **effective Caliper sensor URL per course** in `timeback.config.json`.
546
546
 
547
547
  Sensor precedence (highest → lowest):
548
548
 
@@ -550,24 +550,23 @@ Sensor precedence (highest → lowest):
550
550
  - per-course base override: `course.sensor`
551
551
  - top-level default: `config.sensor`
552
552
 
553
- ```typescript
554
- export default {
555
- name: 'My App',
556
- sensor: 'https://my-app.example.com/sensors/default',
557
- courses: [
553
+ ```json
554
+ {
555
+ "$schema": "https://timeback.dev/schema.json",
556
+ "name": "My App",
557
+ "sensor": "https://my-app.example.com/sensors/default",
558
+ "courses": [
558
559
  {
559
- subject: 'Math',
560
- grade: 3,
561
- courseCode: 'MATH-3',
562
- // Base override (all envs)
563
- sensor: 'https://my-app.example.com/sensors/math',
564
- // Env-specific overrides (applied when syncing/editing in that env)
565
- overrides: {
566
- staging: { sensor: 'https://staging.my-app.example.com/sensors/math' },
567
- production: { sensor: 'https://my-app.example.com/sensors/math' },
568
- },
569
- },
570
- ],
560
+ "subject": "Math",
561
+ "grade": 3,
562
+ "courseCode": "MATH-3",
563
+ "sensor": "https://my-app.example.com/sensors/math",
564
+ "overrides": {
565
+ "staging": { "sensor": "https://staging.my-app.example.com/sensors/math" },
566
+ "production": { "sensor": "https://my-app.example.com/sensors/math" }
567
+ }
568
+ }
569
+ ]
571
570
  }
572
571
  ```
573
572
 
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Verification state for the current user.
3
+ */
4
+ export type TimebackVerificationState = {
5
+ status: 'loading';
6
+ } | {
7
+ status: 'verified';
8
+ timebackId: string;
9
+ } | {
10
+ status: 'unverified';
11
+ } | {
12
+ status: 'error';
13
+ message: string;
14
+ };
15
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/client/adapters/react/hooks/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAClC;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,GACxB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA"}
@@ -0,0 +1,18 @@
1
+ import type { TimebackVerificationState } from './types';
2
+ /**
3
+ * Verify the current user against Timeback.
4
+ *
5
+ * The hook runs automatically once the Timeback client is available, and
6
+ * provides a `refresh()` method to retry.
7
+ *
8
+ * @param options - Hook options
9
+ * @param options.enabled - If false, the hook does nothing and stays in loading state.
10
+ * @returns Verification state and a refresh method
11
+ */
12
+ export declare function useTimebackVerification(options?: {
13
+ enabled?: boolean;
14
+ }): {
15
+ state: TimebackVerificationState;
16
+ refresh: () => void;
17
+ };
18
+ //# sourceMappingURL=useTimebackVerification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTimebackVerification.d.ts","sourceRoot":"","sources":["../../../../../src/client/adapters/react/hooks/useTimebackVerification.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAsFxD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG;IAC7E,KAAK,EAAE,yBAAyB,CAAA;IAChC,OAAO,EAAE,MAAM,IAAI,CAAA;CACnB,CA6DA"}
@@ -40,6 +40,8 @@ export { createClient } from '../../timeback-client';
40
40
  export { TimebackClient } from '../../timeback-client.class';
41
41
  export { Activity } from '../../lib/activity';
42
42
  export { TimebackProvider, useTimeback } from './provider';
43
+ export { useTimebackVerification } from './hooks/useTimebackVerification';
44
+ export type { TimebackVerificationState } from './hooks/types';
43
45
  export { SignInButton } from './SignInButton';
44
46
  export type { SignInButtonProps } from './SignInButton';
45
47
  export type { TimebackIdentity, ActivityParams, ActivityMetrics } from '../../../shared/types';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/client/adapters/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAG5D,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAG7C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG1D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAGvD,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/client/adapters/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAG5D,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAG7C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AACzE,YAAY,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAA;AAG9D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAGvD,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA"}
@@ -8,7 +8,8 @@ var ROUTES = {
8
8
  CALLBACK: "/identity/callback"
9
9
  },
10
10
  USER: {
11
- ME: "/user/me"
11
+ ME: "/user/me",
12
+ VERIFY: "/user/verify"
12
13
  }
13
14
  };
14
15
 
@@ -23,6 +24,15 @@ function getDefaultBaseURL() {
23
24
  return `${window.location.origin}${DEFAULT_BASE_PATH}`;
24
25
  }
25
26
 
27
+ // src/client/lib/fetch.ts
28
+ function getDefaultFetch() {
29
+ const globalFetch = typeof globalThis === "undefined" ? undefined : globalThis.fetch;
30
+ if (!globalFetch) {
31
+ return;
32
+ }
33
+ return (input, init) => globalThis.fetch(input, init);
34
+ }
35
+
26
36
  // src/client/lib/activity/activity.class.ts
27
37
  class Activity {
28
38
  params;
@@ -128,14 +138,16 @@ class Auth {
128
138
  // src/client/namespaces/user.ts
129
139
  class User {
130
140
  getBaseURL;
131
- constructor(getBaseURL) {
141
+ fetchImpl;
142
+ constructor(getBaseURL, fetchImpl) {
132
143
  this.getBaseURL = getBaseURL;
144
+ this.fetchImpl = fetchImpl;
133
145
  }
134
146
  async fetch() {
135
147
  if (!isBrowser()) {
136
148
  throw new Error("user.fetch() requires a browser environment");
137
149
  }
138
- const response = await fetch(`${this.getBaseURL()}${ROUTES.USER.ME}`, {
150
+ const response = await this.fetchImpl(`${this.getBaseURL()}${ROUTES.USER.ME}`, {
139
151
  method: "GET",
140
152
  credentials: "include"
141
153
  });
@@ -145,6 +157,24 @@ class User {
145
157
  }
146
158
  return response.json();
147
159
  }
160
+ async verify() {
161
+ if (!isBrowser()) {
162
+ throw new Error("user.verify() requires a browser environment");
163
+ }
164
+ const response = await this.fetchImpl(`${this.getBaseURL()}${ROUTES.USER.VERIFY}`, {
165
+ method: "GET",
166
+ credentials: "include"
167
+ });
168
+ if (!response.ok) {
169
+ const errorResponse = await response.json().catch(() => ({ error: "Unknown error" }));
170
+ throw new Error(errorResponse.error ?? "Failed to verify Timeback user");
171
+ }
172
+ const data = await response.json();
173
+ if (data.verified && data.timebackId) {
174
+ return { verified: true, timebackId: data.timebackId };
175
+ }
176
+ return { verified: false };
177
+ }
148
178
  }
149
179
  // src/client/timeback-client.class.ts
150
180
  class TimebackClient {
@@ -152,11 +182,19 @@ class TimebackClient {
152
182
  auth;
153
183
  user;
154
184
  _baseURL;
185
+ _fetch;
155
186
  constructor(config = {}) {
156
187
  this._baseURL = config.baseURL;
188
+ const baseFetch = config.fetch ?? getDefaultFetch();
189
+ if (!baseFetch) {
190
+ throw new Error("TimebackClient requires a fetch implementation. " + "Provide `fetch` in the constructor config for non-browser runtimes.");
191
+ }
192
+ const rawPlugins = config.plugins;
193
+ const plugins = Array.isArray(rawPlugins) ? rawPlugins : rawPlugins ? [rawPlugins] : [];
194
+ this._fetch = plugins.reduce((f, p) => p.wrapFetch(f), baseFetch);
157
195
  this.activity = new ActivityManager((payload) => this.sendActivity(payload));
158
196
  this.auth = new Auth(() => this.baseURL);
159
- this.user = new User(() => this.baseURL);
197
+ this.user = new User(() => this.baseURL, this._fetch);
160
198
  }
161
199
  get baseURL() {
162
200
  if (!this._baseURL) {
@@ -169,7 +207,7 @@ class TimebackClient {
169
207
  return this._baseURL;
170
208
  }
171
209
  async sendActivity(payload) {
172
- const response = await fetch(`${this.baseURL}${ROUTES.ACTIVITY}`, {
210
+ const response = await this._fetch(`${this.baseURL}${ROUTES.ACTIVITY}`, {
173
211
  method: "POST",
174
212
  headers: { "Content-Type": "application/json" },
175
213
  body: JSON.stringify(payload),
@@ -185,7 +223,11 @@ class TimebackClient {
185
223
  // src/client/timeback-client.ts
186
224
  function createClient(config = {}) {
187
225
  const baseURL = config.baseURL ?? getDefaultBaseURL();
188
- return new TimebackClient({ baseURL });
226
+ return new TimebackClient({
227
+ baseURL,
228
+ fetch: config.fetch,
229
+ plugins: config.plugins
230
+ });
189
231
  }
190
232
  // src/client/adapters/react/provider.tsx
191
233
  import * as React from "react";
@@ -216,8 +258,95 @@ function TimebackProvider({
216
258
  function useTimeback() {
217
259
  return React.useContext(TimebackContext);
218
260
  }
219
- // src/client/adapters/react/SignInButton.tsx
261
+ // src/client/adapters/react/hooks/useTimebackVerification.ts
220
262
  import * as React2 from "react";
263
+ var VERIFY_CACHE_TTL_MS = 1500;
264
+ var verifyInFlight = new WeakMap;
265
+ var verifyCache = new WeakMap;
266
+ function isCacheFresh(entry) {
267
+ return !!entry && Date.now() - entry.atMs < VERIFY_CACHE_TTL_MS;
268
+ }
269
+ function toState(result) {
270
+ return result.verified ? { status: "verified", timebackId: result.timebackId } : { status: "unverified" };
271
+ }
272
+ async function verifyOnce(timeback, force) {
273
+ if (!force) {
274
+ const cached = verifyCache.get(timeback);
275
+ if (isCacheFresh(cached)) {
276
+ return cached.result;
277
+ }
278
+ const existing = verifyInFlight.get(timeback);
279
+ if (existing) {
280
+ return await existing;
281
+ }
282
+ }
283
+ const p = timeback.user.verify().then((result) => {
284
+ if (verifyInFlight.get(timeback) === p) {
285
+ verifyCache.set(timeback, { atMs: Date.now(), result });
286
+ }
287
+ return result;
288
+ });
289
+ verifyInFlight.set(timeback, p);
290
+ try {
291
+ return await p;
292
+ } finally {
293
+ if (verifyInFlight.get(timeback) === p) {
294
+ verifyInFlight.delete(timeback);
295
+ }
296
+ }
297
+ }
298
+ function useTimebackVerification(options = {}) {
299
+ const timeback = useTimeback();
300
+ const enabled = options.enabled ?? true;
301
+ const [state, setState] = React2.useState({ status: "loading" });
302
+ const [refreshNonce, setRefreshNonce] = React2.useState(0);
303
+ const lastHandledRefreshNonceRef = React2.useRef(0);
304
+ const refresh = React2.useCallback(() => {
305
+ setRefreshNonce((n) => n + 1);
306
+ }, []);
307
+ React2.useEffect(() => {
308
+ if (!enabled)
309
+ return;
310
+ let cancelled = false;
311
+ const force = refreshNonce > lastHandledRefreshNonceRef.current;
312
+ if (timeback && !force) {
313
+ const cached = verifyCache.get(timeback);
314
+ if (isCacheFresh(cached)) {
315
+ setState(toState(cached.result));
316
+ return () => {
317
+ cancelled = true;
318
+ };
319
+ }
320
+ }
321
+ setState({ status: "loading" });
322
+ (async () => {
323
+ try {
324
+ if (!timeback)
325
+ return;
326
+ if (force) {
327
+ lastHandledRefreshNonceRef.current = refreshNonce;
328
+ }
329
+ const result = await verifyOnce(timeback, force);
330
+ if (cancelled)
331
+ return;
332
+ setState(toState(result));
333
+ } catch (err) {
334
+ if (!cancelled) {
335
+ setState({
336
+ status: "error",
337
+ message: err instanceof Error ? err.message : "Failed to verify Timeback user"
338
+ });
339
+ }
340
+ }
341
+ })();
342
+ return () => {
343
+ cancelled = true;
344
+ };
345
+ }, [enabled, refreshNonce, timeback]);
346
+ return { state, refresh };
347
+ }
348
+ // src/client/adapters/react/SignInButton.tsx
349
+ import * as React3 from "react";
221
350
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
222
351
  function TimebackLogo({ className }) {
223
352
  return /* @__PURE__ */ jsxs("svg", {
@@ -426,9 +555,9 @@ function SignInButton(props) {
426
555
  showLogo = true
427
556
  } = props;
428
557
  const timeback = useTimeback();
429
- const [isLoading, setIsLoading] = React2.useState(false);
558
+ const [isLoading, setIsLoading] = React3.useState(false);
430
559
  const isReady = !!timeback;
431
- React2.useEffect(() => {
560
+ React3.useEffect(() => {
432
561
  injectStyles();
433
562
  }, []);
434
563
  const handleClick = (e) => {
@@ -466,6 +595,7 @@ function SignInButton(props) {
466
595
  });
467
596
  }
468
597
  export {
598
+ useTimebackVerification,
469
599
  useTimeback,
470
600
  createClient,
471
601
  TimebackProvider,
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Bearer Auth Plugin
3
+ *
4
+ * Adds `Authorization: Bearer <token>` to SDK requests.
5
+ */
6
+ import type { BearerAuthOptions, TimebackClientPlugin } from './types';
7
+ /**
8
+ * Create an auth plugin that attaches a Bearer token.
9
+ *
10
+ * This intentionally throws when no token is available so we never "silently"
11
+ * send an unauthenticated request that then 401s (terrible DX in dev + prod).
12
+ *
13
+ * @param options - The options for the bearer auth plugin.
14
+ * @returns The bearer auth plugin.
15
+ */
16
+ export declare function bearer(options: BearerAuthOptions): TimebackClientPlugin;
17
+ //# sourceMappingURL=bearer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../../../src/client/auth/bearer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAiB,MAAM,SAAS,CAAA;AAErF;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,oBAAoB,CAuBvE"}
@@ -0,0 +1,3 @@
1
+ export type { BearerAuthOptions, TimebackClientPlugin, TimebackFetch } from './types';
2
+ export { bearer } from './bearer';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACrF,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Client Auth Types
3
+ *
4
+ * Small, framework-agnostic extension point for customizing how the SDK makes HTTP requests.
5
+ */
6
+ export type TimebackFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
7
+ /**
8
+ * Auth plugin that can wrap the SDK's fetch implementation.
9
+ *
10
+ * Example use cases:
11
+ * - Add `Authorization: Bearer <token>` for resource-server architectures (Auth0 SPA + FastAPI)
12
+ * - Add custom headers (e.g. trace IDs) to all SDK requests
13
+ */
14
+ export interface TimebackClientPlugin {
15
+ /**
16
+ * Wrap a fetch implementation and return a new one.
17
+ */
18
+ wrapFetch(fetch: TimebackFetch): TimebackFetch;
19
+ }
20
+ /**
21
+ * Options for Bearer auth plugin.
22
+ */
23
+ export interface BearerAuthOptions {
24
+ /**
25
+ * Return the current access token (e.g. Auth0 getAccessTokenSilently()).
26
+ *
27
+ * Return undefined if the user is not authenticated yet.
28
+ */
29
+ getToken: () => Promise<string | undefined> | string | undefined;
30
+ /**
31
+ * Header name to set. Defaults to `Authorization`.
32
+ */
33
+ headerName?: string;
34
+ /**
35
+ * Optional prefix. Defaults to `Bearer `.
36
+ */
37
+ prefix?: string;
38
+ }
39
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/client/auth/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAE/F;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACpC;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAAA;CAC9C;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAAA;IAEhE;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CACf"}
@@ -5,5 +5,7 @@
5
5
  */
6
6
  export { createClient } from './timeback-client';
7
7
  export { TimebackClient } from './timeback-client.class';
8
+ export { bearer } from './auth';
9
+ export type { BearerAuthOptions, TimebackClientPlugin, TimebackFetch } from './auth';
8
10
  export { Activity } from './lib/activity';
9
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAGxD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAGxD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AAGpF,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Fetch Utilities
3
+ *
4
+ * Helpers for selecting a safe fetch implementation across runtimes.
5
+ */
6
+ import type { TimebackFetch } from '../auth/types';
7
+ /**
8
+ * Get a safe default fetch implementation.
9
+ *
10
+ * Some runtimes (notably Safari/WebKit) can throw "Illegal invocation" if the
11
+ * global `fetch` is called unbound (e.g. stored in a variable then invoked).
12
+ *
13
+ * This function returns a wrapper that always calls `fetch` via property access
14
+ * on `globalThis`, preserving the correct receiver.
15
+ *
16
+ * @returns The default fetch implementation.
17
+ */
18
+ export declare function getDefaultFetch(): TimebackFetch | undefined;
19
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../src/client/lib/fetch.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAElD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,IAAI,aAAa,GAAG,SAAS,CAQ3D"}
@@ -3,18 +3,21 @@
3
3
  *
4
4
  * User identity and profile data.
5
5
  */
6
- import type { TimebackProfile } from '../../shared/types';
6
+ import type { TimebackProfile, TimebackVerifyResult } from '../../shared/types';
7
+ import type { TimebackFetch } from '../auth/types';
7
8
  /**
8
9
  * User namespace for profile data.
9
10
  */
10
11
  export declare class User {
11
12
  private readonly getBaseURL;
13
+ private readonly fetchImpl;
12
14
  /**
13
15
  * Create user instance.
14
16
  *
15
17
  * @param getBaseURL - Function that returns the base URL for API routes
18
+ * @param fetchImpl - Fetch implementation to use for requests
16
19
  */
17
- constructor(getBaseURL: () => string);
20
+ constructor(getBaseURL: () => string, fetchImpl: TimebackFetch);
18
21
  /**
19
22
  * Fetch the full user profile from the Timeback API.
20
23
  *
@@ -25,5 +28,25 @@ export declare class User {
25
28
  * @throws Error if not authenticated or API call fails
26
29
  */
27
30
  fetch(): Promise<TimebackProfile>;
31
+ /**
32
+ * Verify if the current user exists in Timeback.
33
+ *
34
+ * Use this to check eligibility before enabling Timeback-gated features
35
+ * (e.g., free tier for Timeback users in a partner app).
36
+ *
37
+ * @returns Verification result with eligibility status and timebackId if eligible
38
+ * @throws Error if not authenticated or API call fails unexpectedly
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const result = await timeback.user.verify()
43
+ * if (result.verified) {
44
+ * console.log('Timeback user:', result.timebackId)
45
+ * } else {
46
+ * console.log('Not a Timeback user')
47
+ * }
48
+ * ```
49
+ */
50
+ verify(): Promise<TimebackVerifyResult>;
28
51
  }
29
52
  //# sourceMappingURL=user.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../../src/client/namespaces/user.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD;;GAEG;AACH,qBAAa,IAAI;IAMJ,OAAO,CAAC,QAAQ,CAAC,UAAU;IALvC;;;;OAIG;IACH,YAA6B,UAAU,EAAE,MAAM,MAAM,EAAI;IAEzD;;;;;;;;OAQG;IACG,KAAK,IAAI,OAAO,CAAC,eAAe,CAAC,CAoBtC;CACD"}
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../../src/client/namespaces/user.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAElD;;GAEG;AACH,qBAAa,IAAI;IAQf,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAR3B;;;;;OAKG;IACH,YACkB,UAAU,EAAE,MAAM,MAAM,EACxB,SAAS,EAAE,aAAa,EACtC;IAEJ;;;;;;;;OAQG;IACG,KAAK,IAAI,OAAO,CAAC,eAAe,CAAC,CAoBtC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACG,MAAM,IAAI,OAAO,CAAC,oBAAoB,CAAC,CA0B5C;CACD"}
@@ -4,12 +4,26 @@
4
4
  * Client-side SDK for activity tracking and identity.
5
5
  */
6
6
  import { ActivityManager, Auth, User } from './namespaces';
7
+ import type { TimebackClientPlugin, TimebackFetch } from './auth/types';
7
8
  /**
8
9
  * Timeback client configuration.
9
10
  */
10
11
  interface TimebackClientConfig {
11
12
  /** Base URL for Timeback API routes. Defaults to window.location.origin + '/api/timeback' */
12
13
  baseURL?: string;
14
+ /**
15
+ * Fetch implementation used by the client.
16
+ *
17
+ * Defaults to the global `fetch` when available.
18
+ */
19
+ fetch?: TimebackFetch;
20
+ /**
21
+ * Optional plugin(s) that can wrap the client fetch.
22
+ *
23
+ * Use this to attach Bearer tokens (Auth0 SPA PKCE) without monkey-patching
24
+ * global `window.fetch`.
25
+ */
26
+ plugins?: TimebackClientPlugin | TimebackClientPlugin[];
13
27
  }
14
28
  /**
15
29
  * Timeback client for activity tracking and identity.
@@ -19,6 +33,7 @@ export declare class TimebackClient {
19
33
  readonly auth: Auth;
20
34
  readonly user: User;
21
35
  private _baseURL;
36
+ private readonly _fetch;
22
37
  /**
23
38
  * Create a new Timeback client.
24
39
  *
@@ -1 +1 @@
1
- {"version":3,"file":"timeback-client.class.d.ts","sourceRoot":"","sources":["../../src/client/timeback-client.class.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAI1D;;GAEG;AACH,UAAU,oBAAoB;IAC7B,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,qBAAa,cAAc;IAC1B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAA;IAClC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,OAAO,CAAC,QAAQ,CAAoB;IAEpC;;;;OAIG;IACH,YAAY,MAAM,GAAE,oBAAyB,EAK5C;IAED;;;;OAIG;IACH,OAAO,KAAK,OAAO,GAYlB;YAOa,YAAY;CAiB1B"}
1
+ {"version":3,"file":"timeback-client.class.d.ts","sourceRoot":"","sources":["../../src/client/timeback-client.class.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAG1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAEvE;;GAEG;AACH,UAAU,oBAAoB;IAC7B,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,oBAAoB,GAAG,oBAAoB,EAAE,CAAA;CACvD;AAED;;GAEG;AACH,qBAAa,cAAc;IAC1B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAA;IAClC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IAEtC;;;;OAIG;IACH,YAAY,MAAM,GAAE,oBAAyB,EAmB5C;IAED;;;;OAIG;IACH,OAAO,KAAK,OAAO,GAYlB;YAOa,YAAY;CAiB1B"}
@@ -4,6 +4,7 @@
4
4
  * Factory function for creating Timeback client instances.
5
5
  */
6
6
  import { TimebackClient } from './timeback-client.class';
7
+ import type { TimebackClientPlugin, TimebackFetch } from './auth/types';
7
8
  /**
8
9
  * Create a Timeback client instance.
9
10
  *
@@ -25,5 +26,7 @@ import { TimebackClient } from './timeback-client.class';
25
26
  */
26
27
  export declare function createClient(config?: {
27
28
  baseURL?: string;
29
+ fetch?: TimebackFetch;
30
+ plugins?: TimebackClientPlugin | TimebackClientPlugin[];
28
31
  }): TimebackClient;
29
32
  //# sourceMappingURL=timeback-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"timeback-client.d.ts","sourceRoot":"","sources":["../../src/client/timeback-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAExD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,cAAc,CAG9E"}
1
+ {"version":3,"file":"timeback-client.d.ts","sourceRoot":"","sources":["../../src/client/timeback-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAExD,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAEvE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAC3B,MAAM,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,OAAO,CAAC,EAAE,oBAAoB,GAAG,oBAAoB,EAAE,CAAA;CAClD,GACJ,cAAc,CAOhB"}
package/dist/client.d.ts CHANGED
@@ -26,5 +26,6 @@
26
26
  */
27
27
  export { createClient, TimebackClient } from './client/index';
28
28
  export { Activity } from './client/index';
29
- export type { TimebackIdentity, TimebackProfile, TimebackSessionUser, ActivityParams, ActivityMetrics, } from './shared/types';
29
+ export { bearer } from './client/auth/bearer';
30
+ export type { TimebackIdentity, TimebackProfile, TimebackSessionUser, TimebackVerifyResult, ActivityParams, ActivityMetrics, } from './shared/types';
30
31
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,YAAY,EACX,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,eAAe,GACf,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAE7C,YAAY,EACX,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,eAAe,GACf,MAAM,gBAAgB,CAAA"}