@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
@@ -3,33 +3,5 @@
3
3
  *
4
4
  * Types for Svelte-specific stores.
5
5
  */
6
- import type { TimebackProfile } from '../../../shared/types';
7
- /**
8
- * Verification state for the current user.
9
- */
10
- export type TimebackVerificationState = {
11
- status: 'loading';
12
- } | {
13
- status: 'verified';
14
- timebackId: string;
15
- } | {
16
- status: 'unverified';
17
- } | {
18
- status: 'error';
19
- message: string;
20
- };
21
- /**
22
- * Profile state for the current user.
23
- */
24
- export type TimebackProfileState = {
25
- status: 'idle';
26
- } | {
27
- status: 'loading';
28
- } | {
29
- status: 'loaded';
30
- profile: TimebackProfile;
31
- } | {
32
- status: 'error';
33
- message: string;
34
- };
6
+ export type { TimebackProfileState, TimebackVerificationState } from '../../../shared/types';
35
7
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/client/adapters/svelte/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAE5D;;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;AAEvC;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAC7B;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,eAAe,CAAA;CAAE,GAC9C;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/client/adapters/svelte/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../../src/client/adapters/vue/provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAGrD,OAAO,KAAK,EAAgB,QAAQ,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAqC7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,gBAAgB;IAG3B,wFAAwF;;;;;;;;IAAxF,wFAAwF;;;;;;;0FAoBxF,CAAA;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,WAAW,IAAI,UAAU,CAAC,cAAc,GAAG,SAAS,CAAC,CAQpE"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../../src/client/adapters/vue/provider.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAIrD,OAAO,KAAK,EAAgB,QAAQ,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AA2B7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,gBAAgB;IAG3B,wFAAwF;;;;;;;;IAAxF,wFAAwF;;;;;;;0FAoBxF,CAAA;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,WAAW,IAAI,UAAU,CAAC,cAAc,GAAG,SAAS,CAAC,CAQpE"}
@@ -1,6 +1,9 @@
1
- import { TimebackClient } from '@timeback/sdk/client'
2
1
  import { defineComponent, inject, onMounted, provide, shallowRef } from 'vue'
3
2
 
3
+ import { TimebackClient } from '@timeback/sdk/client'
4
+
5
+ import { isBrowser } from '../../lib/utils'
6
+
4
7
  import type { InjectionKey, PropType, ShallowRef } from 'vue'
5
8
 
6
9
  /**
@@ -14,16 +17,6 @@ import type { InjectionKey, PropType, ShallowRef } from 'vue'
14
17
  */
15
18
  const TimebackKey: InjectionKey<ShallowRef<TimebackClient | undefined>> = Symbol('timeback')
16
19
 
17
- /**
18
- * Check if we're running in the browser.
19
- *
20
- * @returns True if in browser
21
- */
22
- function isBrowser(): boolean {
23
- return typeof window !== 'undefined'
24
- }
25
-
26
- // Singleton client instance for browser
27
20
  let clientInstance: TimebackClient | undefined
28
21
 
29
22
  /**
@@ -3,35 +3,8 @@
3
3
  *
4
4
  * Types for Vue-specific composables.
5
5
  */
6
- import type { TimebackProfile } from '../../../shared/types';
7
- /**
8
- * Verification state for the current user.
9
- */
10
- export type TimebackVerificationState = {
11
- status: 'loading';
12
- } | {
13
- status: 'verified';
14
- timebackId: string;
15
- } | {
16
- status: 'unverified';
17
- } | {
18
- status: 'error';
19
- message: string;
20
- };
21
- /**
22
- * Profile state for the current user.
23
- */
24
- export type TimebackProfileState = {
25
- status: 'idle';
26
- } | {
27
- status: 'loading';
28
- } | {
29
- status: 'loaded';
30
- profile: TimebackProfile;
31
- } | {
32
- status: 'error';
33
- message: string;
34
- };
6
+ import type { TimebackProfileState, TimebackVerificationState } from '../../../shared/types';
7
+ export type { TimebackProfileState, TimebackVerificationState } from '../../../shared/types';
35
8
  /**
36
9
  * Options for the useTimebackVerification composable.
37
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/client/adapters/vue/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAE5D;;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;AAEvC;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAC7B;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,eAAe,CAAA;CAAE,GAC9C;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvC;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC7C,gDAAgD;IAChD,KAAK,EAAE,yBAAyB,CAAA;IAEhC,qDAAqD;IACrD,OAAO,EAAE,MAAM,IAAI,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACzC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACd;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,2CAA2C;IAC3C,KAAK,EAAE,oBAAoB,CAAA;IAE3B,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAA;IAEjB,oEAAoE;IACpE,YAAY,EAAE,MAAM,IAAI,CAAA;IAExB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,IAAI,CAAA;CACnB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/client/adapters/vue/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAA;AAE5F,YAAY,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAA;AAE5F;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC7C,gDAAgD;IAChD,KAAK,EAAE,yBAAyB,CAAA;IAEhC,qDAAqD;IACrD,OAAO,EAAE,MAAM,IAAI,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACzC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACd;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,2CAA2C;IAC3C,KAAK,EAAE,oBAAoB,CAAA;IAE3B,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAA;IAEjB,oEAAoE;IACpE,YAAY,EAAE,MAAM,IAAI,CAAA;IAExB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,IAAI,CAAA;CACnB"}
@@ -4,25 +4,9 @@
4
4
  * Types for Vue-specific composables.
5
5
  */
