@payez/next-mvp 3.0.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 (654) hide show
  1. package/README.md +782 -0
  2. package/dist/api/auth-handler.d.ts +67 -0
  3. package/dist/api/auth-handler.js +397 -0
  4. package/dist/api/index.d.ts +10 -0
  5. package/dist/api/index.js +19 -0
  6. package/dist/api-handlers/account/change-password.d.ts +9 -0
  7. package/dist/api-handlers/account/change-password.js +112 -0
  8. package/dist/api-handlers/account/masked-info.d.ts +2 -0
  9. package/dist/api-handlers/account/masked-info.js +41 -0
  10. package/dist/api-handlers/account/profile.d.ts +3 -0
  11. package/dist/api-handlers/account/profile.js +63 -0
  12. package/dist/api-handlers/account/recovery/initiate.d.ts +2 -0
  13. package/dist/api-handlers/account/recovery/initiate.js +26 -0
  14. package/dist/api-handlers/account/recovery/send-code.d.ts +2 -0
  15. package/dist/api-handlers/account/recovery/send-code.js +28 -0
  16. package/dist/api-handlers/account/recovery/verify-code.d.ts +2 -0
  17. package/dist/api-handlers/account/recovery/verify-code.js +28 -0
  18. package/dist/api-handlers/account/reset-password.d.ts +2 -0
  19. package/dist/api-handlers/account/reset-password.js +26 -0
  20. package/dist/api-handlers/account/send-code.d.ts +24 -0
  21. package/dist/api-handlers/account/send-code.js +60 -0
  22. package/dist/api-handlers/account/update-phone.d.ts +27 -0
  23. package/dist/api-handlers/account/update-phone.js +64 -0
  24. package/dist/api-handlers/account/validate-password.d.ts +17 -0
  25. package/dist/api-handlers/account/validate-password.js +81 -0
  26. package/dist/api-handlers/account/verify-email.d.ts +26 -0
  27. package/dist/api-handlers/account/verify-email.js +106 -0
  28. package/dist/api-handlers/account/verify-sms.d.ts +26 -0
  29. package/dist/api-handlers/account/verify-sms.js +106 -0
  30. package/dist/api-handlers/admin/analytics.d.ts +20 -0
  31. package/dist/api-handlers/admin/analytics.js +379 -0
  32. package/dist/api-handlers/admin/audit.d.ts +20 -0
  33. package/dist/api-handlers/admin/audit.js +214 -0
  34. package/dist/api-handlers/admin/index.d.ts +21 -0
  35. package/dist/api-handlers/admin/index.js +41 -0
  36. package/dist/api-handlers/admin/redis-sessions.d.ts +36 -0
  37. package/dist/api-handlers/admin/redis-sessions.js +204 -0
  38. package/dist/api-handlers/admin/sessions.d.ts +21 -0
  39. package/dist/api-handlers/admin/sessions.js +284 -0
  40. package/dist/api-handlers/admin/site-logs.d.ts +46 -0
  41. package/dist/api-handlers/admin/site-logs.js +318 -0
  42. package/dist/api-handlers/admin/users.d.ts +20 -0
  43. package/dist/api-handlers/admin/users.js +222 -0
  44. package/dist/api-handlers/admin/vibe-data.d.ts +80 -0
  45. package/dist/api-handlers/admin/vibe-data.js +268 -0
  46. package/dist/api-handlers/anon/preferences.d.ts +37 -0
  47. package/dist/api-handlers/anon/preferences.js +96 -0
  48. package/dist/api-handlers/auth/jwks.d.ts +2 -0
  49. package/dist/api-handlers/auth/jwks.js +24 -0
  50. package/dist/api-handlers/auth/login.d.ts +42 -0
  51. package/dist/api-handlers/auth/login.js +178 -0
  52. package/dist/api-handlers/auth/refresh.d.ts +74 -0
  53. package/dist/api-handlers/auth/refresh.js +635 -0
  54. package/dist/api-handlers/auth/signout.d.ts +37 -0
  55. package/dist/api-handlers/auth/signout.js +187 -0
  56. package/dist/api-handlers/auth/status.d.ts +8 -0
  57. package/dist/api-handlers/auth/status.js +26 -0
  58. package/dist/api-handlers/auth/update-session.d.ts +37 -0
  59. package/dist/api-handlers/auth/update-session.js +95 -0
  60. package/dist/api-handlers/auth/validate.d.ts +6 -0
  61. package/dist/api-handlers/auth/validate.js +43 -0
  62. package/dist/api-handlers/auth/verify-code.d.ts +43 -0
  63. package/dist/api-handlers/auth/verify-code.js +94 -0
  64. package/dist/api-handlers/session/refresh-viability.d.ts +14 -0
  65. package/dist/api-handlers/session/refresh-viability.js +39 -0
  66. package/dist/api-handlers/session/viability.d.ts +13 -0
  67. package/dist/api-handlers/session/viability.js +146 -0
  68. package/dist/api-handlers/test/force-expire.d.ts +23 -0
  69. package/dist/api-handlers/test/force-expire.js +65 -0
  70. package/dist/auth/auth-decision.d.ts +39 -0
  71. package/dist/auth/auth-decision.js +182 -0
  72. package/dist/auth/auth-options.d.ts +57 -0
  73. package/dist/auth/auth-options.js +213 -0
  74. package/dist/auth/callbacks/index.d.ts +6 -0
  75. package/dist/auth/callbacks/index.js +12 -0
  76. package/dist/auth/callbacks/jwt.d.ts +45 -0
  77. package/dist/auth/callbacks/jwt.js +305 -0
  78. package/dist/auth/callbacks/session.d.ts +60 -0
  79. package/dist/auth/callbacks/session.js +170 -0
  80. package/dist/auth/callbacks/signin.d.ts +23 -0
  81. package/dist/auth/callbacks/signin.js +44 -0
  82. package/dist/auth/events/index.d.ts +4 -0
  83. package/dist/auth/events/index.js +8 -0
  84. package/dist/auth/events/signout.d.ts +17 -0
  85. package/dist/auth/events/signout.js +32 -0
  86. package/dist/auth/providers/credentials.d.ts +32 -0
  87. package/dist/auth/providers/credentials.js +223 -0
  88. package/dist/auth/providers/index.d.ts +5 -0
  89. package/dist/auth/providers/index.js +21 -0
  90. package/dist/auth/providers/oauth.d.ts +26 -0
  91. package/dist/auth/providers/oauth.js +105 -0
  92. package/dist/auth/route-config.d.ts +66 -0
  93. package/dist/auth/route-config.js +190 -0
  94. package/dist/auth/types/auth-types.d.ts +417 -0
  95. package/dist/auth/types/auth-types.js +53 -0
  96. package/dist/auth/types/index.d.ts +6 -0
  97. package/dist/auth/types/index.js +22 -0
  98. package/dist/auth/unauthenticated-routes.d.ts +1 -0
  99. package/dist/auth/unauthenticated-routes.js +19 -0
  100. package/dist/auth/utils/idp-client.d.ts +94 -0
  101. package/dist/auth/utils/idp-client.js +383 -0
  102. package/dist/auth/utils/index.d.ts +5 -0
  103. package/dist/auth/utils/index.js +21 -0
  104. package/dist/auth/utils/token-utils.d.ts +84 -0
  105. package/dist/auth/utils/token-utils.js +219 -0
  106. package/dist/client/AuthContext.d.ts +19 -0
  107. package/dist/client/AuthContext.js +112 -0
  108. package/dist/client/fetch-with-auth.d.ts +11 -0
  109. package/dist/client/fetch-with-auth.js +44 -0
  110. package/dist/client/fetchWithSession.d.ts +3 -0
  111. package/dist/client/fetchWithSession.js +24 -0
  112. package/dist/client/index.d.ts +9 -0
  113. package/dist/client/index.js +20 -0
  114. package/dist/client/useAnonSession.d.ts +36 -0
  115. package/dist/client/useAnonSession.js +99 -0
  116. package/dist/components/SessionSync.d.ts +13 -0
  117. package/dist/components/SessionSync.js +119 -0
  118. package/dist/components/SignalRHealthCheck.d.ts +10 -0
  119. package/dist/components/SignalRHealthCheck.js +97 -0
  120. package/dist/components/account/UserAvatarMenu.d.ts +20 -0
  121. package/dist/components/account/UserAvatarMenu.js +80 -0
  122. package/dist/components/account/index.d.ts +7 -0
  123. package/dist/components/account/index.js +10 -0
  124. package/dist/components/admin/AlertSettingsTab.d.ts +48 -0
  125. package/dist/components/admin/AlertSettingsTab.js +351 -0
  126. package/dist/components/admin/AnalyticsTab.d.ts +22 -0
  127. package/dist/components/admin/AnalyticsTab.js +167 -0
  128. package/dist/components/admin/DataBrowserTab.d.ts +19 -0
  129. package/dist/components/admin/DataBrowserTab.js +252 -0
  130. package/dist/components/admin/LoggingSettingsTab.d.ts +73 -0
  131. package/dist/components/admin/LoggingSettingsTab.js +339 -0
  132. package/dist/components/admin/SessionsTab.d.ts +37 -0
  133. package/dist/components/admin/SessionsTab.js +165 -0
  134. package/dist/components/admin/StatsTab.d.ts +53 -0
  135. package/dist/components/admin/StatsTab.js +161 -0
  136. package/dist/components/admin/VibeAdminContext.d.ts +32 -0
  137. package/dist/components/admin/VibeAdminContext.js +38 -0
  138. package/dist/components/admin/VibeAdminLayout.d.ts +11 -0
  139. package/dist/components/admin/VibeAdminLayout.js +69 -0
  140. package/dist/components/admin/index.d.ts +29 -0
  141. package/dist/components/admin/index.js +44 -0
  142. package/dist/components/auth/FederatedAuthSection.d.ts +8 -0
  143. package/dist/components/auth/FederatedAuthSection.js +45 -0
  144. package/dist/components/auth/ModeAwareLoginPage.d.ts +10 -0
  145. package/dist/components/auth/ModeAwareLoginPage.js +42 -0
  146. package/dist/components/auth/ModeAwareSignupPage.d.ts +9 -0
  147. package/dist/components/auth/ModeAwareSignupPage.js +78 -0
  148. package/dist/components/auth/TraditionalAuthSection.d.ts +14 -0
  149. package/dist/components/auth/TraditionalAuthSection.js +20 -0
  150. package/dist/components/recovery/CompleteStep.d.ts +5 -0
  151. package/dist/components/recovery/CompleteStep.js +8 -0
  152. package/dist/components/recovery/InitiateRecoveryStep.d.ts +8 -0
  153. package/dist/components/recovery/InitiateRecoveryStep.js +20 -0
  154. package/dist/components/recovery/SelectMethodStep.d.ts +8 -0
  155. package/dist/components/recovery/SelectMethodStep.js +8 -0
  156. package/dist/components/recovery/SetPasswordStep.d.ts +6 -0
  157. package/dist/components/recovery/SetPasswordStep.js +20 -0
  158. package/dist/components/recovery/VerifyCodeStep.d.ts +10 -0
  159. package/dist/components/recovery/VerifyCodeStep.js +24 -0
  160. package/dist/components/reserved/ReservedRecoveryWarning.d.ts +38 -0
  161. package/dist/components/reserved/ReservedRecoveryWarning.js +92 -0
  162. package/dist/components/reserved/ReservedStatusBox.d.ts +30 -0
  163. package/dist/components/reserved/ReservedStatusBox.js +71 -0
  164. package/dist/components/ui/BetaBadge.d.ts +29 -0
  165. package/dist/components/ui/BetaBadge.js +38 -0
  166. package/dist/components/ui/Footer.d.ts +37 -0
  167. package/dist/components/ui/Footer.js +41 -0
  168. package/dist/config/env.d.ts +66 -0
  169. package/dist/config/env.js +57 -0
  170. package/dist/config/logger.d.ts +57 -0
  171. package/dist/config/logger.js +73 -0
  172. package/dist/config/logging-config.d.ts +30 -0
  173. package/dist/config/logging-config.js +122 -0
  174. package/dist/config/unauthenticated-routes.d.ts +17 -0
  175. package/dist/config/unauthenticated-routes.js +24 -0
  176. package/dist/config/vibe-log-transport.d.ts +79 -0
  177. package/dist/config/vibe-log-transport.js +203 -0
  178. package/dist/edge/internal-api-url.d.ts +53 -0
  179. package/dist/edge/internal-api-url.js +63 -0
  180. package/dist/edge/middleware.d.ts +14 -0
  181. package/dist/edge/middleware.js +32 -0
  182. package/dist/hooks/useAuth.d.ts +23 -0
  183. package/dist/hooks/useAuth.js +81 -0
  184. package/dist/hooks/useAuthSettings.d.ts +59 -0
  185. package/dist/hooks/useAuthSettings.js +93 -0
  186. package/dist/hooks/useAvailableProviders.d.ts +45 -0
  187. package/dist/hooks/useAvailableProviders.js +108 -0
  188. package/dist/hooks/usePasswordValidation.d.ts +27 -0
  189. package/dist/hooks/usePasswordValidation.js +102 -0
  190. package/dist/hooks/useProfile.d.ts +15 -0
  191. package/dist/hooks/useProfile.js +59 -0
  192. package/dist/hooks/usePublicAuthSettings.d.ts +56 -0
  193. package/dist/hooks/usePublicAuthSettings.js +131 -0
  194. package/dist/hooks/useSessionExpiration.d.ts +57 -0
  195. package/dist/hooks/useSessionExpiration.js +72 -0
  196. package/dist/hooks/useViabilitySession.d.ts +75 -0
  197. package/dist/hooks/useViabilitySession.js +268 -0
  198. package/dist/index.d.ts +12 -0
  199. package/dist/index.js +54 -0
  200. package/dist/lib/anon-session.d.ts +74 -0
  201. package/dist/lib/anon-session.js +169 -0
  202. package/dist/lib/api-handler.d.ts +123 -0
  203. package/dist/lib/api-handler.js +478 -0
  204. package/dist/lib/app-slug.d.ts +95 -0
  205. package/dist/lib/app-slug.js +172 -0
  206. package/dist/lib/demo-mode.d.ts +6 -0
  207. package/dist/lib/demo-mode.js +16 -0
  208. package/dist/lib/geolocation.d.ts +64 -0
  209. package/dist/lib/geolocation.js +235 -0
  210. package/dist/lib/idp-client-config.d.ts +75 -0
  211. package/dist/lib/idp-client-config.js +351 -0
  212. package/dist/lib/idp-fetch.d.ts +14 -0
  213. package/dist/lib/idp-fetch.js +91 -0
  214. package/dist/lib/internal-api.d.ts +87 -0
  215. package/dist/lib/internal-api.js +122 -0
  216. package/dist/lib/jwt-decode-client.d.ts +10 -0
  217. package/dist/lib/jwt-decode-client.js +46 -0
  218. package/dist/lib/jwt-decode.d.ts +48 -0
  219. package/dist/lib/jwt-decode.js +57 -0
  220. package/dist/lib/nextauth-secret.d.ts +10 -0
  221. package/dist/lib/nextauth-secret.js +104 -0
  222. package/dist/lib/rate-limit-service.d.ts +23 -0
  223. package/dist/lib/rate-limit-service.js +6 -0
  224. package/dist/lib/redis.d.ts +5 -0
  225. package/dist/lib/redis.js +28 -0
  226. package/dist/lib/refresh-token-validator.d.ts +13 -0
  227. package/dist/lib/refresh-token-validator.js +117 -0
  228. package/dist/lib/roles.d.ts +145 -0
  229. package/dist/lib/roles.js +168 -0
  230. package/dist/lib/secret-validation.d.ts +4 -0
  231. package/dist/lib/secret-validation.js +14 -0
  232. package/dist/lib/session-store.d.ts +166 -0
  233. package/dist/lib/session-store.js +537 -0
  234. package/dist/lib/session.d.ts +21 -0
  235. package/dist/lib/session.js +26 -0
  236. package/dist/lib/site-logger.d.ts +214 -0
  237. package/dist/lib/site-logger.js +210 -0
  238. package/dist/lib/standardized-client-api.d.ts +161 -0
  239. package/dist/lib/standardized-client-api.js +786 -0
  240. package/dist/lib/startup-init.d.ts +40 -0
  241. package/dist/lib/startup-init.js +261 -0
  242. package/dist/lib/test-aware-get-token.d.ts +2 -0
  243. package/dist/lib/test-aware-get-token.js +81 -0
  244. package/dist/lib/token-expiry.d.ts +14 -0
  245. package/dist/lib/token-expiry.js +39 -0
  246. package/dist/lib/token-lifecycle.d.ts +52 -0
  247. package/dist/lib/token-lifecycle.js +398 -0
  248. package/dist/lib/types/api-responses.d.ts +128 -0
  249. package/dist/lib/types/api-responses.js +171 -0
  250. package/dist/lib/user-agent-parser.d.ts +50 -0
  251. package/dist/lib/user-agent-parser.js +220 -0
  252. package/dist/logging/api/admin-analytics.d.ts +3 -0
  253. package/dist/logging/api/admin-analytics.js +45 -0
  254. package/dist/logging/api/audit-log.d.ts +3 -0
  255. package/dist/logging/api/audit-log.js +52 -0
  256. package/dist/logging/components/AdminAnalyticsLayout.d.ts +10 -0
  257. package/dist/logging/components/AdminAnalyticsLayout.js +11 -0
  258. package/dist/logging/components/AuditLogViewer.d.ts +7 -0
  259. package/dist/logging/components/AuditLogViewer.js +51 -0
  260. package/dist/logging/components/ErrorMetricsCard.d.ts +7 -0
  261. package/dist/logging/components/ErrorMetricsCard.js +16 -0
  262. package/dist/logging/components/HealthMetricsCard.d.ts +7 -0
  263. package/dist/logging/components/HealthMetricsCard.js +19 -0
  264. package/dist/logging/hooks/useAdminAnalytics.d.ts +24 -0
  265. package/dist/logging/hooks/useAdminAnalytics.js +22 -0
  266. package/dist/logging/hooks/useAuditLog.d.ts +6 -0
  267. package/dist/logging/hooks/useAuditLog.js +25 -0
  268. package/dist/logging/hooks/useErrorMetrics.d.ts +6 -0
  269. package/dist/logging/hooks/useErrorMetrics.js +38 -0
  270. package/dist/logging/hooks/useHealthMetrics.d.ts +6 -0
  271. package/dist/logging/hooks/useHealthMetrics.js +41 -0
  272. package/dist/logging/index.d.ts +11 -0
  273. package/dist/logging/index.js +40 -0
  274. package/dist/logging/types/analytics.d.ts +68 -0
  275. package/dist/logging/types/analytics.js +3 -0
  276. package/dist/logging/types/audit.d.ts +29 -0
  277. package/dist/logging/types/audit.js +2 -0
  278. package/dist/logging/types/index.d.ts +2 -0
  279. package/dist/logging/types/index.js +19 -0
  280. package/dist/middleware/auth-decision.d.ts +33 -0
  281. package/dist/middleware/auth-decision.js +65 -0
  282. package/dist/middleware/create-middleware.d.ts +100 -0
  283. package/dist/middleware/create-middleware.js +445 -0
  284. package/dist/middleware/rbac-check.d.ts +44 -0
  285. package/dist/middleware/rbac-check.js +191 -0
  286. package/dist/middleware/twofa-presets.d.ts +134 -0
  287. package/dist/middleware/twofa-presets.js +175 -0
  288. package/dist/models/DecodedAccessToken.d.ts +17 -0
  289. package/dist/models/DecodedAccessToken.js +2 -0
  290. package/dist/models/SessionModel.d.ts +122 -0
  291. package/dist/models/SessionModel.js +136 -0
  292. package/dist/pages/admin-login/page.d.ts +31 -0
  293. package/dist/pages/admin-login/page.js +83 -0
  294. package/dist/pages/admin-roles/RolesAdminPage.d.ts +15 -0
  295. package/dist/pages/admin-roles/RolesAdminPage.js +78 -0
  296. package/dist/pages/admin-roles/index.d.ts +8 -0
  297. package/dist/pages/admin-roles/index.js +15 -0
  298. package/dist/pages/admin-roles/modals.d.ts +72 -0
  299. package/dist/pages/admin-roles/modals.js +154 -0
  300. package/dist/pages/client-admin/ClientSiteAdminPage.d.ts +79 -0
  301. package/dist/pages/client-admin/ClientSiteAdminPage.js +177 -0
  302. package/dist/pages/client-admin/index.d.ts +32 -0
  303. package/dist/pages/client-admin/index.js +37 -0
  304. package/dist/pages/login/page.d.ts +22 -0
  305. package/dist/pages/login/page.js +239 -0
  306. package/dist/pages/profile/EnhancedProfilePage.d.ts +13 -0
  307. package/dist/pages/profile/EnhancedProfilePage.js +150 -0
  308. package/dist/pages/profile/index.d.ts +8 -0
  309. package/dist/pages/profile/index.js +16 -0
  310. package/dist/pages/profile/page.d.ts +19 -0
  311. package/dist/pages/profile/page.js +47 -0
  312. package/dist/pages/profile/profile-patch.d.ts +1 -0
  313. package/dist/pages/profile/profile-patch.js +281 -0
  314. package/dist/pages/recovery/page.d.ts +1 -0
  315. package/dist/pages/recovery/page.js +142 -0
  316. package/dist/pages/roles/MyRolesPage.d.ts +24 -0
  317. package/dist/pages/roles/MyRolesPage.js +71 -0
  318. package/dist/pages/roles/components.d.ts +63 -0
  319. package/dist/pages/roles/components.js +108 -0
  320. package/dist/pages/roles/index.d.ts +8 -0
  321. package/dist/pages/roles/index.js +19 -0
  322. package/dist/pages/security/EnhancedSecurityPage.d.ts +14 -0
  323. package/dist/pages/security/EnhancedSecurityPage.js +248 -0
  324. package/dist/pages/security/index.d.ts +8 -0
  325. package/dist/pages/security/index.js +16 -0
  326. package/dist/pages/security/page.d.ts +21 -0
  327. package/dist/pages/security/page.js +212 -0
  328. package/dist/pages/security/security-patch.d.ts +1 -0
  329. package/dist/pages/security/security-patch.js +302 -0
  330. package/dist/pages/settings/EnhancedSettingsPage.d.ts +46 -0
  331. package/dist/pages/settings/EnhancedSettingsPage.js +231 -0
  332. package/dist/pages/settings/index.d.ts +8 -0
  333. package/dist/pages/settings/index.js +16 -0
  334. package/dist/pages/settings/page.d.ts +7 -0
  335. package/dist/pages/settings/page.js +26 -0
  336. package/dist/pages/showcase/ShowcasePage.d.ts +13 -0
  337. package/dist/pages/showcase/ShowcasePage.js +140 -0
  338. package/dist/pages/showcase/index.d.ts +12 -0
  339. package/dist/pages/showcase/index.js +17 -0
  340. package/dist/pages/test-env/EmergencyLogoutPage.d.ts +14 -0
  341. package/dist/pages/test-env/EmergencyLogoutPage.js +98 -0
  342. package/dist/pages/test-env/JwtInspectPage.d.ts +14 -0
  343. package/dist/pages/test-env/JwtInspectPage.js +114 -0
  344. package/dist/pages/test-env/RefreshTokenPage.d.ts +15 -0
  345. package/dist/pages/test-env/RefreshTokenPage.js +91 -0
  346. package/dist/pages/test-env/TestEnvPage.d.ts +13 -0
  347. package/dist/pages/test-env/TestEnvPage.js +49 -0
  348. package/dist/pages/test-env/index.d.ts +24 -0
  349. package/dist/pages/test-env/index.js +32 -0
  350. package/dist/pages/verify-code/page.d.ts +30 -0
  351. package/dist/pages/verify-code/page.js +408 -0
  352. package/dist/routes/account/index.d.ts +28 -0
  353. package/dist/routes/account/index.js +71 -0
  354. package/dist/routes/account/masked-info.d.ts +33 -0
  355. package/dist/routes/account/masked-info.js +39 -0
  356. package/dist/routes/account/send-code.d.ts +37 -0
  357. package/dist/routes/account/send-code.js +42 -0
  358. package/dist/routes/account/update-phone.d.ts +13 -0
  359. package/dist/routes/account/update-phone.js +17 -0
  360. package/dist/routes/account/verify-email.d.ts +38 -0
  361. package/dist/routes/account/verify-email.js +43 -0
  362. package/dist/routes/account/verify-sms.d.ts +38 -0
  363. package/dist/routes/account/verify-sms.js +43 -0
  364. package/dist/routes/auth/index.d.ts +19 -0
  365. package/dist/routes/auth/index.js +64 -0
  366. package/dist/routes/auth/logout.d.ts +31 -0
  367. package/dist/routes/auth/logout.js +113 -0
  368. package/dist/routes/auth/nextauth.d.ts +19 -0
  369. package/dist/routes/auth/nextauth.js +72 -0
  370. package/dist/routes/auth/refresh.d.ts +30 -0
  371. package/dist/routes/auth/refresh.js +51 -0
  372. package/dist/routes/auth/session.d.ts +72 -0
  373. package/dist/routes/auth/session.js +180 -0
  374. package/dist/routes/auth/settings.d.ts +25 -0
  375. package/dist/routes/auth/settings.js +55 -0
  376. package/dist/routes/auth/viability.d.ts +52 -0
  377. package/dist/routes/auth/viability.js +201 -0
  378. package/dist/routes/index.d.ts +12 -0
  379. package/dist/routes/index.js +54 -0
  380. package/dist/routes/session/index.d.ts +6 -0
  381. package/dist/routes/session/index.js +10 -0
  382. package/dist/routes/session/refresh-viability.d.ts +16 -0
  383. package/dist/routes/session/refresh-viability.js +20 -0
  384. package/dist/services/signalrActivityService.d.ts +44 -0
  385. package/dist/services/signalrActivityService.js +257 -0
  386. package/dist/stores/authStore.d.ts +154 -0
  387. package/dist/stores/authStore.js +1531 -0
  388. package/dist/theme/ThemeProvider.d.ts +14 -0
  389. package/dist/theme/ThemeProvider.js +28 -0
  390. package/dist/theme/default.d.ts +8 -0
  391. package/dist/theme/default.js +33 -0
  392. package/dist/theme/index.d.ts +15 -0
  393. package/dist/theme/index.js +25 -0
  394. package/dist/theme/types.d.ts +56 -0
  395. package/dist/theme/types.js +8 -0
  396. package/dist/theme/useTheme.d.ts +60 -0
  397. package/dist/theme/useTheme.js +63 -0
  398. package/dist/theme/utils.d.ts +13 -0
  399. package/dist/theme/utils.js +39 -0
  400. package/dist/types/api.d.ts +134 -0
  401. package/dist/types/api.js +44 -0
  402. package/dist/types/auth.d.ts +19 -0
  403. package/dist/types/auth.js +2 -0
  404. package/dist/types/logging.d.ts +42 -0
  405. package/dist/types/logging.js +2 -0
  406. package/dist/types/recovery.d.ts +48 -0
  407. package/dist/types/recovery.js +2 -0
  408. package/dist/types/security.d.ts +1 -0
  409. package/dist/types/security.js +2 -0
  410. package/dist/utils/api.d.ts +85 -0
  411. package/dist/utils/api.js +287 -0
  412. package/dist/utils/circuitBreaker.d.ts +43 -0
  413. package/dist/utils/circuitBreaker.js +91 -0
  414. package/dist/utils/error-message.d.ts +1 -0
  415. package/dist/utils/error-message.js +103 -0
  416. package/dist/utils/layout/reservedSpace.d.ts +59 -0
  417. package/dist/utils/layout/reservedSpace.js +102 -0
  418. package/dist/utils/logout.d.ts +14 -0
  419. package/dist/utils/logout.js +32 -0
  420. package/dist/vibe/client.d.ts +261 -0
  421. package/dist/vibe/client.js +445 -0
  422. package/dist/vibe/errors.d.ts +83 -0
  423. package/dist/vibe/errors.js +146 -0
  424. package/dist/vibe/generic.d.ts +234 -0
  425. package/dist/vibe/generic.js +369 -0
  426. package/dist/vibe/hooks/index.d.ts +169 -0
  427. package/dist/vibe/hooks/index.js +252 -0
  428. package/dist/vibe/index.d.ts +23 -0
  429. package/dist/vibe/index.js +67 -0
  430. package/dist/vibe/sessions.d.ts +161 -0
  431. package/dist/vibe/sessions.js +391 -0
  432. package/dist/vibe/types.d.ts +353 -0
  433. package/dist/vibe/types.js +315 -0
  434. package/package.json +855 -0
  435. package/scripts/check-internal-url-usage.sh +73 -0
  436. package/scripts/dev-broker.ps1 +35 -0
  437. package/scripts/dev-local.ps1 +45 -0
  438. package/src/api/auth-handler.ts +550 -0
  439. package/src/api/index.ts +18 -0
  440. package/src/api-handlers/account/change-password.ts +145 -0
  441. package/src/api-handlers/account/masked-info.ts +45 -0
  442. package/src/api-handlers/account/profile.ts +80 -0
  443. package/src/api-handlers/account/recovery/initiate.ts +23 -0
  444. package/src/api-handlers/account/recovery/send-code.ts +25 -0
  445. package/src/api-handlers/account/recovery/verify-code.ts +25 -0
  446. package/src/api-handlers/account/reset-password.ts +23 -0
  447. package/src/api-handlers/account/send-code.ts +76 -0
  448. package/src/api-handlers/account/update-phone.ts +79 -0
  449. package/src/api-handlers/account/validate-password.ts +118 -0
  450. package/src/api-handlers/account/verify-email.ts +125 -0
  451. package/src/api-handlers/account/verify-sms.ts +125 -0
  452. package/src/api-handlers/admin/analytics.ts +445 -0
  453. package/src/api-handlers/admin/audit.ts +225 -0
  454. package/src/api-handlers/admin/index.ts +59 -0
  455. package/src/api-handlers/admin/redis-sessions.ts +253 -0
  456. package/src/api-handlers/admin/sessions.ts +320 -0
  457. package/src/api-handlers/admin/site-logs.ts +367 -0
  458. package/src/api-handlers/admin/users.ts +244 -0
  459. package/src/api-handlers/admin/vibe-data.ts +326 -0
  460. package/src/api-handlers/anon/preferences.ts +123 -0
  461. package/src/api-handlers/auth/jwks.ts +20 -0
  462. package/src/api-handlers/auth/login.ts +240 -0
  463. package/src/api-handlers/auth/refresh.ts +687 -0
  464. package/src/api-handlers/auth/signout.ts +212 -0
  465. package/src/api-handlers/auth/status.ts +23 -0
  466. package/src/api-handlers/auth/update-session.ts +125 -0
  467. package/src/api-handlers/auth/validate.ts +44 -0
  468. package/src/api-handlers/auth/verify-code.ts +129 -0
  469. package/src/api-handlers/session/refresh-viability.ts +36 -0
  470. package/src/api-handlers/session/viability.ts +166 -0
  471. package/src/api-handlers/test/force-expire.ts +67 -0
  472. package/src/auth/auth-decision.ts +230 -0
  473. package/src/auth/auth-options.ts +237 -0
  474. package/src/auth/callbacks/index.ts +7 -0
  475. package/src/auth/callbacks/jwt.ts +382 -0
  476. package/src/auth/callbacks/session.ts +243 -0
  477. package/src/auth/callbacks/signin.ts +56 -0
  478. package/src/auth/events/index.ts +5 -0
  479. package/src/auth/events/signout.ts +33 -0
  480. package/src/auth/providers/credentials.ts +256 -0
  481. package/src/auth/providers/index.ts +6 -0
  482. package/src/auth/providers/oauth.ts +114 -0
  483. package/src/auth/route-config.ts +220 -0
  484. package/src/auth/types/auth-types.ts +555 -0
  485. package/src/auth/types/index.ts +7 -0
  486. package/src/auth/unauthenticated-routes.ts +3 -0
  487. package/src/auth/utils/idp-client.ts +444 -0
  488. package/src/auth/utils/index.ts +6 -0
  489. package/src/auth/utils/token-utils.ts +244 -0
  490. package/src/client/AuthContext.tsx +140 -0
  491. package/src/client/fetch-with-auth.ts +48 -0
  492. package/src/client/fetchWithSession.ts +21 -0
  493. package/src/client/index.ts +13 -0
  494. package/src/client/useAnonSession.ts +131 -0
  495. package/src/components/SessionSync.tsx +137 -0
  496. package/src/components/SignalRHealthCheck.tsx +131 -0
  497. package/src/components/account/UserAvatarMenu.tsx +217 -0
  498. package/src/components/account/index.ts +8 -0
  499. package/src/components/admin/AlertSettingsTab.tsx +728 -0
  500. package/src/components/admin/AnalyticsTab.tsx +703 -0
  501. package/src/components/admin/DataBrowserTab.tsx +505 -0
  502. package/src/components/admin/LoggingSettingsTab.tsx +665 -0
  503. package/src/components/admin/SessionsTab.tsx +414 -0
  504. package/src/components/admin/StatsTab.tsx +379 -0
  505. package/src/components/admin/VibeAdminContext.tsx +87 -0
  506. package/src/components/admin/VibeAdminLayout.tsx +185 -0
  507. package/src/components/admin/index.ts +59 -0
  508. package/src/components/auth/FederatedAuthSection.tsx +95 -0
  509. package/src/components/auth/ModeAwareLoginPage.tsx +135 -0
  510. package/src/components/auth/ModeAwareSignupPage.tsx +267 -0
  511. package/src/components/auth/TraditionalAuthSection.tsx +99 -0
  512. package/src/components/recovery/CompleteStep.tsx +36 -0
  513. package/src/components/recovery/InitiateRecoveryStep.tsx +68 -0
  514. package/src/components/recovery/SelectMethodStep.tsx +73 -0
  515. package/src/components/recovery/SetPasswordStep.tsx +97 -0
  516. package/src/components/recovery/VerifyCodeStep.tsx +90 -0
  517. package/src/components/reserved/ReservedRecoveryWarning.tsx +160 -0
  518. package/src/components/reserved/ReservedStatusBox.tsx +118 -0
  519. package/src/components/ui/BetaBadge.tsx +58 -0
  520. package/src/components/ui/Footer.tsx +93 -0
  521. package/src/config/env.ts +57 -0
  522. package/src/config/logger.ts +62 -0
  523. package/src/config/logging-config.ts +82 -0
  524. package/src/config/unauthenticated-routes.ts +19 -0
  525. package/src/config/vibe-log-transport.ts +250 -0
  526. package/src/edge/internal-api-url.ts +65 -0
  527. package/src/edge/middleware.ts +42 -0
  528. package/src/hooks/useAuth.ts +115 -0
  529. package/src/hooks/useAuthSettings.ts +97 -0
  530. package/src/hooks/useAvailableProviders.ts +118 -0
  531. package/src/hooks/usePasswordValidation.ts +127 -0
  532. package/src/hooks/useProfile.ts +75 -0
  533. package/src/hooks/usePublicAuthSettings.ts +149 -0
  534. package/src/hooks/useSessionExpiration.ts +102 -0
  535. package/src/hooks/useViabilitySession.ts +335 -0
  536. package/src/index.ts +63 -0
  537. package/src/lib/anon-session.ts +213 -0
  538. package/src/lib/api-handler.ts +625 -0
  539. package/src/lib/app-slug.ts +178 -0
  540. package/src/lib/demo-mode.ts +13 -0
  541. package/src/lib/geolocation.ts +265 -0
  542. package/src/lib/idp-client-config.ts +442 -0
  543. package/src/lib/idp-fetch.ts +101 -0
  544. package/src/lib/internal-api.ts +171 -0
  545. package/src/lib/jwt-decode-client.ts +45 -0
  546. package/src/lib/jwt-decode.ts +83 -0
  547. package/src/lib/nextauth-secret.ts +126 -0
  548. package/src/lib/rate-limit-service.ts +9 -0
  549. package/src/lib/redis.ts +27 -0
  550. package/src/lib/refresh-token-validator.ts +64 -0
  551. package/src/lib/roles.ts +177 -0
  552. package/src/lib/secret-validation.ts +8 -0
  553. package/src/lib/session-store.ts +637 -0
  554. package/src/lib/session.ts +34 -0
  555. package/src/lib/site-logger.ts +245 -0
  556. package/src/lib/standardized-client-api.ts +896 -0
  557. package/src/lib/startup-init.ts +247 -0
  558. package/src/lib/test-aware-get-token.ts +30 -0
  559. package/src/lib/token-expiry.ts +40 -0
  560. package/src/lib/token-lifecycle.ts +477 -0
  561. package/src/lib/types/api-responses.ts +336 -0
  562. package/src/lib/user-agent-parser.ts +252 -0
  563. package/src/logging/api/admin-analytics.ts +51 -0
  564. package/src/logging/api/audit-log.ts +53 -0
  565. package/src/logging/components/AdminAnalyticsLayout.tsx +49 -0
  566. package/src/logging/components/AuditLogViewer.tsx +125 -0
  567. package/src/logging/components/ErrorMetricsCard.tsx +98 -0
  568. package/src/logging/components/HealthMetricsCard.tsx +70 -0
  569. package/src/logging/hooks/useAdminAnalytics.ts +22 -0
  570. package/src/logging/hooks/useAuditLog.ts +24 -0
  571. package/src/logging/hooks/useErrorMetrics.ts +40 -0
  572. package/src/logging/hooks/useHealthMetrics.ts +44 -0
  573. package/src/logging/index.ts +18 -0
  574. package/src/logging/types/analytics.ts +81 -0
  575. package/src/logging/types/audit.ts +31 -0
  576. package/src/logging/types/index.ts +3 -0
  577. package/src/middleware/auth-decision.ts +43 -0
  578. package/src/middleware/create-middleware.ts +626 -0
  579. package/src/middleware/rbac-check.ts +244 -0
  580. package/src/middleware/twofa-presets.ts +224 -0
  581. package/src/models/DecodedAccessToken.ts +17 -0
  582. package/src/models/SessionModel.ts +258 -0
  583. package/src/pages/admin-login/page.tsx +229 -0
  584. package/src/pages/admin-roles/RolesAdminPage.tsx +357 -0
  585. package/src/pages/admin-roles/index.ts +9 -0
  586. package/src/pages/admin-roles/modals.tsx +469 -0
  587. package/src/pages/client-admin/ClientSiteAdminPage.tsx +380 -0
  588. package/src/pages/client-admin/index.ts +33 -0
  589. package/src/pages/login/page.tsx +463 -0
  590. package/src/pages/profile/EnhancedProfilePage.tsx +479 -0
  591. package/src/pages/profile/index.ts +9 -0
  592. package/src/pages/profile/page.tsx +166 -0
  593. package/src/pages/recovery/page.tsx +234 -0
  594. package/src/pages/roles/MyRolesPage.tsx +211 -0
  595. package/src/pages/roles/components.tsx +294 -0
  596. package/src/pages/roles/index.ts +17 -0
  597. package/src/pages/security/EnhancedSecurityPage.tsx +574 -0
  598. package/src/pages/security/index.ts +9 -0
  599. package/src/pages/security/page.tsx +507 -0
  600. package/src/pages/settings/EnhancedSettingsPage.tsx +642 -0
  601. package/src/pages/settings/index.ts +9 -0
  602. package/src/pages/settings/page.tsx +47 -0
  603. package/src/pages/showcase/ShowcasePage.tsx +530 -0
  604. package/src/pages/showcase/index.ts +13 -0
  605. package/src/pages/test-env/EmergencyLogoutPage.tsx +179 -0
  606. package/src/pages/test-env/JwtInspectPage.tsx +418 -0
  607. package/src/pages/test-env/RefreshTokenPage.tsx +155 -0
  608. package/src/pages/test-env/TestEnvPage.tsx +116 -0
  609. package/src/pages/test-env/index.ts +25 -0
  610. package/src/pages/verify-code/page.tsx +648 -0
  611. package/src/routes/account/index.ts +32 -0
  612. package/src/routes/account/masked-info.ts +37 -0
  613. package/src/routes/account/send-code.ts +40 -0
  614. package/src/routes/account/update-phone.ts +13 -0
  615. package/src/routes/account/verify-email.ts +41 -0
  616. package/src/routes/account/verify-sms.ts +41 -0
  617. package/src/routes/auth/index.ts +23 -0
  618. package/src/routes/auth/logout.ts +127 -0
  619. package/src/routes/auth/nextauth.ts +71 -0
  620. package/src/routes/auth/refresh.ts +54 -0
  621. package/src/routes/auth/session.ts +193 -0
  622. package/src/routes/auth/settings.ts +75 -0
  623. package/src/routes/auth/viability.ts +220 -0
  624. package/src/routes/index.ts +18 -0
  625. package/src/routes/session/index.ts +7 -0
  626. package/src/routes/session/refresh-viability.ts +17 -0
  627. package/src/services/signalrActivityService.ts +258 -0
  628. package/src/stores/authStore.ts +1904 -0
  629. package/src/templates/instrumentation.ts +41 -0
  630. package/src/theme/ThemeProvider.tsx +39 -0
  631. package/src/theme/default.ts +33 -0
  632. package/src/theme/index.ts +31 -0
  633. package/src/theme/types.ts +69 -0
  634. package/src/theme/useTheme.ts +57 -0
  635. package/src/theme/utils.ts +40 -0
  636. package/src/types/api.ts +13 -0
  637. package/src/types/auth.d.ts +15 -0
  638. package/src/types/auth.ts +22 -0
  639. package/src/types/logging.ts +11 -0
  640. package/src/types/next-auth.d.ts +15 -0
  641. package/src/types/recovery.ts +54 -0
  642. package/src/types/security.ts +1 -0
  643. package/src/utils/api.ts +353 -0
  644. package/src/utils/circuitBreaker.ts +40 -0
  645. package/src/utils/error-message.ts +108 -0
  646. package/src/utils/layout/reservedSpace.ts +124 -0
  647. package/src/utils/logout.ts +30 -0
  648. package/src/vibe/client.ts +590 -0
  649. package/src/vibe/errors.ts +185 -0
  650. package/src/vibe/generic.ts +429 -0
  651. package/src/vibe/hooks/index.ts +367 -0
  652. package/src/vibe/index.ts +121 -0
  653. package/src/vibe/sessions.ts +551 -0
  654. package/src/vibe/types.ts +577 -0
