@payez/next-mvp 4.8.1 → 4.8.2

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 (302) hide show
  1. package/POST-INSTALL.md +408 -408
  2. package/README.md +859 -859
  3. package/dist/components/account/MobileNavDrawer.js +18 -18
  4. package/dist/components/account/UserAvatarMenu.js +1 -1
  5. package/dist/components/admin/AlertSettingsTab.js +40 -40
  6. package/dist/components/admin/LoggingSettingsTab.js +28 -28
  7. package/dist/lib/session-store.js +21 -21
  8. package/dist/lib/startup-init.js +19 -19
  9. package/dist/pages/login/page.js +28 -28
  10. package/dist/pages/settings/EnhancedSettingsPage.js +17 -17
  11. package/dist/vibe/sessions.js +28 -3
  12. package/package.json +2 -2
  13. package/scripts/check-publish-eol.mjs +33 -15
  14. package/src/api/auth-handler.ts +550 -550
  15. package/src/api/index.ts +17 -17
  16. package/src/api-handlers/account/change-password.ts +170 -170
  17. package/src/api-handlers/account/masked-info.ts +46 -46
  18. package/src/api-handlers/account/profile.ts +99 -99
  19. package/src/api-handlers/account/recovery/forward.ts +117 -117
  20. package/src/api-handlers/account/recovery/initiate.ts +59 -59
  21. package/src/api-handlers/account/recovery/send-code.ts +21 -21
  22. package/src/api-handlers/account/recovery/verify-code.ts +20 -20
  23. package/src/api-handlers/account/reset-password.ts +87 -87
  24. package/src/api-handlers/account/send-code.ts +70 -70
  25. package/src/api-handlers/account/update-phone.ts +84 -84
  26. package/src/api-handlers/account/validate-password.ts +139 -139
  27. package/src/api-handlers/account/verify-email.ts +134 -134
  28. package/src/api-handlers/account/verify-sms.ts +134 -134
  29. package/src/api-handlers/admin/analytics.ts +452 -452
  30. package/src/api-handlers/admin/audit.ts +232 -232
  31. package/src/api-handlers/admin/index.ts +63 -63
  32. package/src/api-handlers/admin/redis-sessions.ts +259 -259
  33. package/src/api-handlers/admin/sessions.ts +327 -327
  34. package/src/api-handlers/admin/site-logs.ts +388 -388
  35. package/src/api-handlers/admin/stats.ts +260 -260
  36. package/src/api-handlers/admin/users.ts +251 -251
  37. package/src/api-handlers/admin/vibe-data.ts +333 -333
  38. package/src/api-handlers/anon/preferences.ts +123 -123
  39. package/src/api-handlers/auth/credential-login-email-code.ts +92 -92
  40. package/src/api-handlers/auth/credential-login.ts +948 -948
  41. package/src/api-handlers/auth/jwks.ts +31 -31
  42. package/src/api-handlers/auth/login.ts +252 -252
  43. package/src/api-handlers/auth/passkey-signin.ts +116 -116
  44. package/src/api-handlers/auth/refresh.ts +829 -829
  45. package/src/api-handlers/auth/signout.ts +201 -201
  46. package/src/api-handlers/auth/status.ts +23 -23
  47. package/src/api-handlers/auth/update-session.ts +144 -144
  48. package/src/api-handlers/auth/validate.ts +45 -45
  49. package/src/api-handlers/auth/verify-code.ts +116 -116
  50. package/src/api-handlers/mfa/backup-code-verify.ts +170 -170
  51. package/src/api-handlers/mfa/backup-codes.ts +42 -42
  52. package/src/api-handlers/mfa/methods.ts +47 -47
  53. package/src/api-handlers/mfa/totp-confirm.ts +43 -43
  54. package/src/api-handlers/mfa/totp-enroll.ts +43 -43
  55. package/src/api-handlers/mfa/totp-verify.ts +172 -172
  56. package/src/api-handlers/oauth/resume.ts +182 -182
  57. package/src/api-handlers/onboarding/send-email-code.ts +18 -18
  58. package/src/api-handlers/onboarding/set-password.ts +21 -21
  59. package/src/api-handlers/onboarding/verify-code.ts +20 -20
  60. package/src/api-handlers/onboarding/verify-welcome-email.ts +17 -17
  61. package/src/api-handlers/passkeys/list.ts +32 -32
  62. package/src/api-handlers/passkeys/manage.ts +55 -55
  63. package/src/api-handlers/passkeys/register-options.ts +43 -43
  64. package/src/api-handlers/passkeys/register-verify.ts +42 -42
  65. package/src/api-handlers/session/refresh-viability.ts +56 -56
  66. package/src/api-handlers/session/viability.ts +238 -238
  67. package/src/api-handlers/test/force-expire.ts +167 -167
  68. package/src/auth/auth-decision.ts +292 -292
  69. package/src/auth/better-auth.ts +1688 -1688
  70. package/src/auth/idp-oauth.ts +310 -310
  71. package/src/auth/password-reset-bridge.ts +98 -98
  72. package/src/auth/revocation-fields.ts +16 -16
  73. package/src/auth/route-config.ts +236 -236
  74. package/src/auth/safe-callback.ts +199 -199
  75. package/src/auth/session-cookie-clear.ts +30 -30
  76. package/src/auth/session-reset-link.ts +159 -159
  77. package/src/auth/session-response-sanitizer.ts +151 -151
  78. package/src/auth/types/auth-types.ts +555 -555
  79. package/src/auth/types/index.ts +7 -7
  80. package/src/auth/unauthenticated-routes.ts +2 -2
  81. package/src/auth/utils/idp-client.ts +364 -364
  82. package/src/auth/utils/index.ts +6 -6
  83. package/src/auth/utils/token-utils.ts +243 -243
  84. package/src/client/AuthContext.tsx +192 -192
  85. package/src/client/better-auth-client.ts +70 -70
  86. package/src/client/fetch-with-auth.ts +47 -47
  87. package/src/client/fetchWithSession.ts +21 -21
  88. package/src/client/index.ts +13 -13
  89. package/src/client/useAnonSession.ts +131 -131
  90. package/src/components/SessionSync.tsx +138 -138
  91. package/src/components/SignalRHealthCheck.tsx +131 -131
  92. package/src/components/account/MobileNavDrawer.tsx +305 -305
  93. package/src/components/account/UserAvatarMenu.tsx +277 -277
  94. package/src/components/account/index.ts +13 -13
  95. package/src/components/admin/AlertSettingsTab.tsx +728 -728
  96. package/src/components/admin/AnalyticsTab.tsx +703 -703
  97. package/src/components/admin/DataBrowserTab.tsx +508 -508
  98. package/src/components/admin/LoggingSettingsTab.tsx +665 -665
  99. package/src/components/admin/SessionsTab.tsx +421 -421
  100. package/src/components/admin/StatsTab.tsx +379 -379
  101. package/src/components/admin/VibeAdminContext.tsx +87 -87
  102. package/src/components/admin/VibeAdminLayout.tsx +187 -187
  103. package/src/components/admin/index.ts +59 -59
  104. package/src/components/auth/CredentialSignInForm.tsx +240 -240
  105. package/src/components/auth/FederatedAuthSection.tsx +129 -129
  106. package/src/components/auth/ModeAwareLoginPage.tsx +146 -146
  107. package/src/components/auth/ModeAwareSignupPage.tsx +200 -200
  108. package/src/components/auth/PasswordResetBridge.tsx +30 -30
  109. package/src/components/auth/TraditionalAuthSection.tsx +103 -103
  110. package/src/components/recovery/CompleteStep.tsx +36 -36
  111. package/src/components/recovery/InitiateRecoveryStep.tsx +68 -68
  112. package/src/components/recovery/RequestedStep.tsx +30 -30
  113. package/src/components/recovery/SelectMethodStep.tsx +73 -73
  114. package/src/components/recovery/SetPasswordStep.tsx +118 -118
  115. package/src/components/recovery/VerifyCodeStep.tsx +90 -90
  116. package/src/components/reserved/ReservedRecoveryWarning.tsx +160 -160
  117. package/src/components/reserved/ReservedStatusBox.tsx +118 -118
  118. package/src/components/ui/BetaBadge.tsx +58 -58
  119. package/src/components/ui/Footer.tsx +93 -93
  120. package/src/config/env.ts +55 -55
  121. package/src/config/logger.ts +62 -62
  122. package/src/config/logging-config.ts +82 -82
  123. package/src/config/unauthenticated-routes.ts +19 -19
  124. package/src/config/vibe-log-transport.ts +264 -264
  125. package/src/edge/internal-api-url.ts +65 -65
  126. package/src/edge/middleware.ts +42 -42
  127. package/src/hooks/useAuth.ts +117 -117
  128. package/src/hooks/useAuthSettings.ts +109 -109
  129. package/src/hooks/useAvailableProviders.ts +143 -143
  130. package/src/hooks/usePasswordValidation.ts +127 -127
  131. package/src/hooks/useProfile.ts +82 -82
  132. package/src/hooks/usePublicAuthSettings.ts +171 -171
  133. package/src/hooks/useSessionExpiration.ts +101 -101
  134. package/src/hooks/useViabilitySession.ts +337 -337
  135. package/src/index.ts +64 -64
  136. package/src/lib/anon-session.ts +213 -213
  137. package/src/lib/api-handler.ts +643 -643
  138. package/src/lib/app-slug.ts +228 -228
  139. package/src/lib/authorize-resume.ts +172 -172
  140. package/src/lib/bearer-fetch.ts +31 -31
  141. package/src/lib/client-key.ts +88 -88
  142. package/src/lib/code-step-copy.ts +83 -83
  143. package/src/lib/credential-sign-in-client.ts +101 -101
  144. package/src/lib/demo-mode.ts +18 -18
  145. package/src/lib/device-id.ts +67 -67
  146. package/src/lib/ensure-fresh-access-token.ts +509 -509
  147. package/src/lib/geolocation.ts +265 -265
  148. package/src/lib/idp-client-config.ts +1005 -1005
  149. package/src/lib/idp-fetch.ts +169 -169
  150. package/src/lib/idp-url-guard.ts +37 -37
  151. package/src/lib/internal-api.ts +171 -171
  152. package/src/lib/jwt-decode-client.ts +45 -45
  153. package/src/lib/jwt-decode.ts +82 -82
  154. package/src/lib/lock-window.ts +49 -49
  155. package/src/lib/login-error-message.ts +41 -41
  156. package/src/lib/never-in-response.ts +64 -64
  157. package/src/lib/nextauth-secret.ts +168 -168
  158. package/src/lib/onboarding-proxy.ts +57 -57
  159. package/src/lib/password-rules.ts +50 -50
  160. package/src/lib/rate-limit-service.ts +9 -9
  161. package/src/lib/redis.ts +105 -105
  162. package/src/lib/refresh-token-validator.ts +64 -64
  163. package/src/lib/roles.ts +177 -177
  164. package/src/lib/safe-error-response.ts +149 -149
  165. package/src/lib/secret-validation.ts +8 -8
  166. package/src/lib/session-cookie-shape.ts +185 -185
  167. package/src/lib/session-store.ts +1092 -1092
  168. package/src/lib/session.ts +34 -34
  169. package/src/lib/single-flight.ts +64 -64
  170. package/src/lib/site-logger.ts +245 -245
  171. package/src/lib/standardized-client-api.ts +901 -901
  172. package/src/lib/startup-init.ts +276 -276
  173. package/src/lib/test-aware-get-token.ts +41 -41
  174. package/src/lib/token-expiry.ts +40 -40
  175. package/src/lib/token-lifecycle.ts +546 -546
  176. package/src/lib/token-log-id.ts +69 -69
  177. package/src/lib/twofa-rejection.ts +91 -91
  178. package/src/lib/types/api-responses.ts +336 -336
  179. package/src/lib/user-agent-parser.ts +252 -252
  180. package/src/logging/api/admin-analytics.ts +51 -51
  181. package/src/logging/api/audit-log.ts +53 -53
  182. package/src/logging/components/AdminAnalyticsLayout.tsx +49 -49
  183. package/src/logging/components/AuditLogViewer.tsx +125 -125
  184. package/src/logging/components/ErrorMetricsCard.tsx +98 -98
  185. package/src/logging/components/HealthMetricsCard.tsx +70 -70
  186. package/src/logging/hooks/useAdminAnalytics.ts +22 -22
  187. package/src/logging/hooks/useAuditLog.ts +24 -24
  188. package/src/logging/hooks/useErrorMetrics.ts +40 -40
  189. package/src/logging/hooks/useHealthMetrics.ts +44 -44
  190. package/src/logging/index.ts +18 -18
  191. package/src/logging/types/analytics.ts +81 -81
  192. package/src/logging/types/audit.ts +31 -31
  193. package/src/logging/types/index.ts +3 -3
  194. package/src/middleware/create-middleware.ts +1141 -1141
  195. package/src/middleware/rbac-check.ts +279 -279
  196. package/src/middleware/twofa-presets.ts +224 -224
  197. package/src/models/DecodedAccessToken.ts +16 -16
  198. package/src/models/SessionModel.ts +331 -331
  199. package/src/pages/admin-login/page.tsx +111 -111
  200. package/src/pages/admin-page-permissions/PagePermissionsAdminPage.tsx +527 -527
  201. package/src/pages/admin-page-permissions/index.ts +7 -7
  202. package/src/pages/admin-roles/RolesAdminPage.tsx +533 -533
  203. package/src/pages/admin-roles/index.ts +9 -9
  204. package/src/pages/admin-roles/modals.tsx +469 -469
  205. package/src/pages/client-admin/ClientSiteAdminPage.tsx +382 -382
  206. package/src/pages/client-admin/index.ts +33 -33
  207. package/src/pages/coming-soon/page.tsx +83 -83
  208. package/src/pages/login/page.tsx +338 -338
  209. package/src/pages/onboarding/index.ts +11 -11
  210. package/src/pages/onboarding/page.tsx +212 -212
  211. package/src/pages/profile/EnhancedProfilePage.tsx +514 -514
  212. package/src/pages/profile/index.ts +9 -9
  213. package/src/pages/profile/page.tsx +166 -166
  214. package/src/pages/recovery/page.tsx +267 -267
  215. package/src/pages/roles/MyRolesPage.tsx +211 -211
  216. package/src/pages/roles/components.tsx +294 -294
  217. package/src/pages/roles/index.ts +17 -17
  218. package/src/pages/security/EnhancedSecurityPage.tsx +660 -660
  219. package/src/pages/security/index.ts +12 -12
  220. package/src/pages/security/page.tsx +507 -507
  221. package/src/pages/service-unavailable/page.tsx +63 -63
  222. package/src/pages/settings/EnhancedSettingsPage.tsx +642 -642
  223. package/src/pages/settings/index.ts +9 -9
  224. package/src/pages/settings/page.tsx +47 -47
  225. package/src/pages/showcase/ShowcasePage.tsx +532 -532
  226. package/src/pages/showcase/index.ts +13 -13
  227. package/src/pages/test-env/EmergencyLogoutPage.tsx +192 -192
  228. package/src/pages/test-env/JwtInspectPage.tsx +449 -449
  229. package/src/pages/test-env/RefreshTokenPage.tsx +178 -178
  230. package/src/pages/test-env/TestEnvPage.tsx +130 -130
  231. package/src/pages/test-env/guarded.tsx +81 -81
  232. package/src/pages/test-env/index.ts +77 -77
  233. package/src/pages/test-env/redact-credentials.ts +96 -96
  234. package/src/pages/verify-code/page.tsx +746 -746
  235. package/src/routes/account/index.ts +31 -31
  236. package/src/routes/account/masked-info.ts +36 -36
  237. package/src/routes/account/send-code.ts +39 -39
  238. package/src/routes/account/update-phone.ts +13 -13
  239. package/src/routes/account/verify-email.ts +40 -40
  240. package/src/routes/account/verify-sms.ts +40 -40
  241. package/src/routes/auth/credential-login-email-code.ts +6 -6
  242. package/src/routes/auth/credential-login.ts +6 -6
  243. package/src/routes/auth/index.ts +22 -22
  244. package/src/routes/auth/logout.ts +111 -111
  245. package/src/routes/auth/nextauth.ts +45 -45
  246. package/src/routes/auth/passkey-signin.ts +13 -13
  247. package/src/routes/auth/refresh.ts +77 -77
  248. package/src/routes/auth/session-reset.ts +37 -37
  249. package/src/routes/auth/session.ts +261 -261
  250. package/src/routes/auth/settings.ts +181 -181
  251. package/src/routes/auth/viability.ts +519 -519
  252. package/src/routes/index.ts +17 -17
  253. package/src/routes/mfa/backup-code-verify.ts +1 -1
  254. package/src/routes/mfa/backup-codes.ts +10 -10
  255. package/src/routes/mfa/methods.ts +22 -22
  256. package/src/routes/mfa/totp-confirm.ts +10 -10
  257. package/src/routes/mfa/totp-enroll.ts +10 -10
  258. package/src/routes/mfa/totp-verify.ts +14 -14
  259. package/src/routes/onboarding/index.ts +15 -15
  260. package/src/routes/onboarding/send-email-code.ts +2 -2
  261. package/src/routes/onboarding/set-password.ts +2 -2
  262. package/src/routes/onboarding/verify-code.ts +2 -2
  263. package/src/routes/onboarding/verify-welcome-email.ts +2 -2
  264. package/src/routes/passkeys/list.ts +10 -10
  265. package/src/routes/passkeys/manage.ts +23 -23
  266. package/src/routes/passkeys/register-options.ts +10 -10
  267. package/src/routes/passkeys/register-verify.ts +10 -10
  268. package/src/routes/session/index.ts +7 -7
  269. package/src/routes/session/refresh-viability.ts +17 -17
  270. package/src/server/auth-guard.ts +203 -203
  271. package/src/server/auth.ts +486 -486
  272. package/src/server/decode-session.ts +309 -309
  273. package/src/server/slim-middleware.ts +117 -117
  274. package/src/server/verified-session-token.ts +103 -103
  275. package/src/server/with-auth.ts +93 -93
  276. package/src/services/signalrActivityService.ts +258 -258
  277. package/src/stores/authStore.ts +1900 -1900
  278. package/src/templates/instrumentation.ts +60 -60
  279. package/src/theme/ThemeProvider.tsx +39 -39
  280. package/src/theme/default.ts +33 -33
  281. package/src/theme/index.ts +31 -31
  282. package/src/theme/types.ts +69 -69
  283. package/src/theme/useTheme.ts +57 -57
  284. package/src/theme/utils.ts +40 -40
  285. package/src/types/api.ts +12 -12
  286. package/src/types/auth.d.ts +23 -23
  287. package/src/types/auth.ts +48 -48
  288. package/src/types/logging.ts +11 -11
  289. package/src/types/recovery.ts +54 -54
  290. package/src/utils/api.ts +401 -401
  291. package/src/utils/circuitBreaker.ts +40 -40
  292. package/src/utils/error-message.ts +108 -108
  293. package/src/utils/layout/reservedSpace.ts +124 -124
  294. package/src/utils/logout.ts +30 -30
  295. package/src/vibe/client.ts +877 -877
  296. package/src/vibe/enterprise-auth.ts +208 -208
  297. package/src/vibe/errors.ts +209 -209
  298. package/src/vibe/generic.ts +339 -339
  299. package/src/vibe/hooks/index.ts +367 -367
  300. package/src/vibe/index.ts +133 -133
  301. package/src/vibe/sessions.ts +925 -899
  302. package/src/vibe/types.ts +577 -577
