@payez/next-mvp 4.1.11 → 4.1.12
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.
- package/dist/api-handlers/auth/refresh.js +15 -38
- package/dist/auth/better-auth.js +12 -33
- package/dist/client/better-auth-client.d.ts +14 -2
- package/dist/lib/ensure-fresh-access-token.js +8 -42
- package/dist/lib/session-store.d.ts +0 -1
- package/dist/lib/session-store.js +29 -33
- package/dist/lib/startup-init.js +19 -19
- package/dist/lib/token-lifecycle.d.ts +0 -2
- package/dist/lib/token-lifecycle.js +3 -21
- package/package.json +4 -3
- package/scripts/check-internal-url-usage.sh +73 -73
- package/src/api/index.ts +17 -17
- package/src/api-handlers/account/masked-info.ts +45 -45
- package/src/api-handlers/account/profile.ts +80 -80
- package/src/api-handlers/account/recovery/initiate.ts +23 -23
- package/src/api-handlers/account/recovery/send-code.ts +25 -25
- package/src/api-handlers/account/recovery/verify-code.ts +25 -25
- package/src/api-handlers/account/reset-password.ts +23 -23
- package/src/api-handlers/account/send-code.ts +76 -76
- package/src/api-handlers/account/validate-password.ts +118 -118
- package/src/api-handlers/admin/stats.ts +249 -249
- package/src/api-handlers/anon/preferences.ts +123 -123
- package/src/api-handlers/auth/jwks.ts +20 -20
- package/src/api-handlers/auth/login.ts +240 -240
- package/src/api-handlers/auth/refresh.ts +15 -38
- package/src/api-handlers/auth/validate.ts +43 -43
- package/src/api-handlers/session/viability.ts +142 -142
- package/src/auth/better-auth.ts +450 -467
- package/src/auth/unauthenticated-routes.ts +2 -2
- package/src/client/better-auth-client.ts +70 -70
- package/src/client/fetchWithSession.ts +21 -21
- package/src/client/index.ts +13 -13
- package/src/client/useAnonSession.ts +131 -131
- package/src/components/admin/VibeAdminContext.tsx +87 -87
- package/src/config/logging-config.ts +82 -82
- package/src/config/unauthenticated-routes.ts +19 -19
- package/src/edge/internal-api-url.ts +65 -65
- package/src/edge/middleware.ts +42 -42
- package/src/hooks/useProfile.ts +75 -75
- package/src/lib/anon-session.ts +213 -213
- package/src/lib/auth-secret.ts +137 -137
- package/src/lib/demo-mode.ts +17 -17
- package/src/lib/ensure-fresh-access-token.ts +8 -42
- package/src/lib/idp-client-config.ts +559 -559
- package/src/lib/idp-fetch.ts +126 -126
- package/src/lib/internal-api.ts +171 -171
- package/src/lib/jwt-decode-client.ts +45 -45
- package/src/lib/rate-limit-service.ts +9 -9
- package/src/lib/redis.ts +26 -26
- package/src/lib/refresh-token-validator.ts +64 -64
- package/src/lib/secret-validation.ts +8 -8
- package/src/lib/session-store.ts +692 -695
- package/src/lib/session.ts +34 -34
- package/src/lib/startup-init.ts +251 -251
- package/src/lib/token-expiry.ts +40 -40
- package/src/lib/token-lifecycle.ts +470 -497
- package/src/lib/types/api-responses.ts +336 -336
- package/src/middleware/auth-decision.ts +43 -43
- package/src/models/DecodedAccessToken.ts +16 -16
- package/src/models/SessionModel.ts +264 -264
- package/src/pages/admin-roles/index.ts +9 -9
- package/src/pages/security/EnhancedSecurityPage.tsx +9 -9
- package/src/routes/account/index.ts +31 -31
- package/src/routes/account/masked-info.ts +36 -36
- package/src/routes/account/send-code.ts +39 -39
- package/src/routes/account/verify-email.ts +40 -40
- package/src/routes/account/verify-sms.ts +40 -40
- package/src/routes/auth/index.ts +22 -22
- package/src/routes/auth/refresh.ts +48 -48
- package/src/routes/auth/session.ts +166 -166
- package/src/routes/index.ts +17 -17
- package/src/routes/session/index.ts +7 -7
- package/src/routes/session/refresh-viability.ts +17 -17
- package/src/server/auth.ts +373 -373
- package/src/server/decode-session.ts +202 -202
- package/src/services/signalrActivityService.ts +258 -258
- package/src/theme/ThemeProvider.tsx +39 -39
- package/src/theme/default.ts +33 -33
- package/src/theme/index.ts +31 -31
- package/src/theme/types.ts +69 -69
- package/src/theme/useTheme.ts +57 -57
- package/src/theme/utils.ts +40 -40
- package/src/types/api.ts +12 -12
- package/src/types/auth.d.ts +23 -23
- package/src/types/auth.ts +22 -22
- package/src/types/logging.ts +11 -11
- package/src/types/recovery.ts +54 -54
- package/src/utils/api.ts +353 -353
- package/src/utils/circuitBreaker.ts +40 -40
- package/src/utils/error-message.ts +108 -108
- package/src/utils/layout/reservedSpace.ts +124 -124
package/src/lib/anon-session.ts
CHANGED
|
@@ -1,213 +1,213 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Anonymous Session Store for `@payez/next-mvp`
|
|
3
|
-
*
|
|
4
|
-
* Provides Redis-backed anonymous sessions for tracking user preferences
|
|
5
|
-
* before they log in. When a user logs in, their anonymous session can be
|
|
6
|
-
* merged into their authenticated session.
|
|
7
|
-
*
|
|
8
|
-
* Key features:
|
|
9
|
-
* - Generates a unique visitor ID on first visit
|
|
10
|
-
* - Stores preferences (theme, locale, etc.) in Redis
|
|
11
|
-
* - Tracks usage metrics (resume count for free tier, etc.)
|
|
12
|
-
* - Provides merge functionality when user authenticates
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import redis from './redis';
|
|
16
|
-
import { randomBytes } from 'crypto';
|
|
17
|
-
import { getAnonPrefix, getAnonCookieName } from './app-slug';
|
|
18
|
-
|
|
19
|
-
// Use app-slug prefixes for multi-app isolation
|
|
20
|
-
const getAnonKey = (id: string) => `${getAnonPrefix()}${id}`;
|
|
21
|
-
|
|
22
|
-
const ANON_SESSION_TTL = 90 * 24 * 60 * 60; // 90 days in seconds
|
|
23
|
-
|
|
24
|
-
// Export dynamic cookie name getter for external use
|
|
25
|
-
export const ANON_COOKIE_NAME = getAnonCookieName();
|
|
26
|
-
|
|
27
|
-
export interface AnonSessionPreferences {
|
|
28
|
-
theme?: string;
|
|
29
|
-
locale?: string;
|
|
30
|
-
[key: string]: any;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface AnonSessionMetrics {
|
|
34
|
-
resumeGenerationCount?: number;
|
|
35
|
-
firstVisit?: number;
|
|
36
|
-
lastVisit?: number;
|
|
37
|
-
visitCount?: number;
|
|
38
|
-
[key: string]: any;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface AnonSessionData {
|
|
42
|
-
id: string;
|
|
43
|
-
createdAt: number;
|
|
44
|
-
updatedAt: number;
|
|
45
|
-
preferences: AnonSessionPreferences;
|
|
46
|
-
metrics: AnonSessionMetrics;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Generates a new anonymous session ID
|
|
51
|
-
*/
|
|
52
|
-
export function generateAnonId(): string {
|
|
53
|
-
return randomBytes(16).toString('hex');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// getAnonKey is defined above using app-slug prefix
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Creates a new anonymous session in Redis
|
|
60
|
-
*/
|
|
61
|
-
export async function createAnonSession(anonId?: string): Promise<AnonSessionData> {
|
|
62
|
-
const id = anonId || generateAnonId();
|
|
63
|
-
const now = Date.now();
|
|
64
|
-
|
|
65
|
-
const session: AnonSessionData = {
|
|
66
|
-
id,
|
|
67
|
-
createdAt: now,
|
|
68
|
-
updatedAt: now,
|
|
69
|
-
preferences: {},
|
|
70
|
-
metrics: {
|
|
71
|
-
firstVisit: now,
|
|
72
|
-
lastVisit: now,
|
|
73
|
-
visitCount: 1,
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const key = getAnonKey(id);
|
|
78
|
-
await redis.setex(key, ANON_SESSION_TTL, JSON.stringify(session));
|
|
79
|
-
|
|
80
|
-
return session;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Retrieves an anonymous session from Redis
|
|
85
|
-
*/
|
|
86
|
-
export async function getAnonSession(anonId: string): Promise<AnonSessionData | null> {
|
|
87
|
-
if (!anonId) return null;
|
|
88
|
-
|
|
89
|
-
const key = getAnonKey(anonId);
|
|
90
|
-
const json = await redis.get(key);
|
|
91
|
-
|
|
92
|
-
if (!json) return null;
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
return JSON.parse(json) as AnonSessionData;
|
|
96
|
-
} catch {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Gets or creates an anonymous session
|
|
103
|
-
*/
|
|
104
|
-
export async function getOrCreateAnonSession(anonId?: string): Promise<AnonSessionData> {
|
|
105
|
-
if (anonId) {
|
|
106
|
-
const existing = await getAnonSession(anonId);
|
|
107
|
-
if (existing) {
|
|
108
|
-
// Update last visit
|
|
109
|
-
existing.updatedAt = Date.now();
|
|
110
|
-
existing.metrics.lastVisit = Date.now();
|
|
111
|
-
existing.metrics.visitCount = (existing.metrics.visitCount || 0) + 1;
|
|
112
|
-
await saveAnonSession(existing);
|
|
113
|
-
return existing;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Create new session
|
|
118
|
-
return createAnonSession(anonId);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Saves an anonymous session to Redis
|
|
123
|
-
*/
|
|
124
|
-
export async function saveAnonSession(session: AnonSessionData): Promise<void> {
|
|
125
|
-
session.updatedAt = Date.now();
|
|
126
|
-
const key = getAnonKey(session.id);
|
|
127
|
-
await redis.setex(key, ANON_SESSION_TTL, JSON.stringify(session));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Updates preferences in an anonymous session
|
|
132
|
-
*/
|
|
133
|
-
export async function updateAnonPreferences(
|
|
134
|
-
anonId: string,
|
|
135
|
-
preferences: Partial<AnonSessionPreferences>
|
|
136
|
-
): Promise<AnonSessionData | null> {
|
|
137
|
-
const session = await getAnonSession(anonId);
|
|
138
|
-
if (!session) return null;
|
|
139
|
-
|
|
140
|
-
session.preferences = { ...session.preferences, ...preferences };
|
|
141
|
-
await saveAnonSession(session);
|
|
142
|
-
|
|
143
|
-
return session;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Updates metrics in an anonymous session
|
|
148
|
-
*/
|
|
149
|
-
export async function updateAnonMetrics(
|
|
150
|
-
anonId: string,
|
|
151
|
-
metrics: Partial<AnonSessionMetrics>
|
|
152
|
-
): Promise<AnonSessionData | null> {
|
|
153
|
-
const session = await getAnonSession(anonId);
|
|
154
|
-
if (!session) return null;
|
|
155
|
-
|
|
156
|
-
session.metrics = { ...session.metrics, ...metrics };
|
|
157
|
-
await saveAnonSession(session);
|
|
158
|
-
|
|
159
|
-
return session;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Increments a numeric metric
|
|
164
|
-
*/
|
|
165
|
-
export async function incrementAnonMetric(
|
|
166
|
-
anonId: string,
|
|
167
|
-
metricName: string,
|
|
168
|
-
amount: number = 1
|
|
169
|
-
): Promise<number> {
|
|
170
|
-
const session = await getAnonSession(anonId);
|
|
171
|
-
if (!session) return 0;
|
|
172
|
-
|
|
173
|
-
const currentValue = (session.metrics[metricName] as number) || 0;
|
|
174
|
-
const newValue = currentValue + amount;
|
|
175
|
-
session.metrics[metricName] = newValue;
|
|
176
|
-
await saveAnonSession(session);
|
|
177
|
-
|
|
178
|
-
return newValue;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Deletes an anonymous session
|
|
183
|
-
*/
|
|
184
|
-
export async function deleteAnonSession(anonId: string): Promise<void> {
|
|
185
|
-
if (!anonId) return;
|
|
186
|
-
const key = getAnonKey(anonId);
|
|
187
|
-
await redis.del(key);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Merges anonymous session data into user profile data
|
|
192
|
-
* Call this when a user logs in to preserve their pre-login preferences
|
|
193
|
-
*/
|
|
194
|
-
export async function mergeAnonSessionToUser(
|
|
195
|
-
anonId: string,
|
|
196
|
-
userId: string,
|
|
197
|
-
mergeCallback?: (anonData: AnonSessionData, userId: string) => Promise<void>
|
|
198
|
-
): Promise<AnonSessionData | null> {
|
|
199
|
-
const anonSession = await getAnonSession(anonId);
|
|
200
|
-
if (!anonSession) return null;
|
|
201
|
-
|
|
202
|
-
// If a merge callback is provided, use it to persist data to user profile
|
|
203
|
-
if (mergeCallback) {
|
|
204
|
-
await mergeCallback(anonSession, userId);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Optionally delete the anonymous session after merge
|
|
208
|
-
// await deleteAnonSession(anonId);
|
|
209
|
-
|
|
210
|
-
return anonSession;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// ANON_COOKIE_NAME is already exported at the top of the file
|
|
1
|
+
/**
|
|
2
|
+
* Anonymous Session Store for `@payez/next-mvp`
|
|
3
|
+
*
|
|
4
|
+
* Provides Redis-backed anonymous sessions for tracking user preferences
|
|
5
|
+
* before they log in. When a user logs in, their anonymous session can be
|
|
6
|
+
* merged into their authenticated session.
|
|
7
|
+
*
|
|
8
|
+
* Key features:
|
|
9
|
+
* - Generates a unique visitor ID on first visit
|
|
10
|
+
* - Stores preferences (theme, locale, etc.) in Redis
|
|
11
|
+
* - Tracks usage metrics (resume count for free tier, etc.)
|
|
12
|
+
* - Provides merge functionality when user authenticates
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import redis from './redis';
|
|
16
|
+
import { randomBytes } from 'crypto';
|
|
17
|
+
import { getAnonPrefix, getAnonCookieName } from './app-slug';
|
|
18
|
+
|
|
19
|
+
// Use app-slug prefixes for multi-app isolation
|
|
20
|
+
const getAnonKey = (id: string) => `${getAnonPrefix()}${id}`;
|
|
21
|
+
|
|
22
|
+
const ANON_SESSION_TTL = 90 * 24 * 60 * 60; // 90 days in seconds
|
|
23
|
+
|
|
24
|
+
// Export dynamic cookie name getter for external use
|
|
25
|
+
export const ANON_COOKIE_NAME = getAnonCookieName();
|
|
26
|
+
|
|
27
|
+
export interface AnonSessionPreferences {
|
|
28
|
+
theme?: string;
|
|
29
|
+
locale?: string;
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AnonSessionMetrics {
|
|
34
|
+
resumeGenerationCount?: number;
|
|
35
|
+
firstVisit?: number;
|
|
36
|
+
lastVisit?: number;
|
|
37
|
+
visitCount?: number;
|
|
38
|
+
[key: string]: any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AnonSessionData {
|
|
42
|
+
id: string;
|
|
43
|
+
createdAt: number;
|
|
44
|
+
updatedAt: number;
|
|
45
|
+
preferences: AnonSessionPreferences;
|
|
46
|
+
metrics: AnonSessionMetrics;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generates a new anonymous session ID
|
|
51
|
+
*/
|
|
52
|
+
export function generateAnonId(): string {
|
|
53
|
+
return randomBytes(16).toString('hex');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// getAnonKey is defined above using app-slug prefix
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new anonymous session in Redis
|
|
60
|
+
*/
|
|
61
|
+
export async function createAnonSession(anonId?: string): Promise<AnonSessionData> {
|
|
62
|
+
const id = anonId || generateAnonId();
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
|
|
65
|
+
const session: AnonSessionData = {
|
|
66
|
+
id,
|
|
67
|
+
createdAt: now,
|
|
68
|
+
updatedAt: now,
|
|
69
|
+
preferences: {},
|
|
70
|
+
metrics: {
|
|
71
|
+
firstVisit: now,
|
|
72
|
+
lastVisit: now,
|
|
73
|
+
visitCount: 1,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const key = getAnonKey(id);
|
|
78
|
+
await redis.setex(key, ANON_SESSION_TTL, JSON.stringify(session));
|
|
79
|
+
|
|
80
|
+
return session;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Retrieves an anonymous session from Redis
|
|
85
|
+
*/
|
|
86
|
+
export async function getAnonSession(anonId: string): Promise<AnonSessionData | null> {
|
|
87
|
+
if (!anonId) return null;
|
|
88
|
+
|
|
89
|
+
const key = getAnonKey(anonId);
|
|
90
|
+
const json = await redis.get(key);
|
|
91
|
+
|
|
92
|
+
if (!json) return null;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(json) as AnonSessionData;
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Gets or creates an anonymous session
|
|
103
|
+
*/
|
|
104
|
+
export async function getOrCreateAnonSession(anonId?: string): Promise<AnonSessionData> {
|
|
105
|
+
if (anonId) {
|
|
106
|
+
const existing = await getAnonSession(anonId);
|
|
107
|
+
if (existing) {
|
|
108
|
+
// Update last visit
|
|
109
|
+
existing.updatedAt = Date.now();
|
|
110
|
+
existing.metrics.lastVisit = Date.now();
|
|
111
|
+
existing.metrics.visitCount = (existing.metrics.visitCount || 0) + 1;
|
|
112
|
+
await saveAnonSession(existing);
|
|
113
|
+
return existing;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Create new session
|
|
118
|
+
return createAnonSession(anonId);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Saves an anonymous session to Redis
|
|
123
|
+
*/
|
|
124
|
+
export async function saveAnonSession(session: AnonSessionData): Promise<void> {
|
|
125
|
+
session.updatedAt = Date.now();
|
|
126
|
+
const key = getAnonKey(session.id);
|
|
127
|
+
await redis.setex(key, ANON_SESSION_TTL, JSON.stringify(session));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Updates preferences in an anonymous session
|
|
132
|
+
*/
|
|
133
|
+
export async function updateAnonPreferences(
|
|
134
|
+
anonId: string,
|
|
135
|
+
preferences: Partial<AnonSessionPreferences>
|
|
136
|
+
): Promise<AnonSessionData | null> {
|
|
137
|
+
const session = await getAnonSession(anonId);
|
|
138
|
+
if (!session) return null;
|
|
139
|
+
|
|
140
|
+
session.preferences = { ...session.preferences, ...preferences };
|
|
141
|
+
await saveAnonSession(session);
|
|
142
|
+
|
|
143
|
+
return session;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Updates metrics in an anonymous session
|
|
148
|
+
*/
|
|
149
|
+
export async function updateAnonMetrics(
|
|
150
|
+
anonId: string,
|
|
151
|
+
metrics: Partial<AnonSessionMetrics>
|
|
152
|
+
): Promise<AnonSessionData | null> {
|
|
153
|
+
const session = await getAnonSession(anonId);
|
|
154
|
+
if (!session) return null;
|
|
155
|
+
|
|
156
|
+
session.metrics = { ...session.metrics, ...metrics };
|
|
157
|
+
await saveAnonSession(session);
|
|
158
|
+
|
|
159
|
+
return session;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Increments a numeric metric
|
|
164
|
+
*/
|
|
165
|
+
export async function incrementAnonMetric(
|
|
166
|
+
anonId: string,
|
|
167
|
+
metricName: string,
|
|
168
|
+
amount: number = 1
|
|
169
|
+
): Promise<number> {
|
|
170
|
+
const session = await getAnonSession(anonId);
|
|
171
|
+
if (!session) return 0;
|
|
172
|
+
|
|
173
|
+
const currentValue = (session.metrics[metricName] as number) || 0;
|
|
174
|
+
const newValue = currentValue + amount;
|
|
175
|
+
session.metrics[metricName] = newValue;
|
|
176
|
+
await saveAnonSession(session);
|
|
177
|
+
|
|
178
|
+
return newValue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Deletes an anonymous session
|
|
183
|
+
*/
|
|
184
|
+
export async function deleteAnonSession(anonId: string): Promise<void> {
|
|
185
|
+
if (!anonId) return;
|
|
186
|
+
const key = getAnonKey(anonId);
|
|
187
|
+
await redis.del(key);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Merges anonymous session data into user profile data
|
|
192
|
+
* Call this when a user logs in to preserve their pre-login preferences
|
|
193
|
+
*/
|
|
194
|
+
export async function mergeAnonSessionToUser(
|
|
195
|
+
anonId: string,
|
|
196
|
+
userId: string,
|
|
197
|
+
mergeCallback?: (anonData: AnonSessionData, userId: string) => Promise<void>
|
|
198
|
+
): Promise<AnonSessionData | null> {
|
|
199
|
+
const anonSession = await getAnonSession(anonId);
|
|
200
|
+
if (!anonSession) return null;
|
|
201
|
+
|
|
202
|
+
// If a merge callback is provided, use it to persist data to user profile
|
|
203
|
+
if (mergeCallback) {
|
|
204
|
+
await mergeCallback(anonSession, userId);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Optionally delete the anonymous session after merge
|
|
208
|
+
// await deleteAnonSession(anonId);
|
|
209
|
+
|
|
210
|
+
return anonSession;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ANON_COOKIE_NAME is already exported at the top of the file
|