@windrun-huaiin/backend-core 30.0.0 → 31.0.1

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 (61) hide show
  1. package/README.md +95 -0
  2. package/dist/app/api/user/anonymous/init/fingerprint-only-route.d.ts +8 -0
  3. package/dist/app/api/user/anonymous/init/fingerprint-only-route.d.ts.map +1 -0
  4. package/dist/app/api/user/anonymous/init/fingerprint-only-route.js +20 -0
  5. package/dist/app/api/user/anonymous/init/fingerprint-only-route.mjs +18 -0
  6. package/dist/app/api/user/anonymous/init/route-shared.d.ts +10 -0
  7. package/dist/app/api/user/anonymous/init/route-shared.d.ts.map +1 -0
  8. package/dist/app/api/user/anonymous/init/route-shared.js +557 -0
  9. package/dist/app/api/user/anonymous/init/route-shared.mjs +555 -0
  10. package/dist/app/api/user/anonymous/init/route.d.ts +3 -3
  11. package/dist/app/api/user/anonymous/init/route.d.ts.map +1 -1
  12. package/dist/app/api/user/anonymous/init/route.js +6 -554
  13. package/dist/app/api/user/anonymous/init/route.mjs +7 -555
  14. package/dist/app/api/webhook/clerk/user/route.js +16 -16
  15. package/dist/app/api/webhook/clerk/user/route.mjs +16 -16
  16. package/dist/auth/auth-utils.d.ts +8 -23
  17. package/dist/auth/auth-utils.d.ts.map +1 -1
  18. package/dist/auth/auth-utils.js +8 -20
  19. package/dist/auth/auth-utils.mjs +8 -20
  20. package/dist/lib/money-price-config.d.ts +28 -28
  21. package/dist/lib/money-price-config.js +31 -31
  22. package/dist/lib/money-price-config.mjs +31 -31
  23. package/dist/lib/stripe-config.js +3 -3
  24. package/dist/lib/stripe-config.mjs +3 -3
  25. package/dist/prisma/prisma-transaction-util.js +1 -1
  26. package/dist/prisma/prisma-transaction-util.mjs +1 -1
  27. package/dist/prisma/prisma.d.ts.map +1 -1
  28. package/dist/prisma/prisma.js +18 -19
  29. package/dist/prisma/prisma.mjs +18 -19
  30. package/dist/services/aggregate/billing.aggregate.service.js +6 -6
  31. package/dist/services/aggregate/billing.aggregate.service.mjs +6 -6
  32. package/dist/services/aggregate/user.aggregate.service.d.ts +9 -9
  33. package/dist/services/aggregate/user.aggregate.service.js +16 -16
  34. package/dist/services/aggregate/user.aggregate.service.mjs +16 -16
  35. package/dist/services/database/constants.js +34 -34
  36. package/dist/services/database/constants.mjs +34 -34
  37. package/dist/services/database/credit.service.js +2 -2
  38. package/dist/services/database/credit.service.mjs +2 -2
  39. package/dist/services/database/transaction.service.js +1 -1
  40. package/dist/services/database/transaction.service.mjs +1 -1
  41. package/dist/services/database/user.service.js +2 -2
  42. package/dist/services/database/user.service.mjs +2 -2
  43. package/dist/services/stripe/webhook-handler.js +5 -5
  44. package/dist/services/stripe/webhook-handler.mjs +5 -5
  45. package/package.json +18 -6
  46. package/src/app/api/user/anonymous/init/fingerprint-only-route.ts +14 -0
  47. package/src/app/api/user/anonymous/init/route-shared.ts +710 -0
  48. package/src/app/api/user/anonymous/init/route.ts +7 -712
  49. package/src/app/api/webhook/clerk/user/route.ts +17 -17
  50. package/src/auth/auth-utils.ts +8 -23
  51. package/src/lib/money-price-config.ts +31 -32
  52. package/src/lib/stripe-config.ts +3 -3
  53. package/src/prisma/prisma-transaction-util.ts +1 -1
  54. package/src/prisma/prisma.ts +18 -19
  55. package/src/services/aggregate/billing.aggregate.service.ts +7 -7
  56. package/src/services/aggregate/user.aggregate.service.ts +16 -16
  57. package/src/services/database/constants.ts +34 -34
  58. package/src/services/database/credit.service.ts +2 -2
  59. package/src/services/database/transaction.service.ts +1 -1
  60. package/src/services/database/user.service.ts +2 -2
  61. package/src/services/stripe/webhook-handler.ts +5 -5
@@ -1,565 +1,17 @@
1
- import { __awaiter, __rest } from 'tslib';
2
- import { anonymousAggregateService } from '../../../../../services/aggregate/anonymous.aggregate.service.mjs';
1
+ import { __awaiter } from 'tslib';
3
2
  import { getOptionalServerAuthIdentity } from '../../../../../auth/auth-utils.mjs';