@@ -1,264 +1,264 @@
1
- /**
2
- * Vibe Log Transport for Winston
3
- *
4
- * Buffers log entries to Redis for async processing by Vibe log drain.
5
- * This avoids the "log database errors to database" chicken-and-egg problem.
6
- *
7
- * Features:
8
- * - Async batch writing (doesn't block main thread)
9
- * - Configurable minimum log level
10
- * - Redis buffering with 1-week TTL
11
- * - Graceful degradation on failure
12
- */
13
-
14
- import { getRedis } from '../lib/redis';
15
-
16
- // Dynamic import — winston is a peerDependency and may not be installed.
17
- // We resolve the base class lazily so builds don't break without it.
18
- let TransportBase: any;
19
- try {
20
- TransportBase = require('winston-transport');
21
- } catch {
22
- // Fallback: minimal base class for when winston is not installed
23
- TransportBase = class {
24
- constructor(_opts?: any) {}
25
- emit(_event: string, _info: any) {}
26
- };
27
- }
28
-
29
- /** Redis key for pending log entries */
30
- const REDIS_LOG_KEY = 'vibe:logs:pending';
31
- /** TTL in seconds: 1 week */
32
- const REDIS_LOG_TTL = 7 * 24 * 60 * 60;
33
-
34
- export interface VibeLogTransportOptions {
35
- /** Winston transport level */
36
- level?: string;
37
- /** Redis URL (optional, uses REDIS_URL env var by default) */
38
- redisUrl?: string;
39
- /** Vibe client ID (for log metadata) */
40
- vibeClientId?: string;
41
- /** App slug for identification */
42
- appSlug?: string;
43
- /** Minimum level to send to Vibe (default: 'warn') */
44
- minLevel?: string;
45
- /** Batch size before flush (default: 10) */
46
- batchSize?: number;
47
- /** Flush interval in ms (default: 5000) */
48
- flushInterval?: number;
49
- /** Enable/disable the transport (default: true if Redis available) */
50
- enabled?: boolean;
51
- }
52
-
53
- interface LogEntry {
54
- level: string;
55
- message: string;
56
- timestamp: string;
57
- category?: string;
58
- meta?: Record<string, any>;
59
- }
60
-
61
- const LEVEL_ORDER: Record<string, number> = {
62
- error: 0,
63
- warn: 1,
64
- info: 2,
65
- http: 3,
66
- debug: 4,
67
- };
68
-
69
- /**
70
- * Winston transport that buffers logs to Redis for Vibe drain processing
71
- */
72
- export class VibeLogTransport extends TransportBase {
73
- private vibeClientId: string;
74
- private appSlug: string;
75
- private minLevelNum: number;
76
- private batchSize: number;
77
- private flushInterval: number;
78
- private enabled: boolean;
79
- private redisAvailable: boolean = false;
80
-
81
- private batch: LogEntry[] = [];
82
- private flushTimer: NodeJS.Timeout | null = null;
83
- private isFlushing = false;
84
-
85
- constructor(opts: VibeLogTransportOptions = {}) {
86
- super(opts);
87
-
88
- this.vibeClientId = opts.vibeClientId || process.env.VIBE_CLIENT_ID || '';
89
- this.appSlug = opts.appSlug || process.env.APP_SLUG || process.env.CLIENT_ID || 'mvp-app';
90
- this.minLevelNum = LEVEL_ORDER[opts.minLevel || 'warn'] ?? 1;
91
- this.batchSize = opts.batchSize || 10;
92
- this.flushInterval = opts.flushInterval || 5000;
93
-
94
- // Enable if Redis URL is configured
95
- const redisUrl = opts.redisUrl || process.env.REDIS_URL || '';
96
- this.enabled = opts.enabled !== false && !!redisUrl;
97
- this.redisAvailable = !!redisUrl;
98
-
99
- if (this.enabled) {
100
- this.startFlushTimer();
101
- }
102
- }
103
-
104
- /**
105
- * Winston transport log method
106
- */
107
- log(info: any, callback: () => void): void {
108
- setImmediate(() => this.emit('logged', info));
109
-
110
- if (!this.enabled) {
111
- callback();
112
- return;
113
- }
114
-
115
- const level = info.level?.replace(/\u001b\[\d+m/g, '') || 'info'; // Strip ANSI codes
116
- const levelNum = LEVEL_ORDER[level] ?? 2;
117
-
118
- // Only batch if at or above minimum level
119
- if (levelNum <= this.minLevelNum) {
120
- const entry: LogEntry = {
121
- level,
122
- message: info.message || '',
123
- timestamp: info.timestamp || new Date().toISOString(),
124
- category: this.extractCategory(info.message),
125
- meta: this.extractMeta(info),
126
- };
127
-
128
- this.batch.push(entry);
129
-
130
- // Flush immediately if batch is full or if error/fatal
131
- if (this.batch.length >= this.batchSize || level === 'error') {
132
- this.flush().catch(() => {}); // Ignore flush errors
133
- }
134
- }
135
-
136
- callback();
137
- }
138
-
139
- /**
140
- * Extract category from message prefix like [AUTH], [IDP], etc.
141
- */
142
- private extractCategory(message: string): string {
143
- const match = message.match(/^\[([A-Z-]+)\]/);
144
- return match ? match[1].toLowerCase() : 'app';
145
- }
146
-
147
- /**
148
- * Extract metadata from winston info object
149
- */
150
- private extractMeta(info: any): Record<string, any> {
151
- const { level, message, timestamp, ...rest } = info;
152
- return Object.keys(rest).length > 0 ? rest : {};
153
- }
154
-
155
- /**
156
- * Start the periodic flush timer
157
- */
158
- private startFlushTimer(): void {
159
- if (this.flushTimer) return;
160
-
161
- this.flushTimer = setInterval(() => {
162
- if (this.batch.length > 0) {
163
- this.flush().catch(() => {});
164
- }
165
- }, this.flushInterval);
166
-
167
- // Don't prevent process exit
168
- if (this.flushTimer.unref) {
169
- this.flushTimer.unref();
170
- }
171
- }
172
-
173
- /**
174
- * Flush batch to Vibe
175
- */
176
- async flush(): Promise<void> {
177
- if (this.isFlushing || this.batch.length === 0) return;
178
-
179
- this.isFlushing = true;
180
- const entries = [...this.batch];
181
- this.batch = [];
182
-
183
- try {
184
- await this.sendToVibe(entries);
185
- } catch (err) {
186
- // On failure, put entries back (up to limit to prevent memory issues)
187
- if (this.batch.length < 100) {
188
- this.batch = [...entries, ...this.batch].slice(0, 100);
189
- }
190
- // Log to console as fallback
191
- console.error('[VibeLogTransport] Failed to flush logs:', err);
192
- } finally {
193
- this.isFlushing = false;
194
- }
195
- }
196
-
197
- /**
198
- * Push log entries to Redis for async drain processing
199
- */
200
- private async sendToVibe(entries: LogEntry[]): Promise<void> {
201
- if (entries.length === 0) return;
202
-
203
- const redis = getRedis();
204
-
205
- // Format entries for the drain to process
206
- const logRecords = entries.map(e => JSON.stringify({
207
- level: e.level,
208
- message: e.message,
209
- timestamp: e.timestamp,
210
- source: 'next-mvp',
211
- app_slug: this.appSlug,
212
- vibe_client_id: this.vibeClientId,
213
- category: e.category,
214
- error_code: e.meta?.errorCode || e.meta?.error_code,
215
- stack: e.meta?.stack,
216
- request_id: e.meta?.requestId || e.meta?.request_id,
217
- user_id: e.meta?.userId || e.meta?.user_id,
218
- path: e.meta?.path,
219
- method: e.meta?.method,
220
- status_code: e.meta?.statusCode || e.meta?.status_code,
221
- duration_ms: e.meta?.durationMs || e.meta?.duration_ms,
222
- meta: e.meta,
223
- queued_at: new Date().toISOString(),
224
- }));
225
-
226
- // LPUSH all entries (newest first, drain will RPOP for FIFO)
227
- await redis.lpush(REDIS_LOG_KEY, ...logRecords);
228
-
229
- // Reset TTL on each write (1 week from last activity)
230
- await redis.expire(REDIS_LOG_KEY, REDIS_LOG_TTL);
231
- }
232
-
233
- /**
234
- * Close transport and flush remaining logs
235
- */
236
- close(): void {
237
- if (this.flushTimer) {
238
- clearInterval(this.flushTimer);
239
- this.flushTimer = null;
240
- }
241
-
242
- // Final flush (best effort)
243
- if (this.batch.length > 0) {
244
- this.flush().catch(() => {});
245
- }
246
- }
247
- }
248
-
249
- /**
250
- * Create a configured VibeLogTransport instance
251
- * Returns null if Redis is not configured (REDIS_URL env var)
252
- */
253
- export function createVibeLogTransport(opts?: VibeLogTransportOptions): VibeLogTransport | null {
254
- const transport = new VibeLogTransport(opts);
255
-
256
- // Return null if transport is disabled (no Redis)
257
- if (!transport['enabled']) {
258
- return null;
259
- }
260
-
261
- return transport;
262
- }
263
-
264
- export default VibeLogTransport;
1
+ /**
2
+ * Vibe Log Transport for Winston
3
+ *
4
+ * Buffers log entries to Redis for async processing by Vibe log drain.
5
+ * This avoids the "log database errors to database" chicken-and-egg problem.
6
+ *
7
+ * Features:
8
+ * - Async batch writing (doesn't block main thread)
9
+ * - Configurable minimum log level
10
+ * - Redis buffering with 1-week TTL
11
+ * - Graceful degradation on failure
12
+ */
13
+
14
+ import { getRedis } from '../lib/redis';
15
+
16
+ // Dynamic import — winston is a peerDependency and may not be installed.
17
+ // We resolve the base class lazily so builds don't break without it.
18
+ let TransportBase: any;
19
+ try {
20
+ TransportBase = require('winston-transport');
21
+ } catch {
22
+ // Fallback: minimal base class for when winston is not installed
23
+ TransportBase = class {
24
+ constructor(_opts?: any) {}
25
+ emit(_event: string, _info: any) {}
26
+ };
27
+ }
28
+
29
+ /** Redis key for pending log entries */
30
+ const REDIS_LOG_KEY = 'vibe:logs:pending';
31
+ /** TTL in seconds: 1 week */
32
+ const REDIS_LOG_TTL = 7 * 24 * 60 * 60;
33
+
34
+ export interface VibeLogTransportOptions {
35
+ /** Winston transport level */
36
+ level?: string;
37
+ /** Redis URL (optional, uses REDIS_URL env var by default) */
38
+ redisUrl?: string;
39
+ /** Vibe client ID (for log metadata) */
40
+ vibeClientId?: string;
41
+ /** App slug for identification */
42
+ appSlug?: string;
43
+ /** Minimum level to send to Vibe (default: 'warn') */
44
+ minLevel?: string;
45
+ /** Batch size before flush (default: 10) */
46
+ batchSize?: number;
47
+ /** Flush interval in ms (default: 5000) */
48
+ flushInterval?: number;
49
+ /** Enable/disable the transport (default: true if Redis available) */
50
+ enabled?: boolean;
51
+ }
52
+
53
+ interface LogEntry {
54
+ level: string;
55
+ message: string;
56
+ timestamp: string;
57
+ category?: string;
58
+ meta?: Record<string, any>;
59
+ }
60
+
61
+ const LEVEL_ORDER: Record<string, number> = {
62
+ error: 0,
63
+ warn: 1,
64
+ info: 2,
65
+ http: 3,
66
+ debug: 4,
67
+ };
68
+
69
+ /**
70
+ * Winston transport that buffers logs to Redis for Vibe drain processing
71
+ */
72
+ export class VibeLogTransport extends TransportBase {
73
+ private vibeClientId: string;
74
+ private appSlug: string;
75
+ private minLevelNum: number;
76
+ private batchSize: number;
77
+ private flushInterval: number;
78
+ private enabled: boolean;
79
+ private redisAvailable: boolean = false;
80
+
81
+ private batch: LogEntry[] = [];
82
+ private flushTimer: NodeJS.Timeout | null = null;
83
+ private isFlushing = false;
84
+
85
+ constructor(opts: VibeLogTransportOptions = {}) {
86
+ super(opts);
87
+
88
+ this.vibeClientId = opts.vibeClientId || process.env.VIBE_CLIENT_ID || '';
89
+ this.appSlug = opts.appSlug || process.env.APP_SLUG || process.env.CLIENT_ID || 'mvp-app';
90
+ this.minLevelNum = LEVEL_ORDER[opts.minLevel || 'warn'] ?? 1;
91
+ this.batchSize = opts.batchSize || 10;
92
+ this.flushInterval = opts.flushInterval || 5000;
93
+
94
+ // Enable if Redis URL is configured
95
+ const redisUrl = opts.redisUrl || process.env.REDIS_URL || '';
96
+ this.enabled = opts.enabled !== false && !!redisUrl;
97
+ this.redisAvailable = !!redisUrl;
98
+
99
+ if (this.enabled) {
100
+ this.startFlushTimer();
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Winston transport log method
106
+ */
107
+ log(info: any, callback: () => void): void {
108
+ setImmediate(() => this.emit('logged', info));
109
+
110
+ if (!this.enabled) {
111
+ callback();
112
+ return;
113
+ }
114
+
115
+ const level = info.level?.replace(/\u001b\[\d+m/g, '') || 'info'; // Strip ANSI codes
116
+ const levelNum = LEVEL_ORDER[level] ?? 2;
117
+
118
+ // Only batch if at or above minimum level
119
+ if (levelNum <= this.minLevelNum) {
120
+ const entry: LogEntry = {
121
+ level,
122
+ message: info.message || '',
123
+ timestamp: info.timestamp || new Date().toISOString(),
124
+ category: this.extractCategory(info.message),
125
+ meta: this.extractMeta(info),
126
+ };
127
+
128
+ this.batch.push(entry);
129
+
130
+ // Flush immediately if batch is full or if error/fatal
131
+ if (this.batch.length >= this.batchSize || level === 'error') {
132
+ this.flush().catch(() => {}); // Ignore flush errors
133
+ }
134
+ }
135
+
136
+ callback();
137
+ }
138
+
139
+ /**
140
+ * Extract category from message prefix like [AUTH], [IDP], etc.
141
+ */
142
+ private extractCategory(message: string): string {
143
+ const match = message.match(/^\[([A-Z-]+)\]/);
144
+ return match ? match[1].toLowerCase() : 'app';
145
+ }
146
+
147
+ /**
148
+ * Extract metadata from winston info object
149
+ */
150
+ private extractMeta(info: any): Record<string, any> {
151
+ const { level, message, timestamp, ...rest } = info;
152
+ return Object.keys(rest).length > 0 ? rest : {};
153
+ }
154
+
155
+ /**
156
+ * Start the periodic flush timer
157
+ */
158
+ private startFlushTimer(): void {
159
+ if (this.flushTimer) return;
160
+
161
+ this.flushTimer = setInterval(() => {
162
+ if (this.batch.length > 0) {
163
+ this.flush().catch(() => {});
164
+ }
165
+ }, this.flushInterval);
166
+
167
+ // Don't prevent process exit
168
+ if (this.flushTimer.unref) {
169
+ this.flushTimer.unref();
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Flush batch to Vibe
175
+ */
176
+ async flush(): Promise<void> {
177
+ if (this.isFlushing || this.batch.length === 0) return;
178
+
179
+ this.isFlushing = true;
180
+ const entries = [...this.batch];
181
+ this.batch = [];
182
+
183
+ try {
184
+ await this.sendToVibe(entries);
185
+ } catch (err) {
186
+ // On failure, put entries back (up to limit to prevent memory issues)
187
+ if (this.batch.length < 100) {
188
+ this.batch = [...entries, ...this.batch].slice(0, 100);
189
+ }
190
+ // Log to console as fallback
191
+ console.error('[VibeLogTransport] Failed to flush logs:', err);
192
+ } finally {
193
+ this.isFlushing = false;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Push log entries to Redis for async drain processing
199
+ */
200
+ private async sendToVibe(entries: LogEntry[]): Promise<void> {
201
+ if (entries.length === 0) return;
202
+
203
+ const redis = getRedis();
204
+
205
+ // Format entries for the drain to process
206
+ const logRecords = entries.map(e => JSON.stringify({
207
+ level: e.level,
208
+ message: e.message,
209
+ timestamp: e.timestamp,
210
+ source: 'next-mvp',
211
+ app_slug: this.appSlug,
212
+ vibe_client_id: this.vibeClientId,
213
+ category: e.category,
214
+ error_code: e.meta?.errorCode || e.meta?.error_code,
215
+ stack: e.meta?.stack,
216
+ request_id: e.meta?.requestId || e.meta?.request_id,
217
+ user_id: e.meta?.userId || e.meta?.user_id,
218
+ path: e.meta?.path,
219
+ method: e.meta?.method,
220
+ status_code: e.meta?.statusCode || e.meta?.status_code,
221
+ duration_ms: e.meta?.durationMs || e.meta?.duration_ms,
222
+ meta: e.meta,
223
+ queued_at: new Date().toISOString(),
224
+ }));
225
+
226
+ // LPUSH all entries (newest first, drain will RPOP for FIFO)
227
+ await redis.lpush(REDIS_LOG_KEY, ...logRecords);
228
+
229
+ // Reset TTL on each write (1 week from last activity)
230
+ await redis.expire(REDIS_LOG_KEY, REDIS_LOG_TTL);
231
+ }
232
+
233
+ /**
234
+ * Close transport and flush remaining logs
235
+ */
236
+ close(): void {
237
+ if (this.flushTimer) {
238
+ clearInterval(this.flushTimer);
239
+ this.flushTimer = null;
240
+ }
241
+
242
+ // Final flush (best effort)
243
+ if (this.batch.length > 0) {
244
+ this.flush().catch(() => {});
245
+ }
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Create a configured VibeLogTransport instance
251
+ * Returns null if Redis is not configured (REDIS_URL env var)
252
+ */
253
+ export function createVibeLogTransport(opts?: VibeLogTransportOptions): VibeLogTransport | null {
254
+ const transport = new VibeLogTransport(opts);
255
+
256
+ // Return null if transport is disabled (no Redis)
257
+ if (!transport['enabled']) {
258
+ return null;
259
+ }
260
+
261
+ return transport;
262
+ }
263
+
264
+ export default VibeLogTransport;
@@ -1,65 +1,65 @@
1
- /**
2
- * Internal API URL Utility for Edge Runtime
3
- *
4
- * INTERNAL_API_URL is REQUIRED. This is the URL for THIS application calling ITSELF.
5
- * Used for middleware to call its own API routes (e.g., /api/session/viability).
6
- *
7
- * WHY HTTP IS REQUIRED (not optional):
8
- * - This is the app calling its OWN backend within the same pod/container
9
- * - NextAuth cookies are encrypted based on request protocol
10
- * - TLS is terminated at ingress, so the pod receives HTTP internally
11
- * - Using HTTPS here causes cookie decryption failures
12
- * - This is NOT about "K8s internal traffic doesn't need TLS" - it's about
13
- * protocol consistency for cookie encryption
14
- *
15
- * This is NOT for calling the IDP - use IDP_URL for that.
16
- *
17
- * For local dev, set INTERNAL_API_URL=http://localhost:3000 (or your dev port).
18
- *
19
- * @module edge/internal-api-url
20
- * @version 3.0.0
21
- */
22
-
23
- import { NextRequest } from 'next/server';
24
-
25
- /**
26
- * Get the internal API base URL for middleware to call its own API routes.
27
- *
28
- * THROWS if INTERNAL_API_URL is not set. No fallbacks.
29
- *
30
- * @param request - The Next.js request object (unused, kept for API compatibility)
31
- * @returns Base URL string for constructing internal API calls
32
- * @throws Error if INTERNAL_API_URL environment variable is not set
33
- *
34
- * @example
35
- * ```typescript
36
- * import { getInternalApiUrl } from '@payez/next-mvp/edge';
37
- *
38
- * export async function middleware(request: NextRequest) {
39
- * const baseUrl = getInternalApiUrl(request);
40
- * const apiUrl = new URL('/api/session/check', baseUrl);
41
- *
42
- * const response = await fetch(apiUrl, {
43
- * headers: {
44
- * 'Cookie': request.headers.get('cookie') || ''
45
- * }
46
- * });
47
- * }
48
- * ```
49
- *
50
- * @environment INTERNAL_API_URL - REQUIRED. URL for this app to call ITSELF.
51
- * MUST be HTTP (not HTTPS) - see module docs for why.
52
- * K8s: http://myapp.namespace.svc.cluster.local:80
53
- * Local: http://localhost:3000
54
- */
55
- export function getInternalApiUrl(request: NextRequest): string {
56
- const internalUrl = process.env.INTERNAL_API_URL;
57
- if (!internalUrl) {
58
- throw new Error(
59
- '[INTERNAL_API_URL] FATAL: INTERNAL_API_URL environment variable is REQUIRED. ' +
60
- 'This is for the app to call ITSELF. MUST be HTTP (not HTTPS). ' +
61
- 'Set to http://myapp.namespace.svc.cluster.local:80 (K8s) or http://localhost:3000 (local).'
62
- );
63
- }
64
- return internalUrl;
65
- }
1
+ /**
2
+ * Internal API URL Utility for Edge Runtime
3
+ *
4
+ * INTERNAL_API_URL is REQUIRED. This is the URL for THIS application calling ITSELF.
5
+ * Used for middleware to call its own API routes (e.g., /api/session/viability).
6
+ *
7
+ * WHY HTTP IS REQUIRED (not optional):
8
+ * - This is the app calling its OWN backend within the same pod/container
9
+ * - NextAuth cookies are encrypted based on request protocol
10
+ * - TLS is terminated at ingress, so the pod receives HTTP internally
11
+ * - Using HTTPS here causes cookie decryption failures
12
+ * - This is NOT about "K8s internal traffic doesn't need TLS" - it's about
13
+ * protocol consistency for cookie encryption
14
+ *
15
+ * This is NOT for calling the IDP - use IDP_URL for that.
16
+ *
17
+ * For local dev, set INTERNAL_API_URL=http://localhost:3000 (or your dev port).
18
+ *
19
+ * @module edge/internal-api-url
20
+ * @version 3.0.0
21
+ */
22
+
23
+ import { NextRequest } from 'next/server';
24
+
25
+ /**
26
+ * Get the internal API base URL for middleware to call its own API routes.
27
+ *
28
+ * THROWS if INTERNAL_API_URL is not set. No fallbacks.
29
+ *
30
+ * @param request - The Next.js request object (unused, kept for API compatibility)
31
+ * @returns Base URL string for constructing internal API calls
32
+ * @throws Error if INTERNAL_API_URL environment variable is not set
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * import { getInternalApiUrl } from '@payez/next-mvp/edge';
37
+ *
38
+ * export async function middleware(request: NextRequest) {
39
+ * const baseUrl = getInternalApiUrl(request);
40
+ * const apiUrl = new URL('/api/session/check', baseUrl);
41
+ *
42
+ * const response = await fetch(apiUrl, {
43
+ * headers: {
44
+ * 'Cookie': request.headers.get('cookie') || ''
45
+ * }
46
+ * });
47
+ * }
48
+ * ```
49
+ *
50
+ * @environment INTERNAL_API_URL - REQUIRED. URL for this app to call ITSELF.
51
+ * MUST be HTTP (not HTTPS) - see module docs for why.
52
+ * K8s: http://myapp.namespace.svc.cluster.local:80
53
+ * Local: http://localhost:3000
54
+ */
55
+ export function getInternalApiUrl(request: NextRequest): string {
56
+ const internalUrl = process.env.INTERNAL_API_URL;
57
+ if (!internalUrl) {
58
+ throw new Error(
59
+ '[INTERNAL_API_URL] FATAL: INTERNAL_API_URL environment variable is REQUIRED. ' +
60
+ 'This is for the app to call ITSELF. MUST be HTTP (not HTTPS). ' +
61
+ 'Set to http://myapp.namespace.svc.cluster.local:80 (K8s) or http://localhost:3000 (local).'
62
+ );
63
+ }
64
+ return internalUrl;
65
+ }