@aurabase/aurabase-js 0.3.0 → 0.4.0

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 (343) hide show
  1. package/README.md +26 -795
  2. package/{packages/aurabase/dist/AuraClient-DAtDbfPB.d.ts → dist/AuraClient-BcFE8u1v.d.cts} +1 -1
  3. package/{packages/aurabase/dist/AuraClient-DAtDbfPB.d.cts → dist/AuraClient-BcFE8u1v.d.ts} +1 -1
  4. package/{packages/aurabase/dist → dist}/index.d.cts +1 -1
  5. package/{packages/aurabase/dist → dist}/index.d.ts +1 -1
  6. package/{packages/aurabase/dist → dist}/ssr.d.cts +1 -1
  7. package/{packages/aurabase/dist → dist}/ssr.d.ts +1 -1
  8. package/package.json +72 -82
  9. package/.changeset/config.json +0 -11
  10. package/CHANGELOG.md +0 -30
  11. package/packages/ai/LICENSE +0 -21
  12. package/packages/ai/README.md +0 -106
  13. package/packages/ai/dist/index.cjs +0 -229
  14. package/packages/ai/dist/index.d.cts +0 -139
  15. package/packages/ai/dist/index.d.ts +0 -139
  16. package/packages/ai/dist/index.js +0 -226
  17. package/packages/ai/node_modules/.bin/esbuild +0 -19
  18. package/packages/ai/node_modules/.bin/tsc +0 -22
  19. package/packages/ai/node_modules/.bin/tsserver +0 -22
  20. package/packages/ai/node_modules/.bin/tsup +0 -22
  21. package/packages/ai/node_modules/.bin/tsup-node +0 -22
  22. package/packages/ai/node_modules/.bin/vite +0 -22
  23. package/packages/ai/node_modules/.bin/vitest +0 -22
  24. package/packages/ai/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  25. package/packages/ai/package.json +0 -61
  26. package/packages/ai/src/AiService.ts +0 -226
  27. package/packages/ai/src/index.ts +0 -18
  28. package/packages/ai/src/sse.ts +0 -141
  29. package/packages/ai/src/types.ts +0 -30
  30. package/packages/ai/tests/chat-stream.test.ts +0 -151
  31. package/packages/ai/tests/rag-namespaces.test.ts +0 -54
  32. package/packages/ai/tests/sse.test.ts +0 -69
  33. package/packages/ai/tsconfig.json +0 -5
  34. package/packages/ai/tsup.config.ts +0 -11
  35. package/packages/aurabase/LICENSE +0 -21
  36. package/packages/aurabase/README.md +0 -64
  37. package/packages/aurabase/node_modules/.bin/esbuild +0 -19
  38. package/packages/aurabase/node_modules/.bin/tsc +0 -22
  39. package/packages/aurabase/node_modules/.bin/tsserver +0 -22
  40. package/packages/aurabase/node_modules/.bin/tsup +0 -22
  41. package/packages/aurabase/node_modules/.bin/tsup-node +0 -22
  42. package/packages/aurabase/node_modules/.bin/vite +0 -22
  43. package/packages/aurabase/node_modules/.bin/vitest +0 -22
  44. package/packages/aurabase/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  45. package/packages/aurabase/package.json +0 -80
  46. package/packages/aurabase/src/AuraClient.ts +0 -237
  47. package/packages/aurabase/src/index.ts +0 -152
  48. package/packages/aurabase/src/ssr.ts +0 -156
  49. package/packages/aurabase/tests/ai-streaming-rag-e2e.test.ts +0 -82
  50. package/packages/aurabase/tests/ai.test.ts +0 -111
  51. package/packages/aurabase/tests/auth-admin.test.ts +0 -515
  52. package/packages/aurabase/tests/auth-e2e.test.ts +0 -135
  53. package/packages/aurabase/tests/auth-hardening.test.ts +0 -206
  54. package/packages/aurabase/tests/auth.test.ts +0 -947
  55. package/packages/aurabase/tests/database.test.ts +0 -230
  56. package/packages/aurabase/tests/db-types-e2e.test.ts +0 -217
  57. package/packages/aurabase/tests/dispose.test.ts +0 -23
  58. package/packages/aurabase/tests/foreign-token.e2e.test.ts +0 -129
  59. package/packages/aurabase/tests/functions.test.ts +0 -107
  60. package/packages/aurabase/tests/integration.test.ts +0 -413
  61. package/packages/aurabase/tests/live-production-validation.js +0 -241
  62. package/packages/aurabase/tests/live-sdk-production-validation.js +0 -293
  63. package/packages/aurabase/tests/mocks.ts +0 -139
  64. package/packages/aurabase/tests/notifications.test.ts +0 -314
  65. package/packages/aurabase/tests/observability-backup.test.ts +0 -186
  66. package/packages/aurabase/tests/observability-hook.test.ts +0 -130
  67. package/packages/aurabase/tests/production-robustness.test.ts +0 -268
  68. package/packages/aurabase/tests/public-api-surface.test.ts +0 -53
  69. package/packages/aurabase/tests/query-builder.test.ts +0 -329
  70. package/packages/aurabase/tests/readme-smoke-e2e.test.ts +0 -231
  71. package/packages/aurabase/tests/realtime-filter-e2e.test.ts +0 -344
  72. package/packages/aurabase/tests/refresh-locks.test.ts +0 -210
  73. package/packages/aurabase/tests/retry-option.test.ts +0 -17
  74. package/packages/aurabase/tests/rpc-e2e.test.ts +0 -254
  75. package/packages/aurabase/tests/rs256-local-e2e.test.ts +0 -114
  76. package/packages/aurabase/tests/rt-alias-public-e2e.test.ts +0 -235
  77. package/packages/aurabase/tests/rt-http-broadcast-e2e.test.ts +0 -124
  78. package/packages/aurabase/tests/service-role-guard.test.ts +0 -99
  79. package/packages/aurabase/tests/ssr.test.ts +0 -91
  80. package/packages/aurabase/tests/storage-backend-e2e.test.ts +0 -481
  81. package/packages/aurabase/tests/storage-sdk-parity-e2e.test.ts +0 -104
  82. package/packages/aurabase/tests/storage-transform-e2e.test.ts +0 -684
  83. package/packages/aurabase/tests/storage.test.ts +0 -55
  84. package/packages/aurabase/tests/types/client-generic.test-d.ts +0 -79
  85. package/packages/aurabase/tests/types/docs-snippets.test-d.ts +0 -571
  86. package/packages/aurabase/tests/types/error-code.test-d.ts +0 -53
  87. package/packages/aurabase/tsconfig.json +0 -5
  88. package/packages/aurabase/tsconfig.test-d.json +0 -5
  89. package/packages/aurabase/tsup.config.ts +0 -11
  90. package/packages/aurabase/vitest.config.ts +0 -16
  91. package/packages/aurabase/vitest.e2e.config.ts +0 -9
  92. package/packages/auth/LICENSE +0 -21
  93. package/packages/auth/README.md +0 -215
  94. package/packages/auth/dist/index.cjs +0 -1175
  95. package/packages/auth/dist/index.d.cts +0 -397
  96. package/packages/auth/dist/index.d.ts +0 -397
  97. package/packages/auth/dist/index.js +0 -1170
  98. package/packages/auth/node_modules/.bin/esbuild +0 -19
  99. package/packages/auth/node_modules/.bin/tsc +0 -22
  100. package/packages/auth/node_modules/.bin/tsserver +0 -22
  101. package/packages/auth/node_modules/.bin/tsup +0 -22
  102. package/packages/auth/node_modules/.bin/tsup-node +0 -22
  103. package/packages/auth/node_modules/.bin/vite +0 -22
  104. package/packages/auth/node_modules/.bin/vitest +0 -22
  105. package/packages/auth/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  106. package/packages/auth/package.json +0 -61
  107. package/packages/auth/src/AuthAdminService.ts +0 -149
  108. package/packages/auth/src/AuthService.ts +0 -1166
  109. package/packages/auth/src/SessionManager.ts +0 -92
  110. package/packages/auth/src/index.ts +0 -17
  111. package/packages/auth/src/jwks.ts +0 -337
  112. package/packages/auth/src/pkce.ts +0 -27
  113. package/packages/auth/src/types.ts +0 -89
  114. package/packages/auth/tests/auth-lifecycle.test.ts +0 -149
  115. package/packages/auth/tests/detect-session.test.ts +0 -361
  116. package/packages/auth/tests/getclaims.test.ts +0 -780
  117. package/packages/auth/tests/is-mfa-challenge.test.ts +0 -27
  118. package/packages/auth/tests/password-recovery.test.ts +0 -124
  119. package/packages/auth/tests/pkce.test.ts +0 -192
  120. package/packages/auth/tests/session-manager.test.ts +0 -103
  121. package/packages/auth/tests/set-session.test.ts +0 -139
  122. package/packages/auth/tests/verify-otp.test.ts +0 -153
  123. package/packages/auth/tsconfig.json +0 -5
  124. package/packages/auth/tsup.config.ts +0 -11
  125. package/packages/core/LICENSE +0 -21
  126. package/packages/core/README.md +0 -132
  127. package/packages/core/dist/index.cjs +0 -695
  128. package/packages/core/dist/index.d.cts +0 -1137
  129. package/packages/core/dist/index.d.ts +0 -1137
  130. package/packages/core/dist/index.js +0 -673
  131. package/packages/core/node_modules/.bin/esbuild +0 -22
  132. package/packages/core/node_modules/.bin/tsc +0 -22
  133. package/packages/core/node_modules/.bin/tsserver +0 -22
  134. package/packages/core/node_modules/.bin/tsup +0 -22
  135. package/packages/core/node_modules/.bin/tsup-node +0 -22
  136. package/packages/core/node_modules/.bin/vite +0 -22
  137. package/packages/core/node_modules/.bin/vitest +0 -22
  138. package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  139. package/packages/core/package.json +0 -57
  140. package/packages/core/src/AuraError.ts +0 -89
  141. package/packages/core/src/BaseService.ts +0 -44
  142. package/packages/core/src/client.ts +0 -34
  143. package/packages/core/src/cookie-utils.ts +0 -42
  144. package/packages/core/src/fetch.ts +0 -435
  145. package/packages/core/src/index.ts +0 -22
  146. package/packages/core/src/retry.ts +0 -63
  147. package/packages/core/src/serialize.ts +0 -6
  148. package/packages/core/src/stores/BaseAuthStore.ts +0 -164
  149. package/packages/core/src/stores/LocalAuthStore.ts +0 -199
  150. package/packages/core/src/types.ts +0 -1042
  151. package/packages/core/tests/auth-store.test.ts +0 -300
  152. package/packages/core/tests/error-code.test.ts +0 -61
  153. package/packages/core/tests/error.test.ts +0 -24
  154. package/packages/core/tests/fetch.test.ts +0 -286
  155. package/packages/core/tests/mocks.ts +0 -139
  156. package/packages/core/tests/observability.test.ts +0 -86
  157. package/packages/core/tests/refresh-hardening.test.ts +0 -164
  158. package/packages/core/tests/refresh-observability.test.ts +0 -111
  159. package/packages/core/tests/retry.test.ts +0 -106
  160. package/packages/core/tests/serialize.test.ts +0 -12
  161. package/packages/core/tests/timeout.test.ts +0 -79
  162. package/packages/core/tsconfig.json +0 -5
  163. package/packages/core/tsup.config.ts +0 -11
  164. package/packages/db/LICENSE +0 -21
  165. package/packages/db/README.md +0 -152
  166. package/packages/db/VENDORED.md +0 -93
  167. package/packages/db/dist/index.cjs +0 -544
  168. package/packages/db/dist/index.d.cts +0 -1111
  169. package/packages/db/dist/index.d.ts +0 -1111
  170. package/packages/db/dist/index.js +0 -541
  171. package/packages/db/node_modules/.bin/esbuild +0 -19
  172. package/packages/db/node_modules/.bin/tsc +0 -22
  173. package/packages/db/node_modules/.bin/tsserver +0 -22
  174. package/packages/db/node_modules/.bin/tsup +0 -22
  175. package/packages/db/node_modules/.bin/tsup-node +0 -22
  176. package/packages/db/node_modules/.bin/vite +0 -22
  177. package/packages/db/node_modules/.bin/vitest +0 -22
  178. package/packages/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  179. package/packages/db/package.json +0 -62
  180. package/packages/db/src/DatabaseService.ts +0 -170
  181. package/packages/db/src/QueryBuilder.ts +0 -830
  182. package/packages/db/src/index.ts +0 -2
  183. package/packages/db/src/pgrst-types/common/common.ts +0 -86
  184. package/packages/db/src/pgrst-types/common/rpc.ts +0 -151
  185. package/packages/db/src/pgrst-types/feature-flags.ts +0 -20
  186. package/packages/db/src/pgrst-types/types.ts +0 -134
  187. package/packages/db/src/select-query-parser/parser.ts +0 -479
  188. package/packages/db/src/select-query-parser/result.ts +0 -553
  189. package/packages/db/src/select-query-parser/types.ts +0 -132
  190. package/packages/db/src/select-query-parser/utils.ts +0 -707
  191. package/packages/db/tests/filters.test.ts +0 -81
  192. package/packages/db/tests/mutations-filtered.e2e.test.ts +0 -132
  193. package/packages/db/tests/mutations-filtered.test.ts +0 -188
  194. package/packages/db/tests/order.test.ts +0 -45
  195. package/packages/db/tests/rpc.test.ts +0 -41
  196. package/packages/db/tests/scale/build-large-schema.ts +0 -94
  197. package/packages/db/tests/scale/scale-compile.test.ts +0 -167
  198. package/packages/db/tests/schema.test.ts +0 -77
  199. package/packages/db/tests/single-accept.e2e.test.ts +0 -123
  200. package/packages/db/tests/single.test.ts +0 -211
  201. package/packages/db/tests/types/filters.test-d.ts +0 -52
  202. package/packages/db/tests/types/fixtures.ts +0 -40
  203. package/packages/db/tests/types/from-views.test-d.ts +0 -18
  204. package/packages/db/tests/types/mutations.test-d.ts +0 -23
  205. package/packages/db/tests/types/rpc.test-d.ts +0 -60
  206. package/packages/db/tests/types/schema-shape.test-d.ts +0 -8
  207. package/packages/db/tests/types/select-inference.test-d.ts +0 -38
  208. package/packages/db/tests/vendored/fixtures.ts +0 -83
  209. package/packages/db/tests/vendored/get-result.test-d.ts +0 -75
  210. package/packages/db/tsconfig.json +0 -5
  211. package/packages/db/tsconfig.test-d.json +0 -5
  212. package/packages/db/tsup.config.ts +0 -11
  213. package/packages/db/vitest.config.ts +0 -8
  214. package/packages/functions/LICENSE +0 -21
  215. package/packages/functions/README.md +0 -96
  216. package/packages/functions/dist/index.cjs +0 -173
  217. package/packages/functions/dist/index.d.cts +0 -81
  218. package/packages/functions/dist/index.d.ts +0 -81
  219. package/packages/functions/dist/index.js +0 -168
  220. package/packages/functions/node_modules/.bin/esbuild +0 -19
  221. package/packages/functions/node_modules/.bin/tsc +0 -22
  222. package/packages/functions/node_modules/.bin/tsserver +0 -22
  223. package/packages/functions/node_modules/.bin/tsup +0 -22
  224. package/packages/functions/node_modules/.bin/tsup-node +0 -22
  225. package/packages/functions/node_modules/.bin/vite +0 -22
  226. package/packages/functions/node_modules/.bin/vitest +0 -22
  227. package/packages/functions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  228. package/packages/functions/package.json +0 -60
  229. package/packages/functions/src/FunctionsService.ts +0 -188
  230. package/packages/functions/src/errors.ts +0 -70
  231. package/packages/functions/src/index.ts +0 -6
  232. package/packages/functions/tests/invoke.test.ts +0 -97
  233. package/packages/functions/tests/response-type-errors.test.ts +0 -135
  234. package/packages/functions/tsconfig.json +0 -5
  235. package/packages/functions/tsup.config.ts +0 -11
  236. package/packages/gen/LICENSE +0 -21
  237. package/packages/gen/README.md +0 -84
  238. package/packages/gen/dist/cli.cjs +0 -334
  239. package/packages/gen/dist/cli.d.cts +0 -1
  240. package/packages/gen/dist/cli.d.ts +0 -1
  241. package/packages/gen/dist/cli.js +0 -332
  242. package/packages/gen/dist/index.cjs +0 -257
  243. package/packages/gen/dist/index.d.cts +0 -12
  244. package/packages/gen/dist/index.d.ts +0 -12
  245. package/packages/gen/dist/index.js +0 -255
  246. package/packages/gen/node_modules/.bin/esbuild +0 -19
  247. package/packages/gen/node_modules/.bin/tsc +0 -22
  248. package/packages/gen/node_modules/.bin/tsserver +0 -22
  249. package/packages/gen/node_modules/.bin/tsup +0 -22
  250. package/packages/gen/node_modules/.bin/tsup-node +0 -22
  251. package/packages/gen/node_modules/.bin/vite +0 -22
  252. package/packages/gen/node_modules/.bin/vitest +0 -22
  253. package/packages/gen/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  254. package/packages/gen/package.json +0 -66
  255. package/packages/gen/src/cli.ts +0 -67
  256. package/packages/gen/src/generate.ts +0 -0
  257. package/packages/gen/src/index.ts +0 -2
  258. package/packages/gen/tests/compile.test.ts +0 -213
  259. package/packages/gen/tests/e2e.test.ts +0 -130
  260. package/packages/gen/tests/generate.test.ts +0 -396
  261. package/packages/gen/tsconfig.json +0 -5
  262. package/packages/gen/tsup.config.ts +0 -11
  263. package/packages/notifications/LICENSE +0 -21
  264. package/packages/notifications/README.md +0 -134
  265. package/packages/notifications/dist/index.cjs +0 -128
  266. package/packages/notifications/dist/index.d.cts +0 -81
  267. package/packages/notifications/dist/index.d.ts +0 -81
  268. package/packages/notifications/dist/index.js +0 -125
  269. package/packages/notifications/node_modules/.bin/esbuild +0 -19
  270. package/packages/notifications/node_modules/.bin/tsc +0 -22
  271. package/packages/notifications/node_modules/.bin/tsserver +0 -22
  272. package/packages/notifications/node_modules/.bin/tsup +0 -22
  273. package/packages/notifications/node_modules/.bin/tsup-node +0 -22
  274. package/packages/notifications/node_modules/.bin/vite +0 -22
  275. package/packages/notifications/node_modules/.bin/vitest +0 -22
  276. package/packages/notifications/package.json +0 -60
  277. package/packages/notifications/src/NotificationsService.ts +0 -230
  278. package/packages/notifications/src/index.ts +0 -19
  279. package/packages/notifications/tsconfig.json +0 -5
  280. package/packages/notifications/tsup.config.ts +0 -11
  281. package/packages/realtime/LICENSE +0 -21
  282. package/packages/realtime/README.md +0 -183
  283. package/packages/realtime/dist/index.cjs +0 -977
  284. package/packages/realtime/dist/index.d.cts +0 -259
  285. package/packages/realtime/dist/index.d.ts +0 -259
  286. package/packages/realtime/dist/index.js +0 -973
  287. package/packages/realtime/node_modules/.bin/esbuild +0 -19
  288. package/packages/realtime/node_modules/.bin/tsc +0 -22
  289. package/packages/realtime/node_modules/.bin/tsserver +0 -22
  290. package/packages/realtime/node_modules/.bin/tsup +0 -22
  291. package/packages/realtime/node_modules/.bin/tsup-node +0 -22
  292. package/packages/realtime/node_modules/.bin/vite +0 -22
  293. package/packages/realtime/node_modules/.bin/vitest +0 -22
  294. package/packages/realtime/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  295. package/packages/realtime/package.json +0 -60
  296. package/packages/realtime/src/AuraChannel.ts +0 -485
  297. package/packages/realtime/src/HeartbeatManager.ts +0 -73
  298. package/packages/realtime/src/RealtimeService.ts +0 -505
  299. package/packages/realtime/src/columnFilter.ts +0 -301
  300. package/packages/realtime/src/index.ts +0 -4
  301. package/packages/realtime/tests/ack-routing.test.ts +0 -81
  302. package/packages/realtime/tests/column-filter.test.ts +0 -144
  303. package/packages/realtime/tests/dispatch.test.ts +0 -191
  304. package/packages/realtime/tests/heartbeat.test.ts +0 -67
  305. package/packages/realtime/tests/http-broadcast.test.ts +0 -68
  306. package/packages/realtime/tests/realtime-token.test.ts +0 -43
  307. package/packages/realtime/tests/reconnect-race.test.ts +0 -149
  308. package/packages/realtime/tests/ref-id.test.ts +0 -34
  309. package/packages/realtime/tests/set-auth.test.ts +0 -70
  310. package/packages/realtime/tsconfig.json +0 -5
  311. package/packages/realtime/tsup.config.ts +0 -11
  312. package/packages/storage/LICENSE +0 -21
  313. package/packages/storage/README.md +0 -141
  314. package/packages/storage/dist/index.cjs +0 -333
  315. package/packages/storage/dist/index.d.cts +0 -185
  316. package/packages/storage/dist/index.d.ts +0 -185
  317. package/packages/storage/dist/index.js +0 -329
  318. package/packages/storage/node_modules/.bin/esbuild +0 -19
  319. package/packages/storage/node_modules/.bin/tsc +0 -22
  320. package/packages/storage/node_modules/.bin/tsserver +0 -22
  321. package/packages/storage/node_modules/.bin/tsup +0 -22
  322. package/packages/storage/node_modules/.bin/tsup-node +0 -22
  323. package/packages/storage/node_modules/.bin/vite +0 -22
  324. package/packages/storage/node_modules/.bin/vitest +0 -22
  325. package/packages/storage/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  326. package/packages/storage/package.json +0 -60
  327. package/packages/storage/src/DownloadBuilder.ts +0 -60
  328. package/packages/storage/src/StorageService.ts +0 -323
  329. package/packages/storage/src/index.ts +0 -2
  330. package/packages/storage/src/path.ts +0 -35
  331. package/packages/storage/src/transform.ts +0 -15
  332. package/packages/storage/tests/download.test.ts +0 -64
  333. package/packages/storage/tests/parity.test.ts +0 -154
  334. package/packages/storage/tests/path.test.ts +0 -87
  335. package/packages/storage/tests/transform.test.ts +0 -79
  336. package/packages/storage/tsconfig.json +0 -5
  337. package/packages/storage/tsup.config.ts +0 -11
  338. package/pnpm-workspace.yaml +0 -24
  339. package/tsconfig.base.json +0 -15
  340. /package/{packages/aurabase/dist → dist}/index.cjs +0 -0
  341. /package/{packages/aurabase/dist → dist}/index.js +0 -0
  342. /package/{packages/aurabase/dist → dist}/ssr.cjs +0 -0
  343. /package/{packages/aurabase/dist → dist}/ssr.js +0 -0