4
- import { extractFingerprintFromNextRequest } from '@windrun-huaiin/third-ui/fingerprint/server';
5
- import { NextResponse } from 'next/server';
6
- import { fetchUserContextByClerkUserId, fetchLatestUserContextByFingerprintId, mapSubscriptionToXSubscription, mapCreditToXCredit, mapUserToXUser } from '../../../../../services/context/user-context-service.mjs';
7
- import { finalizeUserContext } from '../../../../../services/context/user-context-finalizer.mjs';
3
+ import { handleFingerprintRequest } from './route-shared.mjs';
8
4
 
9
- /* eslint-disable @typescript-eslint/no-explicit-any */
10
- // Fix BigInt serialization issue
11
- BigInt.prototype.toJSON = function () {
12
- return this.toString();
13
- };
14
- // ==================== 工具函数 ====================
15
- /** 创建成功响应对象 */
16
- function createSuccessResponse(params) {
17
- const response = Object.assign({ success: true, xUser: mapUserToXUser(params.entities.user), xCredit: params.entities.credit ? mapCreditToXCredit(params.entities.credit) : null, xSubscription: mapSubscriptionToXSubscription(params.entities.subscription), isNewUser: params.isNewUser }, params.options);
18
- return finalizeUserContext(response);
19
- }
20
- /** 创建错误响应 */
21
- function createErrorResponse(message, status = 400) {
22
- const errorResponse = { error: message };
23
- return NextResponse.json(errorResponse, { status });
24
- }
25
- const SOURCE_REF_MAX_LENGTH = 2048;
26
- const QUERY_PARAM_MAX_LENGTH = 512;
27
- const USER_AGENT_MAX_LENGTH = 1024;
28
- const FIRST_TOUCH_HEADER_MAX_LENGTH = 4096;
29
- const FIRST_TOUCH_HEADER_NAME = 'x-first-touch';
30
- function normalizeSourceRef(ref) {
31
- if (!ref) {
32
- return null;
33
- }
34
- const trimmed = ref.trim();
35
- if (!trimmed) {
36
- return null;
37
- }
38
- return trimmed.length > SOURCE_REF_MAX_LENGTH
39
- ? trimmed.slice(0, SOURCE_REF_MAX_LENGTH)
40
- : trimmed;
41
- }
42
- function normalizeQueryParam(value) {
43
- if (!value) {
44
- return null;
45
- }
46
- const trimmed = value.trim();
47
- if (!trimmed) {
48
- return null;
49
- }
50
- return trimmed.length > QUERY_PARAM_MAX_LENGTH
51
- ? trimmed.slice(0, QUERY_PARAM_MAX_LENGTH)
52
- : trimmed;
53
- }
54
- function decodeHeaderValue(value) {
55
- try {
56
- return decodeURIComponent(value);
57
- }
58
- catch (_a) {
59
- return null;
60
- }
61
- }
62
- function mergeSourceRef(target, source) {
63
- if (!source) {
64
- return;
65
- }
66
- const entries = Object.entries(source);
67
- for (const [key, value] of entries) {
68
- if (value === undefined || value === null) {
69
- continue;
70
- }
71
- if (target[key] === undefined) {
72
- target[key] = value;
73
- }
74
- }
75
- }
76
- function applySearchParams(sourceRef, params) {
77
- const setIfEmpty = (key, value) => {
78
- if (sourceRef[key] !== undefined) {
79
- return;
80
- }
81
- const normalized = normalizeQueryParam(value);
82
- if (normalized) {
83
- sourceRef[key] = normalized;
84
- }
85
- };
86
- setIfEmpty('utmSource', params.get('utm_source'));
87
- setIfEmpty('utmMedium', params.get('utm_medium'));
88
- setIfEmpty('utmCampaign', params.get('utm_campaign'));
89
- setIfEmpty('utmTerm', params.get('utm_term'));
90
- setIfEmpty('utmContent', params.get('utm_content'));
91
- setIfEmpty('utmId', params.get('utm_id'));
92
- setIfEmpty('ref', params.get('ref'));
93
- setIfEmpty('gclid', params.get('gclid'));
94
- setIfEmpty('fbclid', params.get('fbclid'));
95
- setIfEmpty('msclkid', params.get('msclkid'));
96
- setIfEmpty('ttclid', params.get('ttclid'));
97
- setIfEmpty('twclid', params.get('twclid'));
98
- setIfEmpty('liFatId', params.get('li_fat_id'));
99
- }
100
- function normalizeHost(host) {
101
- if (!host) {
102
- return null;
103
- }
104
- return host.trim().toLowerCase() || null;
105
- }
106
- function getRootDomain(host) {
107
- const normalizedHost = normalizeHost(host);
108
- if (!normalizedHost) {
109
- return null;
110
- }
111
- const hostname = normalizedHost.split(':')[0];
112
- if (hostname === 'localhost' || /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) {
113
- return hostname;
114
- }
115
- const parts = hostname.split('.').filter(Boolean);
116
- if (parts.length <= 2) {
117
- return hostname;
118
- }
119
- return parts.slice(-2).join('.');
120
- }
121
- function isInternalReferer(landingHost, refererHost) {
122
- const normalizedLandingHost = normalizeHost(landingHost);
123
- const normalizedRefererHost = normalizeHost(refererHost);
124
- if (!normalizedLandingHost || !normalizedRefererHost) {
125
- return false;
126
- }
127
- if (normalizedLandingHost === normalizedRefererHost) {
128
- return true;
129
- }
130
- return normalizedLandingHost.endsWith(`.${normalizedRefererHost}`)
131
- || normalizedRefererHost.endsWith(`.${normalizedLandingHost}`);
132
- }
133
- function detectPlatform(value) {
134
- const normalized = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();
135
- if (!normalized) {
136
- return null;
137
- }
138
- const matcherList = [
139
- { pattern: /chatgpt|chat-openai|openai/, platform: 'openai', channel: 'ai' },
140
- { pattern: /claude|anthropic/, platform: 'anthropic', channel: 'ai' },
141
- { pattern: /perplexity/, platform: 'perplexity', channel: 'ai' },
142
- { pattern: /gemini/, platform: 'gemini', channel: 'ai' },
143
- { pattern: /copilot/, platform: 'copilot', channel: 'ai' },
144
- { pattern: /google/, platform: 'google', channel: 'search' },
145
- { pattern: /bing/, platform: 'bing', channel: 'search' },
146
- { pattern: /baidu/, platform: 'baidu', channel: 'search' },
147
- { pattern: /yahoo/, platform: 'yahoo', channel: 'search' },
148
- { pattern: /duckduckgo/, platform: 'duckduckgo', channel: 'search' },
149
- { pattern: /facebook/, platform: 'facebook', channel: 'social' },
150
- { pattern: /instagram/, platform: 'instagram', channel: 'social' },
151
- { pattern: /x\.com|twitter/, platform: 'x', channel: 'social' },
152
- { pattern: /linkedin/, platform: 'linkedin', channel: 'social' },
153
- { pattern: /reddit/, platform: 'reddit', channel: 'social' },
154
- { pattern: /youtube/, platform: 'youtube', channel: 'social' },
155
- ];
156
- const matched = matcherList.find(({ pattern }) => pattern.test(normalized));
157
- if (!matched) {
158
- return null;
159
- }
160
- return matched.platform;
161
- }
162
- function detectChannelFromPlatform(platform) {
163
- switch (platform) {
164
- case 'openai':
165
- case 'anthropic':
166
- case 'perplexity':
167
- case 'gemini':
168
- case 'copilot':
169
- return 'ai';
170
- case 'google':
171
- case 'bing':
172
- case 'baidu':
173
- case 'yahoo':
174
- case 'duckduckgo':
175
- return 'search';
176
- case 'facebook':
177
- case 'instagram':
178
- case 'x':
179
- case 'linkedin':
180
- case 'reddit':
181
- case 'youtube':
182
- return 'social';
183
- default:
184
- return null;
185
- }
186
- }
187
- function detectChannelFromUtmMedium(value) {
188
- const normalized = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();
189
- if (!normalized) {
190
- return null;
191
- }
192
- if (/^(cpc|ppc|paid|paid_search|display|banner|affiliate|email|newsletter|push|sms)$/.test(normalized)) {
193
- return 'campaign';
194
- }
195
- if (/^(social|social_paid|social-organic|social_organic)$/.test(normalized)) {
196
- return 'social';
197
- }
198
- if (/^(organic|seo|search)$/.test(normalized)) {
199
- return 'search';
200
- }
201
- if (/^(referral|partner)$/.test(normalized)) {
202
- return 'referral';
203
- }
204
- if (/^(ai|llm)$/.test(normalized)) {
205
- return 'ai';
206
- }
207
- return 'campaign';
208
- }
209
- function parseUserAgent(request) {
210
- var _a, _b, _c, _d, _e;
211
- const userAgentHeader = request.headers.get('user-agent');
212
- const secChUaMobile = (_a = normalizeQueryParam(request.headers.get('sec-ch-ua-mobile'))) !== null && _a !== void 0 ? _a : undefined;
213
- const secChUaPlatform = (_b = normalizeQueryParam(request.headers.get('sec-ch-ua-platform'))) !== null && _b !== void 0 ? _b : undefined;
214
- const userAgent = (_d = (_c = normalizeSourceRef(userAgentHeader)) === null || _c === void 0 ? void 0 : _c.slice(0, USER_AGENT_MAX_LENGTH)) !== null && _d !== void 0 ? _d : undefined;
215
- const ua = (_e = userAgent === null || userAgent === void 0 ? void 0 : userAgent.toLowerCase()) !== null && _e !== void 0 ? _e : '';
216
- let deviceType = 'desktop';
217
- if (!ua) {
218
- deviceType = 'unknown';
219
- }
220
- else if (/bot|spider|crawler|curl|wget|headless/.test(ua)) {
221
- deviceType = 'bot';
222
- }
223
- else if (/ipad|tablet/.test(ua)) {
224
- deviceType = 'tablet';
225
- }
226
- else if (/mobi|iphone|android/.test(ua) || secChUaMobile === '?1') {
227
- deviceType = 'mobile';
228
- }
229
- let os = 'Unknown';
230
- if (/iphone|ipad|ipod/.test(ua)) {
231
- os = 'iOS';
232
- }
233
- else if (/android/.test(ua)) {
234
- os = 'Android';
235
- }
236
- else if (/windows nt/.test(ua)) {
237
- os = 'Windows';
238
- }
239
- else if (/mac os x|macintosh/.test(ua)) {
240
- os = 'macOS';
241
- }
242
- else if (/cros/.test(ua)) {
243
- os = 'Chrome OS';
244
- }
245
- else if (/linux/.test(ua)) {
246
- os = 'Linux';
247
- }
248
- if (secChUaPlatform) {
249
- const normalizedPlatform = secChUaPlatform.replaceAll('"', '');
250
- if (normalizedPlatform && normalizedPlatform !== 'Unknown') {
251
- os = normalizedPlatform;
252
- }
253
- }
254
- let browser = 'Unknown';
255
- if (/edg\//.test(ua)) {
256
- browser = 'Edge';
257
- }
258
- else if (/opr\//.test(ua) || /opera/.test(ua)) {
259
- browser = 'Opera';
260
- }
261
- else if (/samsungbrowser\//.test(ua)) {
262
- browser = 'Samsung Internet';
263
- }
264
- else if (/crios\//.test(ua) || /chrome\//.test(ua)) {
265
- browser = 'Chrome';
266
- }
267
- else if (/firefox\//.test(ua)) {
268
- browser = 'Firefox';
269
- }
270
- else if (/safari\//.test(ua) && !/chrome\//.test(ua) && !/crios\//.test(ua)) {
271
- browser = 'Safari';
272
- }
273
- return {
274
- userAgent,
275
- deviceType,
276
- os,
277
- browser,
278
- secChUaMobile,
279
- secChUaPlatform,
280
- };
281
- }
282
- function parseFirstTouchHeader(request) {
283
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
284
- const rawHeader = request.headers.get(FIRST_TOUCH_HEADER_NAME);
285
- const normalizedHeader = (_a = normalizeSourceRef(rawHeader)) === null || _a === void 0 ? void 0 : _a.slice(0, FIRST_TOUCH_HEADER_MAX_LENGTH);
286
- if (!normalizedHeader) {
287
- return null;
288
- }
289
- const decodedHeader = decodeHeaderValue(normalizedHeader);
290
- if (!decodedHeader) {
291
- return null;
292
- }
293
- try {
294
- const parsed = JSON.parse(decodedHeader);
295
- const sourceRef = {};
296
- sourceRef.capturedAt = (_b = normalizeQueryParam(typeof parsed.capturedAt === 'string' ? parsed.capturedAt : null)) !== null && _b !== void 0 ? _b : undefined;
297
- sourceRef.landingUrl = (_c = normalizeSourceRef(typeof parsed.landingUrl === 'string' ? parsed.landingUrl : null)) !== null && _c !== void 0 ? _c : undefined;
298
- sourceRef.landingPath = (_d = normalizeSourceRef(typeof parsed.landingPath === 'string' ? parsed.landingPath : null)) !== null && _d !== void 0 ? _d : undefined;
299
- sourceRef.landingHost = (_e = normalizeHost(typeof parsed.landingHost === 'string' ? parsed.landingHost : null)) !== null && _e !== void 0 ? _e : undefined;
300
- sourceRef.ref = (_f = normalizeQueryParam(typeof parsed.ref === 'string' ? parsed.ref : null)) !== null && _f !== void 0 ? _f : undefined;
301
- sourceRef.utmSource = (_g = normalizeQueryParam(typeof parsed.utmSource === 'string' ? parsed.utmSource : null)) !== null && _g !== void 0 ? _g : undefined;
302
- sourceRef.utmMedium = (_h = normalizeQueryParam(typeof parsed.utmMedium === 'string' ? parsed.utmMedium : null)) !== null && _h !== void 0 ? _h : undefined;
303
- sourceRef.utmCampaign = (_j = normalizeQueryParam(typeof parsed.utmCampaign === 'string' ? parsed.utmCampaign : null)) !== null && _j !== void 0 ? _j : undefined;
304
- sourceRef.utmTerm = (_k = normalizeQueryParam(typeof parsed.utmTerm === 'string' ? parsed.utmTerm : null)) !== null && _k !== void 0 ? _k : undefined;
305
- sourceRef.utmContent = (_l = normalizeQueryParam(typeof parsed.utmContent === 'string' ? parsed.utmContent : null)) !== null && _l !== void 0 ? _l : undefined;
306
- sourceRef.utmId = (_m = normalizeQueryParam(typeof parsed.utmId === 'string' ? parsed.utmId : null)) !== null && _m !== void 0 ? _m : undefined;
307
- sourceRef.gclid = (_o = normalizeQueryParam(typeof parsed.gclid === 'string' ? parsed.gclid : null)) !== null && _o !== void 0 ? _o : undefined;
308
- sourceRef.fbclid = (_p = normalizeQueryParam(typeof parsed.fbclid === 'string' ? parsed.fbclid : null)) !== null && _p !== void 0 ? _p : undefined;
309
- sourceRef.msclkid = (_q = normalizeQueryParam(typeof parsed.msclkid === 'string' ? parsed.msclkid : null)) !== null && _q !== void 0 ? _q : undefined;
310
- sourceRef.ttclid = (_r = normalizeQueryParam(typeof parsed.ttclid === 'string' ? parsed.ttclid : null)) !== null && _r !== void 0 ? _r : undefined;
311
- sourceRef.twclid = (_s = normalizeQueryParam(typeof parsed.twclid === 'string' ? parsed.twclid : null)) !== null && _s !== void 0 ? _s : undefined;
312
- sourceRef.liFatId = (_t = normalizeQueryParam(typeof parsed.liFatId === 'string' ? parsed.liFatId : null)) !== null && _t !== void 0 ? _t : undefined;
313
- const externalReferrer = normalizeSourceRef(typeof parsed.externalReferrer === 'string' ? parsed.externalReferrer : null);
314
- if (externalReferrer) {
315
- sourceRef.httpRefer = externalReferrer;
316
- try {
317
- const refererUrl = new URL(externalReferrer);
318
- sourceRef.refererHost = (_u = normalizeHost(refererUrl.host)) !== null && _u !== void 0 ? _u : undefined;
319
- sourceRef.refererPath = (_v = normalizeSourceRef(refererUrl.pathname)) !== null && _v !== void 0 ? _v : undefined;
320
- sourceRef.refererDomain = (_w = getRootDomain(refererUrl.host)) !== null && _w !== void 0 ? _w : undefined;
321
- applySearchParams(sourceRef, refererUrl.searchParams);
322
- }
323
- catch (error) {
324
- console.warn('Failed to parse first-touch referrer url:', error);
325
- }
326
- }
327
- return Object.keys(sourceRef).length > 0 ? sourceRef : null;
328
- }
329
- catch (error) {
330
- console.warn('Failed to parse first-touch header:', error);
331
- return null;
332
- }
333
- }
334
- function finalizeAttribution(sourceRef) {
335
- var _a, _b, _c, _d, _e;
336
- const landingHost = normalizeHost(sourceRef.landingHost);
337
- const refererHost = normalizeHost(sourceRef.refererHost);
338
- const internal = isInternalReferer(landingHost, refererHost);
339
- const hasCampaignMarker = Boolean(sourceRef.utmSource
340
- || sourceRef.utmMedium
341
- || sourceRef.utmCampaign
342
- || sourceRef.utmTerm
343
- || sourceRef.utmContent
344
- || sourceRef.utmId
345
- || sourceRef.ref
346
- || sourceRef.gclid
347
- || sourceRef.fbclid
348
- || sourceRef.msclkid
349
- || sourceRef.ttclid
350
- || sourceRef.twclid
351
- || sourceRef.liFatId);
352
- if (internal) {
353
- sourceRef.isInternalReferer = true;
354
- }
355
- const utmPlatform = detectPlatform(sourceRef.utmSource) || detectPlatform(sourceRef.ref);
356
- if (utmPlatform) {
357
- sourceRef.sourcePlatform = utmPlatform;
358
- sourceRef.sourceChannel = (_c = (_b = (_a = detectChannelFromPlatform(utmPlatform)) !== null && _a !== void 0 ? _a : detectChannelFromUtmMedium(sourceRef.utmMedium)) !== null && _b !== void 0 ? _b : sourceRef.sourceChannel) !== null && _c !== void 0 ? _c : 'campaign';
359
- sourceRef.sourceType = 'campaign';
360
- return;
361
- }
362
- if (sourceRef.gclid) {
363
- sourceRef.sourcePlatform = 'google';
364
- sourceRef.sourceChannel = 'search';
365
- sourceRef.sourceType = 'campaign';
366
- return;
367
- }
368
- if (sourceRef.msclkid) {
369
- sourceRef.sourcePlatform = 'bing';
370
- sourceRef.sourceChannel = 'search';
371
- sourceRef.sourceType = 'campaign';
372
- return;
373
- }
374
- if (sourceRef.fbclid) {
375
- sourceRef.sourcePlatform = 'facebook';
376
- sourceRef.sourceChannel = 'social';
377
- sourceRef.sourceType = 'campaign';
378
- return;
379
- }
380
- if (sourceRef.ttclid) {
381
- sourceRef.sourcePlatform = 'tiktok';
382
- sourceRef.sourceChannel = 'social';
383
- sourceRef.sourceType = 'campaign';
384
- return;
385
- }
386
- if (sourceRef.twclid) {
387
- sourceRef.sourcePlatform = 'x';
388
- sourceRef.sourceChannel = 'social';
389
- sourceRef.sourceType = 'campaign';
390
- return;
391
- }
392
- if (sourceRef.liFatId) {
393
- sourceRef.sourcePlatform = 'linkedin';
394
- sourceRef.sourceChannel = 'social';
395
- sourceRef.sourceType = 'campaign';
396
- return;
397
- }
398
- if (hasCampaignMarker) {
399
- sourceRef.sourcePlatform = 'other';
400
- sourceRef.sourceChannel = (_d = detectChannelFromUtmMedium(sourceRef.utmMedium)) !== null && _d !== void 0 ? _d : 'campaign';
401
- sourceRef.sourceType = 'campaign';
402
- return;
403
- }
404
- if (!internal && refererHost) {
405
- const refererPlatform = detectPlatform(refererHost) || detectPlatform(sourceRef.httpRefer);
406
- sourceRef.sourcePlatform = refererPlatform !== null && refererPlatform !== void 0 ? refererPlatform : 'other';
407
- sourceRef.sourceChannel = (_e = detectChannelFromPlatform(refererPlatform)) !== null && _e !== void 0 ? _e : 'referral';
408
- sourceRef.sourceType = 'referer';
409
- return;
410
- }
411
- sourceRef.sourcePlatform = 'direct';
412
- sourceRef.sourceChannel = 'direct';
413
- sourceRef.sourceType = 'direct';
414
- }
415
- // 提取用户首次访问来源
416
- function extractSourceRef(request) {
417
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
418
- const headerRef = request.headers.get('referer') || request.headers.get('referrer');
419
- const customRef = request.headers.get('x-source-ref');
420
- const queryRef = request.nextUrl.searchParams.get('ref');
421
- const firstTouchRef = parseFirstTouchHeader(request);
422
- const sourceRef = Object.assign({}, parseUserAgent(request));
423
- mergeSourceRef(sourceRef, firstTouchRef);
424
- sourceRef.landingUrl = (_b = (_a = sourceRef.landingUrl) !== null && _a !== void 0 ? _a : normalizeSourceRef(request.nextUrl.toString())) !== null && _b !== void 0 ? _b : undefined;
425
- sourceRef.landingPath = (_d = (_c = sourceRef.landingPath) !== null && _c !== void 0 ? _c : normalizeSourceRef(request.nextUrl.pathname)) !== null && _d !== void 0 ? _d : undefined;
426
- sourceRef.landingHost = (_f = (_e = sourceRef.landingHost) !== null && _e !== void 0 ? _e : normalizeHost(request.nextUrl.host)) !== null && _f !== void 0 ? _f : undefined;
427
- sourceRef.ref = (_h = (_g = sourceRef.ref) !== null && _g !== void 0 ? _g : normalizeQueryParam(queryRef)) !== null && _h !== void 0 ? _h : undefined;
428
- let normalizedHttpRef = null;
429
- const candidates = [customRef, headerRef];
430
- for (const candidate of candidates) {
431
- const normalized = normalizeSourceRef(candidate);
432
- if (normalized) {
433
- normalizedHttpRef = normalized;
434
- sourceRef.httpRefer = (_j = sourceRef.httpRefer) !== null && _j !== void 0 ? _j : normalized;
435
- break;
436
- }
437
- }
438
- const searchParams = request.nextUrl.searchParams;
439
- applySearchParams(sourceRef, searchParams);
440
- if (normalizedHttpRef) {
441
- try {
442
- const refererUrl = new URL(normalizedHttpRef);
443
- sourceRef.refererHost = (_l = (_k = sourceRef.refererHost) !== null && _k !== void 0 ? _k : normalizeHost(refererUrl.host)) !== null && _l !== void 0 ? _l : undefined;
444
- sourceRef.refererPath = (_o = (_m = sourceRef.refererPath) !== null && _m !== void 0 ? _m : normalizeSourceRef(refererUrl.pathname)) !== null && _o !== void 0 ? _o : undefined;
445
- sourceRef.refererDomain = (_q = (_p = sourceRef.refererDomain) !== null && _p !== void 0 ? _p : getRootDomain(refererUrl.host)) !== null && _q !== void 0 ? _q : undefined;
446
- applySearchParams(sourceRef, refererUrl.searchParams);
447
- }
448
- catch (error) {
449
- console.warn('Failed to parse referer url for utm/ref:', error);
450
- }
451
- }
452
- finalizeAttribution(sourceRef);
453
- return Object.keys(sourceRef).length > 0 ? sourceRef : null;
454
- }
455
5
  /**
456
- * 根据fingerprint_id查询用户并返回响应数据
457
- */
458
- function getUserByClerkId(clerkUserId) {
459
- return __awaiter(this, void 0, void 0, function* () {
460
- const entities = yield fetchUserContextByClerkUserId(clerkUserId);
461
- if (!entities) {
462
- return null;
463
- }
464
- return createSuccessResponse({
465
- entities,
466
- isNewUser: false,
467
- });
468
- });
469
- }
470
- /**
471
- * 根据fingerprint_id查询用户并返回响应数据
472
- */
473
- function getUserByFingerprintId(fingerprintId) {
474
- return __awaiter(this, void 0, void 0, function* () {
475
- const result = yield fetchLatestUserContextByFingerprintId(fingerprintId);
476
- if (!result) {
477
- return null;
478
- }
479
- const { totalUsersOnDevice, hasAnonymousUser } = result, entities = __rest(result, ["totalUsersOnDevice", "hasAnonymousUser"]);
480
- return createSuccessResponse({
481
- entities,
482
- isNewUser: false,
483
- options: {
484
- totalUsersOnDevice,
485
- hasAnonymousUser,
486
- },
487
- });
488
- });
489
- }
490
- /**
491
- * 通用的fingerprint处理逻辑
492
- */
493
- function handleFingerprintRequest(request_1) {
494
- return __awaiter(this, arguments, void 0, function* (request, options = {}) {
495
- var _a;
496
- // 从请求中提取fingerprint ID
497
- const fingerprintId = extractFingerprintFromNextRequest(request);
498
- // 验证fingerprint ID
499
- if (!fingerprintId) {
500
- return createErrorResponse('Invalid or missing fingerprint ID');
501
- }
502
- console.log('Received fingerprintId:', fingerprintId);
503
- const authIdentity = yield getOptionalServerAuthIdentity();
504
- const clerkUserId = (_a = authIdentity === null || authIdentity === void 0 ? void 0 : authIdentity.providerUserId) !== null && _a !== void 0 ? _a : null;
505
- try {
506
- // 优先根据 Clerk ID 查询(如果已登录)
507
- let existingUserResult = null;
508
- if (clerkUserId) {
509
- // 已登录一律按照clerkUserId去查
510
- existingUserResult = yield getUserByClerkId(clerkUserId);
511
- if (existingUserResult && existingUserResult.xUser.fingerprintId !== fingerprintId) {
512
- // 说明当前用户的指纹ID发生了改变,为什么呢?因为它使用同一账号去注册Clerk,Clerk判定是同一用户!
513
- // 这个时候一定以登录用户clerkUserId为准
514
- // 但是考虑到同一指纹ID本身可以绑定多个账号,所以这里什么都不做
515
- // 就是以当前登录用户去查他自己的数据就行!
516
- console.warn(`Current login user used diff fp_ids: ${clerkUserId}, db_fp_id=${existingUserResult.xUser.fingerprintId}, req_fp_id=${fingerprintId}`);
517
- }
518
- }
519
- else {
520
- // 其次才是检查是否已存在该fingerprint的用户
521
- existingUserResult = yield getUserByFingerprintId(fingerprintId);
522
- }
523
- if (existingUserResult) {
524
- return NextResponse.json(existingUserResult);
525
- }
526
- // 如果不存在用户且不允许创建,返回404
527
- if (!options.createIfNotExists) {
528
- return createErrorResponse('User not found', 404);
529
- }
530
- const sourceRef = extractSourceRef(request);
531
- const anonymousInitResult = yield anonymousAggregateService.getOrCreateByFingerprintId(fingerprintId, { sourceRef: sourceRef !== null && sourceRef !== void 0 ? sourceRef : undefined });
532
- if (anonymousInitResult.isNewUser) {
533
- console.log(`Created new anonymous user ${anonymousInitResult.user.userId} with fingerprint ${fingerprintId}`);
534
- }
535
- // 返回创建结果
536
- const response = createSuccessResponse({
537
- entities: {
538
- user: anonymousInitResult.user,
539
- credit: anonymousInitResult.credit,
540
- subscription: anonymousInitResult.subscription,
541
- },
542
- isNewUser: anonymousInitResult.isNewUser,
543
- options: {
544
- totalUsersOnDevice: anonymousInitResult.totalUsersOnDevice,
545
- hasAnonymousUser: anonymousInitResult.hasAnonymousUser,
546
- },
547
- });
548
- return NextResponse.json(response);
549
- }
550
- catch (error) {
551
- console.error('Fingerprint request error:', error);
552
- return createErrorResponse('Internal server error', 500);
553
- }
554
- });
555
- }
556
- /**
557
- * 匿名用户初始化API
6
+ * Clerk-aware anonymous user initialization API.
558
7
  * POST /api/user/anonymous/init
559
8
  */