6
6
 
7
- import type { TimebackProfile } from '../../../shared/types'
7
+ import type { TimebackProfileState, TimebackVerificationState } from '../../../shared/types'
8
8
 
9
- /**
10
- * Verification state for the current user.
11
- */
12
- export type TimebackVerificationState =
13
- | { status: 'loading' }
14
- | { status: 'verified'; timebackId: string }
15
- | { status: 'unverified' }
16
- | { status: 'error'; message: string }
17
-
18
- /**
19
- * Profile state for the current user.
20
- */
21
- export type TimebackProfileState =
22
- | { status: 'idle' }
23
- | { status: 'loading' }
24
- | { status: 'loaded'; profile: TimebackProfile }
25
- | { status: 'error'; message: string }
9
+ export type { TimebackProfileState, TimebackVerificationState } from '../../../shared/types'
26
10
 
27
11
  /**
28
12
  * Options for the useTimebackVerification composable.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Client Auth Types
3
3
  *
4
- * Small, framework-agnostic extension point for customizing how the SDK makes HTTP requests.
4
+ * Extension point for customizing how the SDK makes HTTP requests.
5
5
  */
6
6
  export type TimebackFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
7
7
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Client Module
3
3
  *
4
- * Framework-agnostic client exports.
4
+ * Client exports.
5
5
  */
6
6
  export { createClient } from './timeback-client';
7
7
  export { TimebackClient } from './timeback-client.class';
@@ -1,32 +1,56 @@
1
1
  /**
2
2
  * Activity Class
3
3
  *
4
- * Client-side activity tracking logic.
4
+ * Client-side activity tracking with continuous time-spent reporting.
5
5
  */
6
- import type { ActivityEndData, ActivityEndPayload, ActivityParams } from '../../../shared/types';
6
+ import type { ActivityEndData, ActivityParams } from '../../../shared/types';
7
+ import type { ActivityCallbacks } from './types';
7
8
  /**
8
- * Activity tracker that tracks time spent on an activity.
9
+ * Activity tracker with continuous heartbeat reporting.
9
10
  *
10
- * Activities are started immediately on construction. Use `pause()`, `resume()`,
11
+ * Activities are started immediately on construction. Heartbeats are sent
12
+ * at regular intervals to report time spent. Use `pause()`, `resume()`,
11
13
  * and `end()` to control the activity lifecycle.
12
14
  */