@@ -1,435 +0,0 @@
1
- import { AuraError } from './AuraError'
2
- import type { AuraResponse, TokenPair, Meta } from './types'
3
- import { emitAuraEvent } from './types'
4
- import type { AuraClientLike } from './client'
5
- import { isTokenExpired } from './stores/BaseAuthStore'
6
- import {
7
- isRetryableMethod,
8
- isRetryableStatus,
9
- computeBackoff,
10
- parseRetryAfter,
11
- abortableSleep,
12
- isAbortError,
13
- DEFAULT_MAX_RETRIES,
14
- } from './retry'
15
- import { serializeBody } from './serialize'
16
-
17
- const REFRESH_MARGIN_SEC = 30
18
-
19
- /** Plafond du délai `Retry-After` (borne un serveur renvoyant une valeur énorme). */
20
- const RETRY_AFTER_CAP_MS = 60_000
21
-
22
- /** Timeout par défaut d'une requête HTTP (borne un serveur qui « hang »). */
23
- export const DEFAULT_REQUEST_TIMEOUT_MS = 30_000
24
-
25
- /**
26
- * Déballe l'enveloppe backend `{data, meta}` de façon SÛRE (minor 6b).
27
- * `'data' in body` JETTE un TypeError si `body` n'est pas un objet (corps JSON
28
- * primitif ou `null` : `42`, `"x"`, `null`). On garde alors le corps tel quel comme
29
- * `data` (pas de throw, pas de perte de valeur). `null` explicite dans une enveloppe
30
- * (`{data:null,meta:null}`) reste distingué de « absent » (I5).
31
- */
32
- function unwrapEnvelope(body: unknown): unknown {
33
- if (body !== null && typeof body === 'object' && 'data' in body) {
34
- return (body as { data: unknown }).data
35
- }
36
- return body
37
- }
38
-
39
- /**
40
- * Single-flight de rafraîchissement (E17).
41
- * Une seule promesse de refresh en vol par client : `maybeRefresh` (proactif),
42
- * `AuthService.refreshSession` (manuel) et le retry 401 (E19) passent TOUS par
43
- * `refreshOnce`, ce qui évite deux refresh concurrents avec le même refresh token
44
- * (qui, sur un backend à rotation stricte, provoquaient une déconnexion aléatoire).
45
- */
46
- const inflightRefreshes = new WeakMap<AuraClientLike, Promise<AuraResponse<TokenPair>>>()
47
-
48
- /** Effectue le refresh (sans dédup) — appelé uniquement via `refreshOnce`. */
49
- async function doRefresh(client: AuraClientLike): Promise<AuraResponse<TokenPair>> {
50
- const store = client.authStore
51
- const rt = store.refreshToken
52
- if (!rt) {
53
- return {
54
- data: null,
55
- error: new AuraError(0, { error: { code: 'no_session', message: 'No refresh token available' } }),
56
- }
57
- }
58
- // Commit-guard (B) : snapshot (epoch + refresh token) avant le fetch. Si un clear()/signOut
59
- // OU un save()/signIn concurrent (ou une rotation par un autre onglet) s'intercale pendant
60
- // l'await, la session courante a changé → on ne doit NI la ressusciter (succès) NI l'effacer
61
- // (échec sur l'ancien token). On compare epoch ET l'identité du refresh token.
62
- const epoch = store.removalEpoch
63
- let res: Response
64
- try {
65
- res = await client.fetchFn(`${client.baseUrl}/v1/auth/${client.projectId}/refresh`, {
66
- method: 'POST',
67
- headers: { 'Content-Type': 'application/json' },
68
- body: serializeBody({ refresh_token: rt }),
69
- })
70
- } catch (err) {
71
- // Erreur réseau : on garde les tokens existants (peut-être encore valides).
72
- return {
73
- data: null,
74
- error: new AuraError(0, {
75
- error: { code: 'network_error', message: err instanceof Error ? err.message : 'Network request failed' },
76
- }),
77
- }
78
- }
79
- if (!res.ok) {
80
- let body: { error?: { code?: string; message?: string }; message?: string } | null = null
81
- try {
82
- body = await res.json()
83
- } catch {
84
- // corps non-JSON
85
- }
86
- // Clear sélectif (B, proactive-preserve) : ne détruire la session que sur rejet DÉFINITIF
87
- // du refresh token (invalide/révoqué/expiré). Sur transitoire (5xx/429/408/réseau), on
88
- // préserve la session. Commit-guard : ne PAS effacer une session déjà changée pendant
89
- // l'await (signIn/rotation concurrente) — le 401 porte alors sur un ANCIEN token.
90
- if (res.status === 400 || res.status === 401 || res.status === 403) {
91
- if (store.removalEpoch === epoch && store.refreshToken === rt) {
92
- store.clear()
93
- }
94
- }
95
- return { data: null, error: new AuraError(res.status, body) }
96
- }
97
- // Chemin succès : le corps DOIT être du JSON. Un 200 non-JSON (proxy mal
98
- // configuré, page HTML d'erreur renvoyée en 200…) ne doit pas faire rejeter
99
- // la promesse `refreshOnce` (les appelants attendent un AuraResponse, jamais
100
- // un throw) — on renvoie un AuraError structuré, cohérent avec le chemin erreur.
101
- let body: { data?: TokenPair } & Partial<TokenPair>
102
- try {
103
- body = await res.json()
104
- } catch {
105
- return {
106
- data: null,
107
- error: new AuraError(0, { error: { code: 'invalid_response', message: 'Invalid refresh response (non-JSON body)' } }),
108
- }
109
- }
110
- // I5 : `'data' in body` (pas `body.data ?? body`) — un `data` EXPLICITEMENT `null`
111
- // dans l'enveloppe backend (`ApiResponse::empty()` → `{"data":null,"meta":null}`)
112
- // ne doit jamais retomber sur l'enveloppe entière (nullish coalescing traite `null`
113
- // comme absent). Ici la réponse de refresh a toujours un `data` enveloppé ; ce
114
- // garde-fou reste cohérent avec le même correctif dans `fetchWithAuth`.
115
- const data = unwrapEnvelope(body) as TokenPair
116
- if (data?.access_token && data?.refresh_token && data?.user) {
117
- // Commit-guard : un clear()/signOut OU un save()/signIn concurrent s'est-il intercalé ?
118
- // On compare l'epoch de suppression ET l'identité du refresh token utilisé.
119
- if (store.removalEpoch !== epoch || store.refreshToken !== rt) {
120
- emitAuraEvent(client.onEvent, client.debug, {
121
- kind: 'refresh-discarded',
122
- reason: 'Session changed during refresh (concurrent signIn/signOut/rotation)',
123
- })
124
- return {
125
- data: null,
126
- error: new AuraError(409, { error: { code: 'refresh_discarded', message: 'Session changed during refresh' } }),
127
- }
128
- }
129
- store.save(data.access_token, data.refresh_token, data.user)
130
- emitAuraEvent(client.onEvent, client.debug, { kind: 'refresh-success' })
131
- return { data, error: null }
132
- }
133
- return {
134
- data: null,
135
- error: new AuraError(0, { error: { code: 'invalid_response', message: 'Invalid refresh response' } }),
136
- }
137
- }
138
-
139
- /**
140
- * Coordination CROSS-ONGLETS du refresh via l'API Web Locks (I2). Le single-flight
141
- * `inflightRefreshes` ne dédup que DANS un onglet ; deux onglets pouvaient encore
142
- * lancer deux refresh concurrents avec le même refresh token → churn de rotation
143
- * (le commit-guard évitait la déconnexion mais pas la course). On enveloppe donc le
144
- * refresh dans `navigator.locks.request(lockName)` EN NAVIGATEUR uniquement ; hors
145
- * navigateur (SSR/Node) fallback au single-flight in-process seul (aucun lock).
146
- *
147
- * Anti-interblocage : l'ATTENTE d'acquisition est bornée par un `AbortSignal`
148
- * (timeout = requestTimeoutMs). Si un autre onglet tient le lock trop longtemps
149
- * (refresh qui « hang »), on n'attend pas indéfiniment — on abandonne le lock et on
150
- * exécute le refresh SANS coordination (fallback). Une fois le lock acquis, le timer
151
- * est désarmé (le signal n'a plus d'effet côté spec) : pas de double exécution.
152
- */
153
- function withRefreshLock(
154
- client: AuraClientLike,
155
- fn: () => Promise<AuraResponse<TokenPair>>,
156
- ): Promise<AuraResponse<TokenPair>> {
157
- // Web Locks = coordination cross-ONGLETS : purement navigateur. On gate sur
158
- // `window` : sur un serveur Node (SSR), `navigator.locks` existe (Node 21+) mais
159
- // est PROCESS-GLOBAL — deux requêtes d'utilisateurs différents partageant le même
160
- // `projectId` verraient leurs refresh sérialisés par `aura-refresh-{projectId}`.
161
- // En dehors d'un navigateur, on retombe sur le single-flight in-process seul.
162
- const browser = typeof window !== 'undefined'
163
- const nav = (globalThis as { navigator?: { locks?: { request?: unknown } } }).navigator
164
- const locks = nav?.locks
165
- if (!browser || !locks || typeof locks.request !== 'function') {
166
- return fn()
167
- }
168
- const request = locks.request as (
169
- name: string,
170
- options: { signal?: AbortSignal },
171
- cb: () => Promise<AuraResponse<TokenPair>>,
172
- ) => Promise<AuraResponse<TokenPair>>
173
-
174
- const lockName = `aura-refresh-${client.projectId}`
175
- const timeoutMs =
176
- (client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS) || DEFAULT_REQUEST_TIMEOUT_MS
177
-
178
- let timer: ReturnType<typeof setTimeout> | undefined
179
- const options: { signal?: AbortSignal } = {}
180
- if (typeof AbortController !== 'undefined') {
181
- const ctrl = new AbortController()
182
- timer = setTimeout(() => ctrl.abort(), timeoutMs)
183
- ;(timer as unknown as { unref?: () => void }).unref?.()
184
- options.signal = ctrl.signal
185
- }
186
- const clearTimer = (): void => {
187
- if (timer) {
188
- clearTimeout(timer)
189
- timer = undefined
190
- }
191
- }
192
-
193
- try {
194
- return Promise.resolve(
195
- request(lockName, options, () => {
196
- // Lock acquis : désarmer le timer d'attente avant d'exécuter le refresh.
197
- clearTimer()
198
- return fn()
199
- }),
200
- ).catch(() => {
201
- // Acquisition abandonnée (signal abort → AbortError) ou API en erreur
202
- // (SecurityError…) : fallback SANS lock — jamais de blocage permanent.
203
- clearTimer()
204
- return fn()
205
- })
206
- } catch {
207
- clearTimer()
208
- return fn()
209
- }
210
- }
211
-
212
- /** Point d'entrée unique du refresh, dédupliqué par client (single-flight). */
213
- export function refreshOnce(client: AuraClientLike): Promise<AuraResponse<TokenPair>> {
214
- const existing = inflightRefreshes.get(client)
215
- if (existing) return existing
216
- // Le lock cross-onglets enveloppe le refresh ; le single-flight in-process (ci-dessus)
217
- // reste la 1re barrière (une seule acquisition de lock par onglet à la fois).
218
- const p = withRefreshLock(client, () => doRefresh(client)).finally(() =>
219
- inflightRefreshes.delete(client),
220
- )
221
- inflightRefreshes.set(client, p)
222
- return p
223
- }
224
-
225
- /** Refresh proactif : seulement si le token est (presque) expiré. */
226
- async function maybeRefresh(client: AuraClientLike): Promise<void> {
227
- const store = client.authStore
228
- if (!store.token || !store.refreshToken) return
229
- if (!isTokenExpired(store.token, REFRESH_MARGIN_SEC)) return
230
- await refreshOnce(client)
231
- }
232
-
233
- export interface FetchInit {
234
- method?: string
235
- headers?: Record<string, string>
236
- body?: unknown
237
- signal?: AbortSignal
238
- skipAuthRefresh?: boolean
239
- /**
240
- * Override explicite du `Authorization: Bearer` (LOT D). S'il est fourni, il
241
- * GAGNE sur `store.token` — permet de valider un token ARBITRAIRE (foreign)
242
- * plutôt que celui de la session courante (ex. `getUser(jwt)`). Sans lui, le
243
- * comportement historique s'applique (`Authorization = Bearer store.token`).
244
- */
245
- authToken?: string
246
- /** Désactive le retry transitoire pour cette requête (override). */
247
- skipRetry?: boolean
248
- /** Interne (E19) : marque une requête déjà rejouée après refresh pour borner la récursion. */
249
- _retried?: boolean
250
- }
251
-
252
- /**
253
- * Core fetch wrapper that handles:
254
- * - Lazy token refresh before expiry
255
- * - Authorization / API key injection
256
- * - Content-Type defaulting
257
- * - Structured AuraResponse parsing
258
- */
259
- export async function fetchWithAuth<T>(
260
- url: string,
261
- init: FetchInit,
262
- client: AuraClientLike,
263
- ): Promise<AuraResponse<T>> {
264
- // Attempt lazy token refresh
265
- if (!init.skipAuthRefresh) {
266
- await maybeRefresh(client)
267
- }
268
-
269
- const method = (init.method ?? 'GET').toUpperCase()
270
- const retryEnabled =
271
- !init.skipRetry &&
272
- (client.retryConfig?.enabled ?? true) &&
273
- isRetryableMethod(method)
274
- const maxRetries = client.retryConfig?.maxRetries ?? DEFAULT_MAX_RETRIES
275
-
276
- const store = client.authStore
277
- const isFormData = typeof FormData !== 'undefined' && init.body instanceof FormData
278
-
279
- // Timeout par défaut : borne une requête vers un serveur qui « hang ». Appliqué
280
- // seulement si l'appelant n'a pas fourni son propre `signal` (invoke, downloads…
281
- // gèrent leur annulation) et si le corps n'est pas un FormData (uploads longs).
282
- const timeoutMs = client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS
283
- const applyTimeout = timeoutMs > 0 && init.signal === undefined && !isFormData
284
-
285
- let attempt = 0
286
- for (;;) {
287
- const headers: Record<string, string> = { ...init.headers }
288
-
289
- // Inject auth headers (Supabase-compatible: apikey ALWAYS sent, JWT when authenticated)
290
- if (client.apiKey) {
291
- headers['apikey'] = client.apiKey
292
- }
293
- // LOT D : override `authToken` prioritaire sur `store.token`. Sans override,
294
- // comportement historique (JWT de session injecté quand authentifié).
295
- if (init.authToken) {
296
- headers['Authorization'] = `Bearer ${init.authToken}`
297
- } else if (store.token) {
298
- headers['Authorization'] = `Bearer ${store.token}`
299
- }
300
- // Set Content-Type unless body is FormData
301
- if (!isFormData && init.body !== undefined) {
302
- headers['Content-Type'] = headers['Content-Type'] ?? 'application/json'
303
- }
304
- if (attempt > 0) {
305
- headers['X-Retry-Count'] = String(attempt)
306
- }
307
-
308
- // Timer de timeout PAR TENTATIVE (chaque rejeu repart avec un timeout neuf).
309
- // On utilise un AbortController maison (plutôt que AbortSignal.timeout) pour
310
- // pouvoir clearTimeout dès la résolution — évite tout timer fantôme en test.
311
- let timedOut = false
312
- let timeoutTimer: ReturnType<typeof setTimeout> | undefined
313
- let attemptSignal = init.signal
314
- if (applyTimeout) {
315
- const ctrl = new AbortController()
316
- timeoutTimer = setTimeout(() => { timedOut = true; ctrl.abort() }, timeoutMs)
317
- // `unref` (Node) pour ne pas bloquer la sortie de process ; no-op en navigateur.
318
- ;(timeoutTimer as unknown as { unref?: () => void }).unref?.()
319
- attemptSignal = ctrl.signal
320
- }
321
- const clearTimeoutTimer = () => { if (timeoutTimer) clearTimeout(timeoutTimer) }
322
-
323
- const requestInit: RequestInit = {
324
- method,
325
- headers,
326
- signal: attemptSignal,
327
- }
328
- if (init.body !== undefined) {
329
- requestInit.body = isFormData ? (init.body as FormData) : serializeBody(init.body)
330
- }
331
-
332
- let res: Response
333
- try {
334
- res = await client.fetchFn(url, requestInit)
335
- } catch (err) {
336
- clearTimeoutTimer()
337
- // Erreur réseau transitoire : rejeu (méthodes sûres uniquement, jamais sur AbortError).
338
- // Un timeout produit un AbortError → non rejoué (échec borné et rapide).
339
- if (retryEnabled && attempt < maxRetries && !isAbortError(err)) {
340
- const nextAttempt = attempt + 1
341
- emitAuraEvent(client.onEvent, client.debug, { kind: 'retry', attempt: nextAttempt, url })
342
- await abortableSleep(computeBackoff(attempt), init.signal)
343
- attempt = nextAttempt
344
- continue
345
- }
346
- if (timedOut) {
347
- emitAuraEvent(client.onEvent, client.debug, { kind: 'timeout', url })
348
- }
349
- // E18 : vraie instance AuraError (status 0 = erreur côté client).
350
- return {
351
- data: null,
352
- error: new AuraError(0, {
353
- error: timedOut
354
- ? { code: 'timeout', message: `Request timed out after ${timeoutMs}ms` }
355
- : { code: 'network_error', message: err instanceof Error ? err.message : 'Network request failed' },
356
- }),
357
- }
358
- }
359
- clearTimeoutTimer()
360
-
361
- // Retry transitoire (GET/HEAD uniquement, statuts 408/429/502/503/504).
362
- if (retryEnabled && attempt < maxRetries && isRetryableStatus(res.status)) {
363
- // Retry-After prioritaire mais PLAFONNÉ (un serveur mal configuré/hostile pourrait
364
- // renvoyer 86400 → sommeil de 24h, ou overflow setTimeout Node → retry immédiat).
365
- const delay = Math.min(
366
- parseRetryAfter(res.headers.get('Retry-After')) ?? computeBackoff(attempt),
367
- RETRY_AFTER_CAP_MS,
368
- )
369
- // Consommer le corps non lu avant de rejouer (sinon fuite de socket sous undici/Node).
370
- await res.body?.cancel().catch(() => {})
371
- const nextAttempt = attempt + 1
372
- emitAuraEvent(client.onEvent, client.debug, { kind: 'retry', attempt: nextAttempt, status: res.status, url })
373
- await abortableSleep(delay, init.signal)
374
- attempt = nextAttempt
375
- continue
376
- }
377
-
378
- // E19 : retry réactif borné sur 401 (inchangé, orthogonal au retry transitoire).
379
- if (
380
- res.status === 401 &&
381
- !init.skipAuthRefresh &&
382
- !init._retried &&
383
- client.authStore.refreshToken
384
- ) {
385
- const refreshed = await refreshOnce(client)
386
- if (refreshed.data) {
387
- // Consommer le corps 401 non lu avant de rejouer la requête.
388
- await res.body?.cancel().catch(() => {})
389
- return fetchWithAuth<T>(url, { ...init, _retried: true }, client)
390
- }
391
- }
392
-
393
- // 204 No Content
394
- if (res.status === 204) {
395
- return { data: null, error: null }
396
- }
397
-
398
- // Error responses
399
- if (!res.ok) {
400
- let body: { error?: { code?: string; message?: string }; message?: string } | null = null
401
- try {
402
- body = await res.json()
403
- } catch {
404
- // non-JSON error body
405
- }
406
- // E18 : retourner la vraie instance (status, isUnauthorized, isRateLimited…).
407
- return { data: null, error: new AuraError(res.status, body) }
408
- }
409
-
410
- // Successful JSON response
411
- try {
412
- const body = await res.json()
413
- // I5 : `'data' in body` (pas `body.data ?? body`). `??` traite un `data`
414
- // EXPLICITEMENT `null` comme absent et retombait sur l'enveloppe entière
415
- // (`{data:null,meta:null}`) au lieu de `null` — ce qui rendait menteur le
416
- // type `AuraResponse<null>` des méthodes void (`signOut`, `deleteUser`,
417
- // `banUser`/`unbanUser`, `revokeSession`, `testEmailTemplate`…) : ces
418
- // handlers renvoient réellement `ApiResponse::<()>::empty()` côté backend
419
- // (`{"data":null,"meta":null}`, vérifié `services/aura-auth/src/handlers/
420
- // {auth,admin,sessions,email_templates}.rs`) — donc `null` est la valeur
421
- // honnête, pas l'enveloppe. `'data' in body` distingue « absent » (corps
422
- // non enveloppé, legacy) de « explicitement null » (enveloppe vide).
423
- return {
424
- data: unwrapEnvelope(body) as T,
425
- error: null,
426
- meta:
427
- body !== null && typeof body === 'object' && 'meta' in body
428
- ? (body as { meta?: Meta }).meta
429
- : undefined,
430
- }
431
- } catch {
432
- return { data: null, error: null }
433
- }
434
- }
435
- }
@@ -1,22 +0,0 @@
1
- export { AuraError, isAuraError } from './AuraError'
2
- export type { AuraClientLike } from './client'
3
- export { fetchWithAuth, refreshOnce } from './fetch'
4
- export type { FetchInit } from './fetch'
5
- export { BaseService } from './BaseService'
6
- export { BaseAuthStore, isTokenExpired } from './stores/BaseAuthStore'
7
- export { LocalAuthStore } from './stores/LocalAuthStore'
8
- export { serializeBody } from './serialize'
9
- export { parseCookieHeader, serializeCookieHeader } from './cookie-utils'
10
- export type { SerializeCookieOptions } from './cookie-utils'
11
- export {
12
- RETRYABLE_STATUS,
13
- RETRYABLE_METHODS,
14
- DEFAULT_MAX_RETRIES,
15
- isRetryableStatus,
16
- isRetryableMethod,
17
- computeBackoff,
18
- parseRetryAfter,
19
- abortableSleep,
20
- isAbortError,
21
- } from './retry'
22
- export * from './types'
@@ -1,63 +0,0 @@
1
- /**
2
- * Primitives de résilience réseau (module pur, sans I/O).
3
- * `Math.random`/`Date.now` sont injectables pour des tests déterministes.
4
- */
5
-
6
- export const RETRYABLE_STATUS = new Set([408, 429, 502, 503, 504])
7
- export const RETRYABLE_METHODS = new Set(['GET', 'HEAD'])
8
- export const DEFAULT_MAX_RETRIES = 3
9
-
10
- const BASE_MS = 500
11
- const CAP_MS = 10_000
12
-
13
- export function isRetryableStatus(status: number): boolean {
14
- return RETRYABLE_STATUS.has(status)
15
- }
16
-
17
- export function isRetryableMethod(method: string): boolean {
18
- return RETRYABLE_METHODS.has(method.toUpperCase())
19
- }
20
-
21
- /** Full jitter : random(0, min(BASE·2^attempt, CAP)). `attempt` commence à 0. */
22
- export function computeBackoff(attempt: number, rand: () => number = Math.random): number {
23
- const bound = Math.min(BASE_MS * 2 ** attempt, CAP_MS)
24
- return Math.floor(rand() * bound)
25
- }
26
-
27
- /** Parse un header `Retry-After` (secondes OU HTTP-date) → délai en ms, ou null. */
28
- export function parseRetryAfter(header: string | null, now: number = Date.now()): number | null {
29
- if (header === null) return null
30
- const trimmed = header.trim()
31
- if (/^\d+$/.test(trimmed)) return parseInt(trimmed, 10) * 1000
32
- const dateMs = Date.parse(trimmed)
33
- if (Number.isNaN(dateMs)) return null
34
- return Math.max(0, dateMs - now)
35
- }
36
-
37
- /** `sleep(ms)` qui résout tôt (sans rejeter) si `signal` s'annule. */
38
- export function abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
39
- return new Promise((resolve) => {
40
- if (signal?.aborted) {
41
- resolve()
42
- return
43
- }
44
- const onAbort = () => {
45
- clearTimeout(timer)
46
- resolve()
47
- }
48
- const timer = setTimeout(() => {
49
- signal?.removeEventListener('abort', onAbort)
50
- resolve()
51
- }, ms)
52
- signal?.addEventListener('abort', onAbort, { once: true })
53
- })
54
- }
55
-
56
- /** True si l'erreur `fetch` est une annulation utilisateur (jamais rejouée). */
57
- export function isAbortError(err: unknown): boolean {
58
- return (
59
- typeof err === 'object' &&
60
- err !== null &&
61
- (err as { name?: unknown }).name === 'AbortError'
62
- )
63
- }
@@ -1,6 +0,0 @@
1
- /** JSON.stringify avec support des bigint (sérialisés en string). */
2
- export function serializeBody(body: unknown): string {
3
- return JSON.stringify(body, (_key, value) =>
4
- typeof value === 'bigint' ? value.toString() : value,
5
- )
6
- }
@@ -1,164 +0,0 @@
1
- import type { User, AuraObservabilityEvent } from '../types'
2
- import { emitAuraEvent } from '../types'
3
-
4
- type AuthChangeCallback = (token: string | null, user: User | null) => void
5
-
6
- /**
7
- * Decode the payload of a JWT without verifying the signature.
8
- * Works in both browser and Node (uses base64url decoding).
9
- */
10
- function decodeJwtPayload(token: string): Record<string, unknown> | null {
11
- try {
12
- const parts = token.split('.')
13
- if (parts.length !== 3) return null
14
- // base64url → base64
15
- let payload = parts[1]!.replace(/-/g, '+').replace(/_/g, '/')
16
- // pad to multiple of 4
17
- while (payload.length % 4 !== 0) payload += '='
18
- // atob is available in all target environments (browsers + Node >= 16)
19
- const json = atob(payload)
20
- return JSON.parse(json)
21
- } catch {
22
- return null
23
- }
24
- }
25
-
26
- /**
27
- * Check whether a JWT has expired (or will expire within `marginSec` seconds).
28
- */
29
- export function isTokenExpired(token: string, marginSec = 0): boolean {
30
- const payload = decodeJwtPayload(token)
31
- if (!payload || typeof payload.exp !== 'number') return true
32
- const nowSec = Math.floor(Date.now() / 1000)
33
- return payload.exp - marginSec <= nowSec
34
- }
35
-
36
- /**
37
- * In-memory auth store with an event-emitter pattern.
38
- * Can be extended (see LocalAuthStore) for persistence.
39
- */
40
- export class BaseAuthStore {
41
- /**
42
- * True si le store survit à un redirect pleine page (persistance disque, ex.
43
- * localStorage). Le store de base est en mémoire → non persistant. Exploité par
44
- * le flux PKCE : le `code_verifier` doit survivre au redirect OAuth.
45
- */
46
- readonly persistent: boolean = false
47
- private _token: string | null = null
48
- private _refreshToken: string | null = null
49
- private _user: User | null = null
50
- private _listeners: Set<AuthChangeCallback> = new Set()
51
- private _removalEpoch = 0
52
- /** Stockage générique clé/valeur (ex: PKCE `code_verifier`). En mémoire par défaut. */
53
- private _kv: Map<string, string> = new Map()
54
- /** Hook d'observabilité (LOT 3) — câblé par `AuraClient` via `setObservabilityHook`. */
55
- private _onEvent?: (event: AuraObservabilityEvent) => void
56
- private _debug = false
57
-
58
- // -- Getters ----------------------------------------------------------------
59
-
60
- /** Compteur incrémenté à chaque `clear()` — sert au commit-guard anti-race du refresh. */
61
- get removalEpoch(): number {
62
- return this._removalEpoch
63
- }
64
-
65
- get token(): string | null {
66
- return this._token
67
- }
68
-
69
- get refreshToken(): string | null {
70
- return this._refreshToken
71
- }
72
-
73
- get user(): User | null {
74
- return this._user
75
- }
76
-
77
- /** True if a non-expired access token is present. */
78
- get isValid(): boolean {
79
- return this._token !== null && !isTokenExpired(this._token)
80
- }
81
-
82
- // -- Mutators ---------------------------------------------------------------
83
-
84
- save(token: string, refreshToken: string, user: User): void {
85
- this._token = token
86
- this._refreshToken = refreshToken
87
- this._user = user
88
- this._triggerChange()
89
- }
90
-
91
- clear(): void {
92
- this._removalEpoch++
93
- this._token = null
94
- this._refreshToken = null
95
- this._user = null
96
- // Purge le stockage clé/valeur (dont le `code_verifier` PKCE) : un `signOut`/
97
- // `clear()` ne doit laisser aucun résidu d'auth réutilisable en mémoire.
98
- this._kv.clear()
99
- this._triggerChange()
100
- }
101
-
102
- // -- Generic key/value storage ------------------------------------------------
103
- // Utilisé par le flux PKCE (`code_verifier`) et tout autre besoin de stockage
104
- // arbitraire lié à l'auth. Impl de base : en mémoire (ne survit pas à un
105
- // redirect pleine page — voir `LocalAuthStore` pour la persistance).
106
-
107
- setItem(key: string, value: string): void {
108
- this._kv.set(key, value)
109
- }
110
-
111
- getItem(key: string): string | null {
112
- return this._kv.get(key) ?? null
113
- }
114
-
115
- removeItem(key: string): void {
116
- this._kv.delete(key)
117
- }
118
-
119
- // -- Observability (LOT 3) ----------------------------------------------------
120
-
121
- /**
122
- * Câble le hook d'observabilité (appelé par `AuraClient` à la construction).
123
- * Permet à `LocalAuthStore` d'émettre `storage-blocked` quand `localStorage` est
124
- * indisponible (navigation privée Safari, quota dépassé, storage désactivé…).
125
- * Sans effet sur `BaseAuthStore` elle-même (en mémoire, jamais "bloquée").
126
- */
127
- setObservabilityHook(
128
- onEvent: ((event: AuraObservabilityEvent) => void) | undefined,
129
- debug: boolean | undefined,
130
- ): void {
131
- this._onEvent = onEvent
132
- this._debug = !!debug
133
- }
134
-
135
- /** Émet `{kind:'storage-blocked', op}` — ne throw jamais (voir `emitAuraEvent`). */
136
- protected _emitStorageBlocked(op: string): void {
137
- emitAuraEvent(this._onEvent, this._debug, { kind: 'storage-blocked', op })
138
- }
139
-
140
- // -- Event emitter ----------------------------------------------------------
141
-
142
- /**
143
- * Register a callback that fires whenever the auth state changes.
144
- * Returns an unsubscribe function.
145
- */
146
- onChange(callback: AuthChangeCallback): () => void {
147
- this._listeners.add(callback)
148
- return () => {
149
- this._listeners.delete(callback)
150
- }
151
- }
152
-
153
- // -- Internal ---------------------------------------------------------------
154
-
155
- protected _triggerChange(): void {
156
- for (const cb of this._listeners) {
157
- try {
158
- cb(this._token, this._user)
159
- } catch {
160
- // subscriber errors must not break the store
161
- }
162
- }
163
- }
164
- }