@@ -0,0 +1,896 @@
1
+ "use client";
2
+
3
+ // ========================================================================================
4
+ // BULLETPROOF STANDARDIZED CLIENT API - ZERO TOLERANCE FOR BAD RESPONSES
5
+ // ========================================================================================
6
+ // This client API ENFORCES the standardized response format
7
+ // It will BREAK if APIs don't return the expected structure
8
+ // NO MORE GUESSING data.data.data.data - EVER AGAIN!
9
+ // ========================================================================================
10
+
11
+ import { getSession } from 'next-auth/react';
12
+ import { DefaultSession } from 'next-auth';
13
+ import {
14
+ StandardizedResponse,
15
+ StandardizedApiResponse,
16
+ StandardizedPagedResponse,
17
+ StandardizedErrorResponse,
18
+ validateStandardizedResponse,
19
+ isSuccessResponse,
20
+ isPagedResponse,
21
+ isErrorResponse
22
+ } from './types/api-responses';
23
+
24
+
25
+
26
+ // ========================================================================================
27
+ // CLIENT API ERROR TYPES
28
+ // ========================================================================================
29
+
30
+ /**
31
+ * ERROR THROWN WHEN API RESPONSE FORMAT IS INVALID
32
+ * This means the API is NOT following our standardized format
33
+ */
34
+ export class ApiResponseFormatError extends Error {
35
+ constructor(message: string, public readonly endpoint: string, public readonly rawResponse: unknown) {
36
+ super(`API_FORMAT_ERROR: ${message}`);
37
+ this.name = 'ApiResponseFormatError';
38
+ }
39
+ }
40
+
41
+ /**
42
+ * ERROR THROWN WHEN API RETURNS A STANDARDIZED ERROR RESPONSE
43
+ * This is a properly formatted error from the API
44
+ */
45
+ export class ApiBusinessLogicError extends Error {
46
+ constructor(
47
+ public readonly errorCode: string,
48
+ message: string,
49
+ public readonly operation: string,
50
+ public readonly details?: unknown
51
+ ) {
52
+ super(message);
53
+ this.name = 'ApiBusinessLogicError';
54
+ }
55
+ }
56
+
57
+ /**
58
+ * ERROR THROWN WHEN VALIDATION FAILS
59
+ * This is a properly formatted validation error from the API
60
+ */
61
+ export class ApiValidationError extends Error {
62
+ constructor(
63
+ message: string,
64
+ public readonly operation: string,
65
+ public readonly validationErrors: Record<string, string[]>,
66
+ public readonly invalidValue?: unknown,
67
+ public readonly primaryField?: string
68
+ ) {
69
+ super(message);
70
+ this.name = 'ApiValidationError';
71
+ }
72
+ }
73
+
74
+ /**
75
+ * ERROR THROWN WHEN NETWORK/HTTP ISSUES OCCUR
76
+ */
77
+ export class ApiNetworkError extends Error {
78
+ constructor(message: string, public readonly status: number, public readonly endpoint: string) {
79
+ super(`NETWORK_ERROR: ${message}`);
80
+ this.name = 'ApiNetworkError';
81
+ }
82
+ }
83
+
84
+ // ========================================================================================
85
+ // AUTHENTICATION STATE MANAGEMENT
86
+ // ========================================================================================
87
+
88
+ // Coordinate client-side refresh to avoid duplicate refresh calls racing with
89
+ // server-side middleware or other tabs. Only one refresh runs at a time.
90
+ let refreshInFlight: Promise<boolean> | null = null;
91
+
92
+ // Enhanced redirect logic with grace period and retry attempts
93
+ let authRedirectScheduled = false;
94
+ let lastAuthFailureTime = 0;
95
+ let consecutiveAuthFailures = 0;
96
+ const AUTH_FAILURE_GRACE_PERIOD = 2000; // 2 seconds grace period
97
+ const MAX_AUTH_FAILURES_BEFORE_REDIRECT = 2; // Allow 2 failures before redirect
98
+ const AUTH_FAILURE_RESET_WINDOW = 30000; // Reset failure count after 30 seconds
99
+
100
+ // Helper: detect pre-2FA session (session exists, requires 2FA and not completed)
101
+ function isPreTwoFactorSession(session: any | null | undefined): boolean {
102
+ return !!(session?.user?.requiresTwoFactor && !session?.user?.twoFactorSessionVerified);
103
+ }
104
+
105
+ // Reset auth failure state on successful requests
106
+ function resetAuthFailureState() {
107
+ if (consecutiveAuthFailures > 0) {
108
+ console.log(`✅ Resetting auth failure state (was ${consecutiveAuthFailures} failures)`);
109
+ consecutiveAuthFailures = 0;
110
+ lastAuthFailureTime = 0;
111
+ authRedirectScheduled = false;
112
+ }
113
+ }
114
+
115
+ function scheduleLoginRedirect(isImmediate = false) {
116
+ if (authRedirectScheduled) return;
117
+
118
+ const now = Date.now();
119
+
120
+ // Reset consecutive failures if enough time has passed
121
+ if (now - lastAuthFailureTime > AUTH_FAILURE_RESET_WINDOW) {
122
+ consecutiveAuthFailures = 0;
123
+ }
124
+
125
+ consecutiveAuthFailures++;
126
+ lastAuthFailureTime = now;
127
+
128
+ console.warn(`🔴 Auth failure #${consecutiveAuthFailures}, immediate: ${isImmediate}`);
129
+
130
+ // Only redirect if we've had multiple failures or if explicitly requested
131
+ if (!isImmediate && consecutiveAuthFailures < MAX_AUTH_FAILURES_BEFORE_REDIRECT) {
132
+ console.log(`⏳ Delaying redirect - only ${consecutiveAuthFailures} failures so far`);
133
+ return;
134
+ }
135
+
136
+ authRedirectScheduled = true;
137
+
138
+ const redirectFunction = () => {
139
+ try {
140
+ const returnUrl = encodeURIComponent(`${window.location.pathname}${window.location.search}`);
141
+ console.warn(`🔄 Redirecting to login with return URL: ${returnUrl}`);
142
+ window.location.href = `/account-auth/login?returnUrl=${returnUrl}`;
143
+ } catch (error) {
144
+ console.error('❌ Error during login redirect:', error);
145
+ // Final fallback
146
+ try {
147
+ window.location.href = '/account-auth/login';
148
+ } catch {
149
+ // no-op if window is not available
150
+ }
151
+ }
152
+ };
153
+
154
+ if (isImmediate) {
155
+ // Immediate redirect for critical auth failures
156
+ redirectFunction();
157
+ } else {
158
+ // Small delay to allow any pending requests to complete
159
+ console.log(`⏳ Scheduling redirect with ${AUTH_FAILURE_GRACE_PERIOD}ms grace period`);
160
+ setTimeout(redirectFunction, AUTH_FAILURE_GRACE_PERIOD);
161
+ }
162
+ }
163
+
164
+ // ========================================================================================
165
+ // RESULT TYPES FOR CLIENT CONSUMPTION
166
+ // ========================================================================================
167
+
168
+ /**
169
+ * SUCCESSFUL API CALL RESULT
170
+ * This is what gets returned to the calling code for successful operations
171
+ */
172
+ export interface ApiSuccessResult<TData> {
173
+ /** Always true for success */
174
+ success: true;
175
+ /** The actual data - NO NESTING! Direct access! */
176
+ data: TData;
177
+ /** Human-readable success message from API */
178
+ message: string;
179
+ /** Operation code for tracking/debugging */
180
+ operation_code: string;
181
+ /** Server timestamp (if provided) */
182
+ timestamp?: string;
183
+ }
184
+
185
+ /**
186
+ * SUCCESSFUL PAGED API CALL RESULT
187
+ * This is what gets returned for successful paged operations
188
+ */
189
+ export interface ApiPagedResult<TData> {
190
+ /** Always true for success */
191
+ success: true;
192
+ /** The actual data array - NO NESTING! Direct access! */
193
+ items: TData[];
194
+ /** Human-readable success message from API */
195
+ message: string;
196
+ /** Operation code for tracking/debugging */
197
+ operation_code: string;
198
+ /** Pagination information */
199
+ pagination: {
200
+ current_page: number;
201
+ total_pages: number;
202
+ page_size: number;
203
+ total_items: number;
204
+ has_next_page: boolean;
205
+ has_previous_page: boolean;
206
+ };
207
+ /** Server timestamp (if provided) */
208
+ timestamp?: string;
209
+ }
210
+
211
+ /**
212
+ * FAILED API CALL RESULT
213
+ * This is what gets returned to the calling code for failed operations
214
+ */
215
+ export interface ApiErrorResult {
216
+ /** Always false for errors */
217
+ success: false;
218
+ /** Standardized error code */
219
+ error_code: string;
220
+ /** Human-readable error message */
221
+ message: string;
222
+ /** Operation that failed */
223
+ operation: string;
224
+ /** Additional error details (if any) */
225
+ details?: unknown;
226
+ /** Validation errors (if any) */
227
+ validation_errors?: Record<string, string[]>;
228
+ /** Server-provided request identifier for tracing (only set on real errors) */
229
+ request_id?: string;
230
+ }
231
+
232
+ /** UNION TYPE FOR ALL POSSIBLE API RESULTS */
233
+ export type ApiResult<TData> = ApiSuccessResult<TData> | ApiPagedResult<TData> | ApiErrorResult;
234
+
235
+ // ========================================================================================
236
+ // BULLETPROOF CLIENT API SERVICE
237
+ // ========================================================================================
238
+
239
+ class StandardizedClientApiService {
240
+ private baseUrl: string;
241
+
242
+ constructor() {
243
+ this.baseUrl = '';
244
+ }
245
+
246
+ /**
247
+ * MAKES HTTP REQUEST AND VALIDATES RESPONSE FORMAT
248
+ * This method ENFORCES standardized response format compliance
249
+ * Will throw ApiResponseFormatError if format is invalid
250
+ */
251
+ private async makeRequest<TData = unknown>(
252
+ endpoint: string,
253
+ options: RequestInit = {},
254
+ sessionToken?: string
255
+ ): Promise<ApiResult<TData>> {
256
+ const fullEndpoint = `${this.baseUrl}${endpoint}`;
257
+
258
+ try {
259
+ // Use provided token or get from NextAuth session
260
+ const currentSession = await getSession();
261
+ let token = sessionToken || currentSession?.accessToken;
262
+
263
+ // Preflight freshness check: if token is near expiry, coordinate refresh BEFORE making request
264
+ const pre2FA = isPreTwoFactorSession(currentSession);
265
+ const hasRefresh = !!currentSession?.refreshToken;
266
+ if (!pre2FA && hasRefresh) {
267
+ try {
268
+ let timeLeft: number | null = null;
269
+ if (currentSession?.accessTokenExpires) {
270
+ timeLeft = currentSession.accessTokenExpires - Date.now();
271
+ } else if (token) {
272
+ // Fallback decode only if session did not include expiry
273
+ const { jwtDecode } = await import('./jwt-decode-client');
274
+ const decoded: any = jwtDecode(token);
275
+ const expMs = decoded?.exp ? decoded.exp * 1000 : 0;
276
+ timeLeft = expMs - Date.now();
277
+ }
278
+ // Refresh if <= 60s remaining (or already expired)
279
+ if (timeLeft !== null && !Number.isNaN(timeLeft) && timeLeft <= 60000) {
280
+ console.log(`⏳ Access token expiring soon (${Math.floor(timeLeft/1000)}s). Coordinating refresh before request...`);
281
+ if (!refreshInFlight) {
282
+ refreshInFlight = (async () => {
283
+ const reqId = crypto.randomUUID();
284
+ const rr = await fetch('/api/auth/refresh', {
285
+ method: 'POST',
286
+ credentials: 'include',
287
+ headers: { 'X-Request-ID': reqId },
288
+ });
289
+ if (rr.ok || rr.status === 409) {
290
+ // ok or in-progress; give it a beat in case of 409
291
+ if (rr.status === 409) await new Promise(r => setTimeout(r, 1200));
292
+ return true;
293
+ }
294
+ if (rr.status === 401 || rr.status === 403) {
295
+ scheduleLoginRedirect();
296
+ throw new ApiNetworkError('Authentication failed - unable to refresh session', rr.status, endpoint);
297
+ }
298
+ const et = await rr.text();
299
+ throw new ApiNetworkError(et || 'Token refresh failed', rr.status, endpoint);
300
+ })().finally(() => { refreshInFlight = null; });
301
+ }
302
+ await refreshInFlight;
303
+ const newSessionAfter = await getSession();
304
+ token = newSessionAfter?.accessToken || token;
305
+ }
306
+ } catch (preErr) {
307
+ console.warn('Preflight token freshness check failed (continuing to attempt request):', preErr);
308
+ }
309
+ } else {
310
+ // Elegantly skip preflight refresh in pre-2FA window or when no refresh token exists
311
+ if (pre2FA) {
312
+ console.log('⏭️ Skipping preflight refresh: 2FA not complete (no refresh allowed yet)');
313
+ } else if (!hasRefresh) {
314
+ console.log('⏭️ Skipping preflight refresh: no refresh token present');
315
+ }
316
+ }
317
+
318
+ const config: RequestInit = {
319
+ ...options,
320
+ headers: {
321
+ 'Content-Type': 'application/json',
322
+ ...(token && { 'Authorization': `Bearer ${token}` }),
323
+ ...options.headers,
324
+ },
325
+ };
326
+
327
+ console.log(`🔄 API Request: ${options.method || 'GET'} ${fullEndpoint}`);
328
+
329
+ const response = await fetch(fullEndpoint, config);
330
+
331
+ if (!response.ok) {
332
+ // Handle coordination blocking (503) with auto-retry
333
+ if (response.status === 503) {
334
+ console.log('🔄 Got 503 Service Unavailable, attempting auto-retry for coordination...');
335
+
336
+ // Parse Retry-After header (in seconds)
337
+ const retryAfterHeader = response.headers.get('Retry-After');
338
+ let retryAfterSeconds = 1; // Default to 1 second
339
+
340
+ if (retryAfterHeader && /^\d+$/.test(retryAfterHeader)) {
341
+ retryAfterSeconds = parseInt(retryAfterHeader, 10);
342
+ }
343
+
344
+ const baseDelayMs = retryAfterSeconds * 1000;
345
+ const maxRetries = 3;
346
+
347
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
348
+ // Add jitter to prevent thundering herd
349
+ const jitterMs = Math.floor(Math.random() * 300) - 150; // ±150ms jitter
350
+ const exponentialBackoff = Math.pow(1.5, attempt - 1); // Mild exponential backoff
351
+ const delayMs = Math.max(100, baseDelayMs * exponentialBackoff + jitterMs);
352
+
353
+ console.log(`🔄 503 retry attempt ${attempt}/${maxRetries}, waiting ${delayMs}ms...`);
354
+ await new Promise(resolve => setTimeout(resolve, delayMs));
355
+
356
+ try {
357
+ const retryResponse = await fetch(fullEndpoint, config);
358
+
359
+ if (retryResponse.ok) {
360
+ console.log(`✅ 503 retry attempt ${attempt} succeeded`);
361
+ const rawData = await retryResponse.json();
362
+ resetAuthFailureState();
363
+
364
+ // COMPATIBILITY MODE: Handle both formats
365
+ if (rawData && typeof rawData === 'object' && 'success' in rawData) {
366
+ const validatedResponse = validateStandardizedResponse<TData>(rawData, endpoint);
367
+ return this.convertToApiResult(validatedResponse);
368
+ } else {
369
+ // New format - raw data
370
+ const wrappedResponse: ApiSuccessResult<TData> = {
371
+ success: true,
372
+ data: rawData as TData,
373
+ message: 'Success',
374
+ operation_code: 'RAW_RESPONSE',
375
+ timestamp: new Date().toISOString()
376
+ };
377
+ return wrappedResponse;
378
+ }
379
+ }
380
+
381
+ // If we get another 503, continue retrying
382
+ if (retryResponse.status === 503) {
383
+ console.log(`🔄 503 retry attempt ${attempt} got another 503, will retry...`);
384
+ continue;
385
+ }
386
+
387
+ // If we get a different error, break and handle it normally
388
+ console.log(`❌ 503 retry attempt ${attempt} got ${retryResponse.status}, stopping retries`);
389
+ // Fall through to handle the retry response error
390
+ const errorText = await retryResponse.text();
391
+ let errorData;
392
+ try {
393
+ errorData = JSON.parse(errorText);
394
+ if (errorData && typeof errorData === 'object' && 'success' in errorData) {
395
+ // Detect PayEz canonical error envelope and map accordingly
396
+ if (errorData.error && typeof errorData.error === 'object') {
397
+ const reqIdHeader = retryResponse.headers.get('X-Request-ID') || retryResponse.headers.get('X-Correlation-ID');
398
+ const reqIdBody = errorData?.request_id || errorData?.requestId;
399
+ const errorResult: ApiErrorResult = {
400
+ success: false,
401
+ error_code: errorData?.error?.code || errorData?.error_code || errorData?.code || `HTTP_${retryResponse.status}`,
402
+ message: errorData?.error?.message || errorData?.message || `Request failed with status ${retryResponse.status}`,
403
+ operation: endpoint,
404
+ details: (errorData?.error?.details ?? errorData?.details) || undefined,
405
+ ...(reqIdBody || reqIdHeader ? { request_id: (reqIdBody || reqIdHeader) as string } : {})
406
+ };
407
+ return errorResult;
408
+ }
409
+ // Otherwise attempt to validate as our standardized error shape
410
+ const validatedError = validateStandardizedResponse(errorData, endpoint);
411
+ return this.convertToApiResult(validatedError) as ApiResult<TData>;
412
+ } else {
413
+ // New/unknown error format - best-effort mapping
414
+ const reqIdHeader = retryResponse.headers.get('X-Request-ID') || retryResponse.headers.get('X-Correlation-ID');
415
+ const reqIdBody = errorData?.request_id || errorData?.requestId;
416
+ const errorResult: ApiErrorResult = {
417
+ success: false,
418
+ error_code: errorData?.error_code || errorData?.code || `HTTP_${retryResponse.status}`,
419
+ message: errorData?.message || (typeof errorData?.error === 'string' ? errorData.error : errorData?.error?.message) || errorText || `Request failed with status ${retryResponse.status}`,
420
+ operation: endpoint,
421
+ details: (errorData?.error?.details ?? errorData?.details) || undefined,
422
+ ...(reqIdBody || reqIdHeader ? { request_id: (reqIdBody || reqIdHeader) as string } : {})
423
+ };
424
+ return errorResult;
425
+ }
426
+ } catch {
427
+ const reqIdHeader2 = retryResponse.headers.get('X-Request-ID') || retryResponse.headers.get('X-Correlation-ID');
428
+ const errorResult: ApiErrorResult = {
429
+ success: false,
430
+ error_code: `HTTP_${retryResponse.status}`,
431
+ message: errorText || `Request failed with status ${retryResponse.status}`,
432
+ operation: endpoint,
433
+ ...(reqIdHeader2 ? { request_id: reqIdHeader2 } : {})
434
+ };
435
+ return errorResult;
436
+ }
437
+
438
+ } catch (retryError) {
439
+ console.log(`❌ 503 retry attempt ${attempt} failed with network error:`, retryError);
440
+ if (attempt === maxRetries) {
441
+ // If all retries failed with network errors, throw the original error
442
+ throw new ApiNetworkError('Service temporarily unavailable after retries', 503, endpoint);
443
+ }
444
+ continue;
445
+ }
446
+ }
447
+
448
+ // If we got here, all retries failed - fall through to normal error handling
449
+ console.log('❌ All 503 retry attempts exhausted, treating as error');
450
+ }
451
+
452
+ // Handle authentication errors with a single refresh+retry
453
+ if (response.status === 401) {
454
+ console.log('🔑 Got 401, checking if we have a session to refresh...');
455
+
456
+ // CRITICAL FIX: Check if we actually have a session before attempting refresh
457
+ const currentSession = await getSession();
458
+ if (!currentSession || !currentSession.accessToken) {
459
+ console.log('🚫 No valid session found, redirecting to login instead of refresh');
460
+ scheduleLoginRedirect(true); // Immediate redirect
461
+ throw new ApiNetworkError('Authentication required - no valid session', 401, endpoint);
462
+ }
463
+
464
+ // Elegantly gate refresh during pre-2FA or when no refresh token exists
465
+ const pre2FA_now = isPreTwoFactorSession(currentSession);
466
+ const hasRefresh_now = !!currentSession?.refreshToken;
467
+ if (pre2FA_now || !hasRefresh_now) {
468
+ console.log('⏭️ Skipping 401-driven refresh:', {
469
+ reason: pre2FA_now ? 'pre-2FA session' : 'no refresh token',
470
+ requiresTwoFactor: (currentSession?.user as any)?.requiresTwoFactor,
471
+ twoFactorVerified: (currentSession?.user as any)?.twoFactorSessionVerified,
472
+ hasRefreshToken: hasRefresh_now
473
+ });
474
+ // CRITICAL: Redirect to login immediately if refresh is impossible
475
+ console.log('🚫 Cannot refresh session, redirecting to login');
476
+ scheduleLoginRedirect(true); // Immediate redirect
477
+ throw new ApiNetworkError(
478
+ pre2FA_now
479
+ ? 'Two-factor authentication required'
480
+ : 'Session expired - refresh token unavailable',
481
+ 401,
482
+ endpoint
483
+ );
484
+ }
485
+
486
+ console.log('🔑 Valid session found, attempting token refresh...');
487
+
488
+ // Try to refresh the token, but coordinate to avoid double refresh
489
+ if (!refreshInFlight) {
490
+ refreshInFlight = (async () => {
491
+ const reqId = crypto.randomUUID();
492
+ const refreshResponse = await fetch('/api/auth/refresh', {
493
+ method: 'POST',
494
+ credentials: 'include',
495
+ headers: { 'X-Request-ID': reqId },
496
+ });
497
+
498
+ if (refreshResponse.ok) {
499
+ console.log('✅ Token refreshed successfully (client-side coordinator)');
500
+ return true;
501
+ }
502
+
503
+ // If refresh is already in progress server-side, wait briefly and allow retry
504
+ if (refreshResponse.status === 409) {
505
+ console.log('↪️ Refresh in progress server-side (409). Waiting for completion...');
506
+ await new Promise(r => setTimeout(r, 1500));
507
+ return true;
508
+ }
509
+
510
+ // For auth failures, schedule redirect; for others, throw
511
+ if (refreshResponse.status === 401 || refreshResponse.status === 403) {
512
+ scheduleLoginRedirect();
513
+ throw new ApiNetworkError('Authentication failed - unable to refresh session', refreshResponse.status, endpoint);
514
+ }
515
+
516
+ const errorText = await refreshResponse.text();
517
+ throw new ApiNetworkError(errorText || 'Token refresh failed', refreshResponse.status, endpoint);
518
+ })().finally(() => { refreshInFlight = null; });
519
+ }
520
+
521
+ try {
522
+ await refreshInFlight;
523
+ } catch (e) {
524
+ throw e; // bubble up to caller handling
525
+ }
526
+
527
+ console.log('🔁 Retrying original request after coordinated refresh...');
528
+ // Get the new session and retry the original request
529
+ const newSession = await getSession();
530
+ const newToken = newSession?.accessToken;
531
+ const retryConfig: RequestInit = {
532
+ ...options,
533
+ headers: {
534
+ 'Content-Type': 'application/json',
535
+ ...(newToken && { 'Authorization': `Bearer ${newToken}` }),
536
+ ...options.headers,
537
+ },
538
+ };
539
+
540
+ const retryResponse = await fetch(fullEndpoint, retryConfig);
541
+
542
+ if (retryResponse.ok) {
543
+ const rawData = await retryResponse.json();
544
+ // Reset auth failure state on successful retry
545
+ resetAuthFailureState();
546
+
547
+ // COMPATIBILITY MODE: Handle both formats in retry as well
548
+ if (rawData && typeof rawData === 'object' && 'success' in rawData) {
549
+ const validatedResponse = validateStandardizedResponse<TData>(rawData, endpoint);
550
+ return this.convertToApiResult(validatedResponse);
551
+ } else {
552
+ // New format - raw data
553
+ console.log(`🔄 Converting raw retry response to standardized format for ${endpoint}`);
554
+ const wrappedResponse: ApiSuccessResult<TData> = {
555
+ success: true,
556
+ data: rawData as TData,
557
+ message: 'Success',
558
+ operation_code: 'RAW_RESPONSE',
559
+ timestamp: new Date().toISOString()
560
+ };
561
+ return wrappedResponse;
562
+ }
563
+ } else {
564
+ // If retry still 401, schedule redirect
565
+ if (retryResponse.status === 401) {
566
+ scheduleLoginRedirect();
567
+ }
568
+
569
+ const errorText = await retryResponse.text();
570
+ let errorData;
571
+ try {
572
+ errorData = JSON.parse(errorText);
573
+ // Check if it has the success field (old format)
574
+ if (errorData && typeof errorData === 'object' && 'success' in errorData) {
575
+ // Detect PayEz canonical error envelope and map accordingly
576
+ if (errorData.error && typeof errorData.error === 'object') {
577
+ const reqIdHeader = retryResponse.headers.get('X-Request-ID') || retryResponse.headers.get('X-Correlation-ID');
578
+ const reqIdBody = errorData?.request_id || errorData?.requestId;
579
+ const errorResult: ApiErrorResult = {
580
+ success: false,
581
+ error_code: errorData?.error?.code || errorData?.error_code || errorData?.code || `HTTP_${retryResponse.status}`,
582
+ message: errorData?.error?.message || errorData?.message || `Request failed with status ${retryResponse.status}`,
583
+ operation: endpoint,
584
+ details: (errorData?.error?.details ?? errorData?.details) || undefined,
585
+ ...(reqIdBody || reqIdHeader ? { request_id: (reqIdBody || reqIdHeader) as string } : {})
586
+ };
587
+ return errorResult;
588
+ }
589
+ const validatedError = validateStandardizedResponse(errorData, endpoint);
590
+ return this.convertToApiResult(validatedError) as ApiResult<TData>;
591
+ } else {
592
+ // New format - convert raw error to standardized format
593
+ const reqIdHeader = retryResponse.headers.get('X-Request-ID') || retryResponse.headers.get('X-Correlation-ID');
594
+ const reqIdBody = errorData?.request_id || errorData?.requestId;
595
+ const errorResult: ApiErrorResult = {
596
+ success: false,
597
+ error_code: errorData?.error_code || errorData?.code || `HTTP_${retryResponse.status}`,
598
+ message: errorData?.message || (typeof errorData?.error === 'string' ? errorData.error : errorData?.error?.message) || errorText || `Request failed with status ${retryResponse.status}`,
599
+ operation: endpoint,
600
+ details: (errorData?.error?.details ?? errorData?.details) || undefined,
601
+ ...(reqIdBody || reqIdHeader ? { request_id: (reqIdBody || reqIdHeader) as string } : {})
602
+ };
603
+ return errorResult;
604
+ }
605
+ } catch {
606
+ // If we can't parse the error, create a generic error response
607
+ const reqIdHeader = retryResponse.headers.get('X-Request-ID') || retryResponse.headers.get('X-Correlation-ID');
608
+ const errorResult: ApiErrorResult = {
609
+ success: false,
610
+ error_code: `HTTP_${retryResponse.status}`,
611
+ message: errorText || `Request failed with status ${retryResponse.status}`,
612
+ operation: endpoint,
613
+ ...(reqIdHeader ? { request_id: reqIdHeader } : {})
614
+ };
615
+ return errorResult;
616
+ }
617
+ }
618
+ }
619
+
620
+ // Non-401 error: try to parse as standardized error response
621
+ const errorText = await response.text();
622
+ let errorData;
623
+ try {
624
+ errorData = JSON.parse(errorText);
625
+ // Check if it has the success field (old format)
626
+ if (errorData && typeof errorData === 'object' && 'success' in errorData) {
627
+ // Detect PayEz canonical error envelope and map accordingly
628
+ if (errorData.error && typeof errorData.error === 'object') {
629
+ const reqIdHeader = response.headers.get('X-Request-ID') || response.headers.get('X-Correlation-ID');
630
+ const reqIdBody = errorData?.request_id || errorData?.requestId;
631
+ const errorResult: ApiErrorResult = {
632
+ success: false,
633
+ error_code: errorData?.error?.code || errorData?.error_code || errorData?.code || `HTTP_${response.status}`,
634
+ message: errorData?.error?.message || errorData?.message || `Request failed with status ${response.status}`,
635
+ operation: endpoint,
636
+ details: (errorData?.error?.details ?? errorData?.details) || undefined,
637
+ validation_errors: errorData?.validation_errors || undefined,
638
+ ...(reqIdBody || reqIdHeader ? { request_id: (reqIdBody || reqIdHeader) as string } : {})
639
+ };
640
+ return errorResult;
641
+ }
642
+ const validatedError = validateStandardizedResponse(errorData, endpoint);
643
+ return this.convertToApiResult(validatedError) as ApiResult<TData>;
644
+ } else {
645
+ // New format - convert raw error to standardized format
646
+ const reqIdHeader = response.headers.get('X-Request-ID') || response.headers.get('X-Correlation-ID');
647
+ const reqIdBody = errorData?.request_id || errorData?.requestId;
648
+ const errorResult: ApiErrorResult = {
649
+ success: false,
650
+ error_code: errorData?.error_code || errorData?.code || `HTTP_${response.status}`,
651
+ message: errorData?.message || (typeof errorData?.error === 'string' ? errorData.error : errorData?.error?.message) || errorText || `Request failed with status ${response.status}`,
652
+ operation: endpoint,
653
+ details: (errorData?.error?.details ?? errorData?.details) || undefined,
654
+ validation_errors: errorData?.validation_errors || undefined,
655
+ ...(reqIdBody || reqIdHeader ? { request_id: (reqIdBody || reqIdHeader) as string } : {})
656
+ };
657
+ return errorResult;
658
+ }
659
+ } catch (parseError) {
660
+ // If we can't parse the error, create a generic error response
661
+ const reqIdHeader = response.headers.get('X-Request-ID') || response.headers.get('X-Correlation-ID');
662
+ const errorResult: ApiErrorResult = {
663
+ success: false,
664
+ error_code: `HTTP_${response.status}`,
665
+ message: errorText || `Request failed with status ${response.status}`,
666
+ operation: endpoint,
667
+ details: undefined,
668
+ ...(reqIdHeader ? { request_id: reqIdHeader } : {})
669
+ };
670
+ return errorResult;
671
+ }
672
+ }
673
+
674
+ // SUCCESS PATH: Parse and validate response
675
+ const rawData = await response.json();
676
+
677
+ // Reset auth failure state on successful request
678
+ resetAuthFailureState();
679
+
680
+ // COMPATIBILITY MODE: Handle both old envelope format and new raw format
681
+ try {
682
+ // First check if it's the old standardized format with success field
683
+ if (rawData && typeof rawData === 'object' && 'success' in rawData) {
684
+ // Old format - validate as standardized response
685
+ const validatedResponse = validateStandardizedResponse<TData>(rawData, endpoint);
686
+ return this.convertToApiResult(validatedResponse);
687
+ } else {
688
+ // New format - raw data, wrap it in success envelope for compatibility
689
+ console.log(`🔄 Converting raw response to standardized format for ${endpoint}`);
690
+ const wrappedResponse: ApiSuccessResult<TData> = {
691
+ success: true,
692
+ data: rawData as TData,
693
+ message: 'Success',
694
+ operation_code: 'RAW_RESPONSE',
695
+ timestamp: new Date().toISOString()
696
+ };
697
+ return wrappedResponse;
698
+ }
699
+ } catch (validationError) {
700
+ // If response format is invalid, this is a CRITICAL error
701
+ throw new ApiResponseFormatError(
702
+ validationError instanceof Error ? validationError.message : 'Response format validation failed',
703
+ endpoint,
704
+ rawData
705
+ );
706
+ }
707
+
708
+ } catch (error) {
709
+ // Re-throw our custom errors as-is
710
+ if (error instanceof ApiResponseFormatError ||
711
+ error instanceof ApiBusinessLogicError ||
712
+ error instanceof ApiValidationError ||
713
+ error instanceof ApiNetworkError) {
714
+ throw error;
715
+ }
716
+
717
+ // Wrap unknown errors as network errors
718
+ console.error('❌ API request failed:', error);
719
+ throw new ApiNetworkError(
720
+ error instanceof Error ? error.message : 'Network error',
721
+ 0,
722
+ endpoint
723
+ );
724
+ }
725
+ }
726
+
727
+ /**
728
+ * CONVERTS VALIDATED STANDARDIZED RESPONSE TO CLIENT RESULT
729
+ * This normalizes the response for client consumption
730
+ */
731
+ private convertToApiResult<TData>(validatedResponse: StandardizedResponse<any>): ApiResult<TData> {
732
+ if (isSuccessResponse(validatedResponse)) {
733
+ return {
734
+ success: true,
735
+ data: validatedResponse.data as TData,
736
+ message: validatedResponse.message,
737
+ operation_code: validatedResponse.operation_code,
738
+ timestamp: validatedResponse.timestamp
739
+ } as ApiSuccessResult<TData>;
740
+ }
741
+
742
+ if (isPagedResponse(validatedResponse)) {
743
+ return {
744
+ success: true,
745
+ items: validatedResponse.data as TData[],
746
+ message: validatedResponse.message,
747
+ operation_code: validatedResponse.operation_code,
748
+ pagination: validatedResponse.pagination,
749
+ timestamp: validatedResponse.timestamp
750
+ } as ApiPagedResult<TData>;
751
+ }
752
+
753
+ if (isErrorResponse(validatedResponse)) {
754
+ const reqId = (validatedResponse as any)?.request_id || (validatedResponse as any)?.requestId;
755
+ if (validatedResponse.error_code === 'VALIDATION_ERROR') {
756
+ // Handle validation error
757
+ const valError = validatedResponse as any;
758
+ return {
759
+ success: false,
760
+ error_code: validatedResponse.error_code,
761
+ message: validatedResponse.message,
762
+ operation: validatedResponse.operation,
763
+ details: validatedResponse.details,
764
+ validation_errors: valError.payload?.validation_errors,
765
+ ...(reqId ? { request_id: reqId } : {})
766
+ } as ApiErrorResult;
767
+ } else {
768
+ // Handle regular error
769
+ return {
770
+ success: false,
771
+ error_code: validatedResponse.error_code,
772
+ message: validatedResponse.message,
773
+ operation: validatedResponse.operation,
774
+ details: validatedResponse.details,
775
+ ...(reqId ? { request_id: reqId } : {})
776
+ } as ApiErrorResult;
777
+ }
778
+ }
779
+
780
+ // This should never happen due to validation, but TypeScript requires it
781
+ throw new ApiResponseFormatError('Unknown response type after validation', 'unknown', validatedResponse);
782
+ }
783
+
784
+ // ========================================================================================
785
+ // HTTP METHOD WRAPPERS - PUBLIC API
786
+ // ========================================================================================
787
+
788
+ /**
789
+ * GET REQUEST - Returns typed result with direct data access
790
+ */
791
+ async get<TData = unknown>(endpoint: string, sessionToken?: string): Promise<ApiResult<TData>> {
792
+ return this.makeRequest<TData>(endpoint, { method: 'GET' }, sessionToken);
793
+ }
794
+
795
+ /**
796
+ * POST REQUEST - Returns typed result with direct data access
797
+ */
798
+ async post<TData = unknown>(endpoint: string, data?: unknown, sessionToken?: string): Promise<ApiResult<TData>> {
799
+ return this.makeRequest<TData>(endpoint, {
800
+ method: 'POST',
801
+ body: data ? JSON.stringify(data) : undefined,
802
+ }, sessionToken);
803
+ }
804
+
805
+ /**
806
+ * PUT REQUEST - Returns typed result with direct data access
807
+ */
808
+ async put<TData = unknown>(endpoint: string, data?: unknown, sessionToken?: string): Promise<ApiResult<TData>> {
809
+ return this.makeRequest<TData>(endpoint, {
810
+ method: 'PUT',
811
+ body: data ? JSON.stringify(data) : undefined,
812
+ }, sessionToken);
813
+ }
814
+
815
+ /**
816
+ * DELETE REQUEST - Returns typed result with direct data access
817
+ */
818
+ async delete<TData = unknown>(endpoint: string): Promise<ApiResult<TData>> {
819
+ return this.makeRequest<TData>(endpoint, { method: 'DELETE' });
820
+ }
821
+ }
822
+
823
+ // ========================================================================================
824
+ // SINGLETON INSTANCE - READY TO USE
825
+ // ========================================================================================
826
+
827
+ export const standardizedApi = new StandardizedClientApiService();
828
+
829
+ // ========================================================================================
830
+ // CONVENIENCE HELPER FUNCTIONS
831
+ // ========================================================================================
832
+
833
+ /**
834
+ * TYPE-SAFE SUCCESS CHECK
835
+ * Use this to check if API call was successful with proper type narrowing
836
+ */
837
+ export function isApiSuccess<TData>(result: ApiResult<TData>): result is ApiSuccessResult<TData> {
838
+ return result.success === true && 'data' in result;
839
+ }
840
+
841
+ /**
842
+ * TYPE-SAFE PAGED SUCCESS CHECK
843
+ * Use this to check if API call was successful paged response with proper type narrowing
844
+ */
845
+ export function isApiPagedSuccess<TData>(result: ApiResult<TData>): result is ApiPagedResult<TData> {
846
+ return result.success === true && 'items' in result;
847
+ }
848
+
849
+ /**
850
+ * TYPE-SAFE ERROR CHECK
851
+ * Use this to check if API call failed with proper type narrowing
852
+ */
853
+ export function isApiError<TData>(result: ApiResult<TData>): result is ApiErrorResult {
854
+ return result.success === false;
855
+ }
856
+
857
+ /**
858
+ * EXTRACT DATA FROM SUCCESS RESULT
859
+ * Use this to get the data from a successful API call
860
+ * Will throw if result is not successful
861
+ */
862
+ export function extractApiData<TData>(result: ApiResult<TData>): TData {
863
+ if (isApiSuccess(result)) {
864
+ return result.data;
865
+ }
866
+ if (isApiPagedSuccess(result)) {
867
+ return result.items as TData;
868
+ }
869
+ throw new ApiBusinessLogicError(
870
+ result.error_code,
871
+ result.message,
872
+ result.operation,
873
+ result.details
874
+ );
875
+ }
876
+
877
+ /**
878
+ * EXTRACT ITEMS FROM PAGED SUCCESS RESULT
879
+ * Use this to get the items array from a successful paged API call
880
+ * Will throw if result is not successful paged response
881
+ */
882
+ export function extractApiItems<TData>(result: ApiResult<TData>): TData[] {
883
+ if (isApiPagedSuccess(result)) {
884
+ return result.items as TData[];
885
+ }
886
+ if (isApiSuccess(result)) {
887
+ // If it's a regular success but expected paged, data should be array
888
+ return result.data as TData[];
889
+ }
890
+ throw new ApiBusinessLogicError(
891
+ result.error_code,
892
+ result.message,
893
+ result.operation,
894
+ result.details
895
+ );
896
+ }