13
15
  export declare class Activity {
14
16
  private readonly params;
15
- private readonly sendActivity;
17
+ /** Activity slug passed to `start()`. */
18
+ readonly id: string;
19
+ readonly runId: string;
16
20
  private readonly _startedAt;
21
+ private readonly _timeEnabled;
22
+ private readonly _timeOptions;
23
+ private readonly _callbacks;
24
+ private _windowStartMs;
25
+ private _activeSinceMs;
26
+ private _accumulatedActiveMs;
27
+ private _totalFlushedActiveMs;
17
28
  private _isPaused;
18
- private _pausedAt;
19
- private _totalPausedMs;
20
29
  private _ended;
30
+ private _ending;
31
+ private _heartbeatTimer;
32
+ private _flushInFlight;
33
+ private _boundVisibilityHandler;
34
+ private _boundPageHideHandler;
35
+ private _hiddenTimeoutTimer;
36
+ private _timedOutWhileHidden;
21
37
  /**
22
38
  * Create and start a new activity tracker.
23
39
  *
24
- * The activity timer begins immediately.
40
+ * The activity timer begins immediately. Heartbeats are sent at regular
41
+ * intervals (default: 15 seconds) to report accumulated time.
25
42
  *
26
43
  * @param params - Activity parameters
27
- * @param sendActivity - Function to send activity to server
44
+ * @param callbacks - Network operation callbacks
28
45
  */
29
- constructor(params: ActivityParams, sendActivity: (payload: ActivityEndPayload) => Promise<void>);
46
+ constructor(params: ActivityParams, callbacks: ActivityCallbacks);
47
+ /**
48
+ * Build an error context object for the `onError` callback.
49
+ *
50
+ * @param type - Which operation failed
51
+ * @returns Error context
52
+ */
53
+ private _errorContext;
30
54
  /**
31
55
  * When the activity was started.
32
56
  *
@@ -40,34 +64,118 @@ export declare class Activity {
40
64
  */
41
65
  get isPaused(): boolean;
42
66
  /**
43
- * Total elapsed time in milliseconds (excluding paused time).
67
+ * Whether the activity has ended.
44
68
  *
45
- * @returns Elapsed milliseconds (0 if ended)
69
+ * @returns True if `end()` completed successfully
70
+ */
71
+ get isEnded(): boolean;
72
+ /**
73
+ * Active time in the current heartbeat window in milliseconds.
74
+ *
75
+ * Resets to 0 after each flush (heartbeat). For total time across all
76
+ * windows, use {@link totalActiveMs}.
77
+ *
78
+ * @returns Active milliseconds in the current window
46
79
  */
47
80
  get elapsedMs(): number;
48
81
  /**
49
- * Build the activity end payload without sending it.
82
+ * Total active time in milliseconds since the activity started.
50
83
  *
51
- * Useful for "preview" flows in examples where you want to inspect the payload
52
- * that would be POSTed to the server without calling `end()`.
84
+ * Accumulates across all flushed heartbeat windows plus the current
85
+ * in-progress window. Not affected by pause/resume or hidden timeouts
86
+ * (those reset the window but already-flushed time is preserved).
53
87
  *
54
- * @param data - Activity completion data (metrics + optional time override)
55
- * @returns Activity end payload
88
+ * @returns Total active milliseconds
89
+ */
90
+ get totalActiveMs(): number;
91
+ /**
92
+ * Get accumulated active time in the current window, including any in-progress period.
93
+ *
94
+ * @returns Active milliseconds in current window
56
95
  */
57
- _buildPayload(data: ActivityEndData): ActivityEndPayload;
96
+ private _getCurrentWindowActiveMs;
58
97
  /**
59
- * Pause the activity timer.
98
+ * Start the heartbeat interval timer.
99
+ */
100
+ private _startHeartbeatTimer;
101
+ /**
102
+ * Stop the heartbeat interval timer.
103
+ */
104
+ private _stopHeartbeatTimer;
105
+ /**
106
+ * Restart the heartbeat interval timer.
107
+ *
108
+ * Used after visibility-triggered flushes to ensure the next heartbeat
109
+ * fires at a full interval from when the page became visible again.
110
+ */
111
+ private _restartHeartbeatTimer;
112
+ /**
113
+ * Set up visibility and pagehide event listeners for time tracking.
114
+ */
115
+ private _setupVisibilityHandlers;
116
+ /**
117
+ * Clean up event listeners.
118
+ */
119
+ private _teardownVisibilityHandlers;
120
+ /**
121
+ * Handle visibility change events — pause/resume time accumulation and flush as configured.
122
+ */
123
+ private _handleVisibilityChange;
124
+ /**
125
+ * Start the hidden timeout timer.
126
+ *
127
+ * After this timeout expires, heartbeats stop and the tracking window
128
+ * resets when the user returns so the extended absence isn't counted.
129
+ */
130
+ private _startHiddenTimeout;
131
+ /**
132
+ * Clear the hidden timeout timer.
133
+ */
134
+ private _clearHiddenTimeout;
135
+ /**
136
+ * Handle pagehide events — best-effort flush via sendBeacon or fetch keepalive.
137
+ */
138
+ private _handlePageHide;
139
+ private _flushSync;
140
+ /**
141
+ * Build heartbeat payload for the current time window.
142
+ *
143
+ * @param endMs - End timestamp in milliseconds
144
+ * @param activeMs - Active time in milliseconds
145
+ * @returns Heartbeat payload
146
+ */
147
+ private _buildHeartbeatPayload;
148
+ /**
149
+ * Flush accumulated time to server.
150
+ *
151
+ * Serialized — only one flush can be in flight at a time. No-op when
152
+ * time tracking is disabled (`time: false`).
153
+ *
154
+ * @returns Promise that resolves when flush completes
155
+ */
156
+ flushTimeSpent(): Promise<void>;
157
+ /**
158
+ * Pause the activity.
159
+ *
160
+ * Flushes accumulated time, then stops heartbeats until `resume()` is called.
60
161
  */
61
162
  pause(): void;
62
163
  /**
63
- * Resume the activity timer.
164
+ * Resume the activity.
165
+ *
166
+ * Starts a fresh tracking window and restarts heartbeats.
64
167
  */
65
168
  resume(): void;
66
169
  /**
67
- * End the activity and send to server.
170
+ * End the activity.
171
+ *
172
+ * Performs a final heartbeat flush then sends the completion event
173
+ * (if metrics or pctComplete are provided). If the network call fails,
174
+ * the activity remains usable so the consumer can retry.
68
175
  *
69
176
  * @param data - Activity completion data (metrics + optional time override)
70
177
  */
71
- end(data: ActivityEndData): Promise<void>;
178
+ end(data?: ActivityEndData): Promise<void>;
179
+ private _sendCompletion;
72
180
  }