560
9
  function POST(request) {
561
10
  return __awaiter(this, void 0, void 0, function* () {
562
- return handleFingerprintRequest(request, { createIfNotExists: true });
11
+ return handleFingerprintRequest(request, {
12
+ createIfNotExists: true,
13
+ getAuthIdentity: getOptionalServerAuthIdentity,
14
+ });
563
15
  });
564
16
  }
565
17
 
@@ -20,10 +20,10 @@ function POST(request) {
20
20
  return tslib.__awaiter(this, void 0, void 0, function* () {
21
21
  var _a, _b, _c;
22
22
  try {
23
- // 获取原始请求体
23
+ // Read the raw request body.
24
24
  const rawBody = yield request.text();
25
25
  let event;
26
- // 开发环境跳过签名校验
26
+ // Skip signature verification in development.
27
27
  if (process.env.NODE_ENV === 'development') {
28
28
  console.log('Development mode: skipping webhook signature verification');
29
29
  try {
@@ -35,22 +35,22 @@ function POST(request) {
35
35
  }
36
36
  }
37
37
  else {
38
- // 生产环境进行签名校验
38
+ // Verify the signature in production.
39
39
  const headerPayload = yield headers.headers();
40
40
  const svix_id = headerPayload.get('svix-id');
41
41
  const svix_timestamp = headerPayload.get('svix-timestamp');
42
42
  const svix_signature = headerPayload.get('svix-signature');
43
- // 如果缺少必要的header,返回错误
43
+ // Reject requests missing required headers.
44
44
  if (!svix_id || !svix_timestamp || !svix_signature) {
45
45
  return server.NextResponse.json({ error: 'Missing webhook headers' }, { status: 400 });
46
46
  }
47
- // 获取webhook signing secret
47
+ // Load the webhook signing secret.
48
48
  const webhookSecret = process.env.CLERK_WEBHOOK_SECRET;
49
49
  if (!webhookSecret) {
50
50
  console.error('CLERK_WEBHOOK_SECRET is not configured');
51
51
  return server.NextResponse.json({ error: 'Webhook configuration error' }, { status: 500 });
52
52
  }
53
- // 验证webhook签名
53
+ // Verify the webhook signature.
54
54
  try {
55
55
  const wh = new svix.Webhook(webhookSecret);
56
56
  event = wh.verify(rawBody, {
@@ -72,7 +72,7 @@ function POST(request) {
72
72
  }, event);
73
73
  let processingResult = { success: true, message: 'Event processed successfully' };
74
74
  try {
75
- // 处理不同的事件类型
75
+ // Dispatch by event type.
76
76
  const { type } = event;
77
77
  switch (type) {
78
78
  case 'user.created':
@@ -108,7 +108,7 @@ function POST(request) {
108
108
  });
109
109
  }
110
110
  /**
111
- * 处理用户创建事件
111
+ * Handle the user.created event.
112
112
  */
113
113
  function handleUserCreated(event) {
114
114
  return tslib.__awaiter(this, void 0, void 0, function* () {
@@ -127,7 +127,7 @@ function handleUserCreated(event) {
127
127
  fingerprintId,
128
128
  userName
129
129
  });
130
- // 检查必要参数
130
+ // Validate required fields.
131
131
  if (!fingerprintId) {
132
132
  console.error('Missing fingerprintId in webhook data, process flow error');
133
133
  return;
@@ -137,16 +137,16 @@ function handleUserCreated(event) {
137
137
  return;
138
138
  }
139
139
  try {
140
- // 按fingerprintId查询该设备的所有未注销过的用户记录,注销过的记录相当于是死数据
140
+ // Find all non-deleted users for this device fingerprint.
141
141
  const existingUsers = yield user_service.userService.findListByFingerprintId(fingerprintId);
142
142
  if (!existingUsers || existingUsers.length === 0) {
143
143
  console.error('Invalid fingerprintId in webhook data, process flow error');
144
144
  return;
145
145
  }
146
- // 查找email相同的记录
146
+ // Find an existing user with the same email.
147
147
  const sameEmailUser = existingUsers.find(user => user.email === email);
148
148
  if (sameEmailUser) {
149
- // 同一账号,检查是否需要更新clerkUserId
149
+ // Same account; update clerkUserId if needed.
150
150
  if (sameEmailUser.clerkUserId !== clerkUserId) {
151
151
  yield user_service.userService.updateUser(sameEmailUser.userId, { clerkUserId, userName: userName, status: constants.UserStatus.REGISTERED });
152
152
  console.log(`Updated clerkUserId for user ${sameEmailUser.userId}`);
@@ -156,15 +156,15 @@ function handleUserCreated(event) {
156
156
  }
157
157
  return;
158
158
  }
159
- // 查找匿名用户(email为空且clerkUserId为空)
159
+ // Find an anonymous user with no email or clerkUserId.
160
160
  const anonymousUser = existingUsers.find(user => !user.email && !user.clerkUserId && user.status === constants.UserStatus.ANONYMOUS);
161
161
  if (anonymousUser) {
162
- // 匿名用户升级
162
+ // Upgrade the anonymous user.
163
163
  yield user_aggregate_service.userAggregateService.upgradeToRegistered(anonymousUser.userId, email, clerkUserId, userName);
164
164
  console.log(`Successfully upgraded anonymous user ${anonymousUser.userId} to registered user`);
165
165
  return;
166
166
  }
167
- // 同设备新账号,创建新用户
167
+ // New account on the same device; create a new user.
168
168
  yield user_aggregate_service.userAggregateService.createNewRegisteredUser(clerkUserId, email, fingerprintId, userName);
169
169
  console.log(`Created new user for device ${fingerprintId} with email ${email}`);
170
170
  }
@@ -175,7 +175,7 @@ function handleUserCreated(event) {
175
175
  });
176
176
  }
177
177
  /**
178
- * 处理用户删除事件
178
+ * Handle the user.deleted event.
179
179
  */
180
180
  function handleUserDeleted(event) {
181
181
  return tslib.__awaiter(this, void 0, void 0, function* () {