73
181
  //# sourceMappingURL=activity.class.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"activity.class.d.ts","sourceRoot":"","sources":["../../../../src/client/lib/activity/activity.class.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAEhG;;;;;GAKG;AACH,qBAAa,QAAQ;IAgBnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAhB9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAM;IACjC,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,MAAM,CAAQ;IAEtB;;;;;;;OAOG;IACH,YACkB,MAAM,EAAE,cAAc,EACtB,YAAY,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,EAG7E;IAED;;;;OAIG;IACH,IAAI,SAAS,IAAI,IAAI,CAEpB;IAED;;;;OAIG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;;OAIG;IACH,IAAI,SAAS,IAAI,MAAM,CAatB;IAED;;;;;;;;OAQG;IACH,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,kBAAkB,CAiDvD;IAED;;OAEG;IACH,KAAK,IAAI,IAAI,CAIZ;IAED;;OAEG;IACH,MAAM,IAAI,IAAI,CAKb;IAED;;;;OAIG;IACG,GAAG,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAa9C;CACD"}
1
+ {"version":3,"file":"activity.class.d.ts","sourceRoot":"","sources":["../../../../src/client/lib/activity/activity.class.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EACX,eAAe,EAGf,cAAc,EAGd,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAEhD;;;;;;GAMG;AACH,qBAAa,QAAQ;IAsCnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IArCxB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IAEtB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAM;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA+B;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmB;IAE9C,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,cAAc,CAAe;IACrC,OAAO,CAAC,oBAAoB,CAAI;IAChC,OAAO,CAAC,qBAAqB,CAAI;IAEjC,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,OAAO,CAAQ;IAEvB,OAAO,CAAC,eAAe,CAA8C;IAErE,OAAO,CAAC,cAAc,CAA6B;IAEnD,OAAO,CAAC,uBAAuB,CAA4B;IAC3D,OAAO,CAAC,qBAAqB,CAA4B;IACzD,OAAO,CAAC,mBAAmB,CAA6C;IACxE,OAAO,CAAC,oBAAoB,CAAQ;IAEpC;;;;;;;;OAQG;IACH,YACkB,MAAM,EAAE,cAAc,EACvC,SAAS,EAAE,iBAAiB,EA+B5B;IAED;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAIrB;;;;OAIG;IACH,IAAI,SAAS,IAAI,IAAI,CAEpB;IAED;;;;OAIG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;;OAIG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;;;;OAOG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;;;;;;;OAQG;IACH,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED;;;;OAIG;IACH,OAAO,CAAC,yBAAyB;IAUjC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAQ5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAO3B;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAK9B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAchC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAcnC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IA0C/B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAO3B;;OAEG;IACH,OAAO,CAAC,eAAe;YAuBT,UAAU;IAoBxB;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;IAe9B;;;;;;;OAOG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CA0DpC;IAED;;;;OAIG;IACH,KAAK,IAAI,IAAI,CAcZ;IAED;;;;OAIG;IACH,MAAM,IAAI,IAAI,CAkBb;IAED;;;;;;;;OAQG;IACG,GAAG,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqDnD;YAOa,eAAe;CA4C7B"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Activity Transport
3
+ *
4
+ * HTTP transport for activity heartbeats and submissions.
5
+ * Encapsulates page-exit transport policy (sendBeacon vs keepalive fetch).
6
+ */
7
+ import type { ActivityTransport, ActivityTransportConfig } from './types';
8
+ /**
9
+ * Create activity transport.
10
+ *
11
+ * @param config - Transport configuration
12
+ * @returns Activity transport
13
+ */
14
+ export declare function createActivityTransport(config: ActivityTransportConfig): ActivityTransport;
15
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../../src/client/lib/activity/transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;AAEzE;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,uBAAuB,GAAG,iBAAiB,CAmH1F"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Activity Module Types
3
+ *
4
+ * Internal types for activity tracking.
5
+ */
6
+ import type { ActivityHeartbeatPayload, ActivitySubmitPayload } from '../../../shared/types';
7
+ import type { TimebackFetch } from '../../auth/types';
8
+ /**
9
+ * Callbacks for activity network operations.
10
+ */
11
+ export interface ActivityCallbacks {
12
+ /** Send heartbeat (normal) */
13
+ sendHeartbeat: (payload: ActivityHeartbeatPayload) => Promise<void>;
14
+ /** Send heartbeat on page exit (beacon/keepalive) */
15
+ sendHeartbeatOnPageHide: (payload: ActivityHeartbeatPayload) => Promise<void>;
16
+ /** Send completion */
17
+ sendSubmit: (payload: ActivitySubmitPayload) => Promise<void>;
18
+ }
19
+ /**
20
+ * Configuration for activity transport.
21
+ */
22
+ export interface ActivityTransportConfig {
23
+ /** Base URL for Timeback API routes */
24
+ baseURL: string;
25
+ /** Fetch implementation (possibly plugin-wrapped) */
26
+ fetch: TimebackFetch;
27
+ /**
28
+ * Whether sendBeacon is safe to use on page exit.
29
+ *
30
+ * False when fetch plugins are present (e.g. for bearer token auth),
31
+ * since sendBeacon cannot set Authorization headers.
32
+ */
33
+ canUseBeacon: boolean;
34
+ /**
35
+ * Credentials policy for fetch requests.
36
+ *
37
+ * Defaults to `'include'` (sends cookies cross-origin). Use `'same-origin'`
38
+ * or `'omit'` for token-based or cross-origin setups where cookies are not needed.
39
+ */
40
+ credentials: RequestCredentials;
41
+ }
42
+ /**
43
+ * Activity transport interface.
44
+ */
45
+ export interface ActivityTransport {
46
+ /** Send heartbeat (normal fetch) */
47
+ sendHeartbeat: (payload: ActivityHeartbeatPayload) => Promise<void>;
48
+ /** Send heartbeat on page exit (beacon or keepalive fetch) */
49
+ sendHeartbeatOnPageHide: (payload: ActivityHeartbeatPayload) => Promise<void>;
50
+ /** Send completion (normal fetch) */
51
+ sendSubmit: (payload: ActivitySubmitPayload) => Promise<void>;
52
+ }
53
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/client/lib/activity/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC5F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAMrD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,8BAA8B;IAC9B,aAAa,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACnE,qDAAqD;IACrD,uBAAuB,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7E,sBAAsB;IACtB,UAAU,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7D;AAMD;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAA;IACf,qDAAqD;IACrD,KAAK,EAAE,aAAa,CAAA;IACpB;;;;;OAKG;IACH,YAAY,EAAE,OAAO,CAAA;IACrB;;;;;OAKG;IACH,WAAW,EAAE,kBAAkB,CAAA;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,oCAAoC;IACpC,aAAa,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACnE,8DAA8D;IAC9D,uBAAuB,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7E,qCAAqC;IACrC,UAAU,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7D"}
@@ -32,4 +32,22 @@ export declare function sleep(ms: number): Promise<void>;
32
32
  * @returns Delay in milliseconds
33
33
  */
34
34
  export declare function getRetryDelay(delays: number | readonly number[], attempt: number): number;
35
+ /**
36
+ * Execute an async operation with optional retry and exponential backoff.
37
+ *
38
+ * @param fn - The async operation to attempt
39
+ * @param attempts - Number of retries (0 = no retry, just one attempt)
40
+ * @param delays - Delay schedule between retries
41
+ * @returns The result of `fn`
42
+ */
43
+ export declare function withRetry<T>(fn: () => Promise<T>, attempts: number, delays: number | readonly number[]): Promise<T>;
44
+ /**
45
+ * Generate a unique run identifier.
46
+ *
47
+ * Uses `crypto.randomUUID()` when available, with a Math.random() fallback
48
+ * for older browsers where crypto is unavailable.
49
+ *
50
+ * @returns Generated UUID string
51
+ */
52
+ export declare function generateRunId(): string;
35
53
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/client/lib/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAMtD;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/C;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/client/lib/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAMtD;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/C;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAChC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAChC,OAAO,CAAC,CAAC,CAAC,CAgBZ;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAWtC"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Client Utilities
3
+ *
4
+ * Internal utility functions for the client SDK.
5
+ */
6
+
7
+ import { DEFAULT_BASE_PATH } from '../../shared/constants'
8
+
9
+ /**
10
+ * Check if we're in a browser environment.
11
+ *
12
+ * @returns True if in browser
13
+ */
14
+ export function isBrowser(): boolean {
15
+ return typeof window !== 'undefined'
16
+ }
17
+
18
+ /**
19
+ * Get the default base URL.
20
+ *
21
+ * Returns undefined during SSR - baseURL is resolved lazily on first API call.
22
+ *
23
+ * @returns The default base URL or undefined during SSR
24
+ */
25
+ export function getDefaultBaseURL(): string | undefined {
26
+ if (!isBrowser()) {
27
+ return undefined
28
+ }
29
+
30
+ return `${window.location.origin}${DEFAULT_BASE_PATH}`
31
+ }
32
+
33
+ /**
34
+ * Sleep for a given number of milliseconds.
35
+ *
36
+ * @param ms - Duration to sleep in milliseconds
37
+ * @returns Promise that resolves after the delay
38
+ */
39
+ export function sleep(ms: number): Promise<void> {
40
+ return new Promise<void>(resolve => {
41
+ setTimeout(resolve, ms)
42
+ })
43
+ }
44
+
45
+ /**
46
+ * Get retry delay for a given attempt from a delay configuration.
47
+ *
48
+ * @param delays - Single delay or array of delays per attempt
49
+ * @param attempt - Zero-based attempt index
50
+ * @returns Delay in milliseconds
51
+ */
52
+ export function getRetryDelay(delays: number | readonly number[], attempt: number): number {
53
+ if (typeof delays === 'number') {
54
+ return delays
55
+ }
56
+
57
+ return delays[Math.min(attempt, delays.length - 1)] ?? 1000
58
+ }
59
+
60
+ /**
61
+ * Execute an async operation with optional retry and exponential backoff.
62
+ *
63
+ * @param fn - The async operation to attempt
64
+ * @param attempts - Number of retries (0 = no retry, just one attempt)
65
+ * @param delays - Delay schedule between retries
66
+ * @returns The result of `fn`
67
+ */
68
+ export async function withRetry<T>(
69
+ fn: () => Promise<T>,
70
+ attempts: number,
71
+ delays: number | readonly number[],
72
+ ): Promise<T> {
73
+ let lastError: unknown
74
+
75
+ for (let attempt = 0; attempt <= attempts; attempt++) {
76
+ try {
77
+ return await fn()
78
+ } catch (error: unknown) {
79
+ lastError = error
80
+
81
+ if (attempt < attempts) {
82
+ await sleep(getRetryDelay(delays, attempt))
83
+ }
84
+ }
85
+ }
86
+
87
+ throw lastError instanceof Error ? lastError : new Error(String(lastError))
88
+ }
89
+
90
+ /**
91
+ * Generate a unique run identifier.
92
+ *
93
+ * Uses `crypto.randomUUID()` when available, with a Math.random() fallback
94
+ * for older browsers where crypto is unavailable.
95
+ *
96
+ * @returns Generated UUID string
97
+ */
98
+ export function generateRunId(): string {
99
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
100
+ return crypto.randomUUID()
101
+ }
102
+
103
+ // Fallback for environments without crypto.randomUUID
104
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
105
+ const r = (Math.random() * 16) | 0
106
+ const v = c === 'x' ? r : (r & 0x3) | 0x8
107
+ return v.toString(16)
108
+ })
109
+ }