thumbgate 1.4.4 → 1.4.6
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +12 -8
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +20 -10
- package/adapters/README.md +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/config/github-about.json +2 -2
- package/package.json +7 -3
- package/public/compare.html +310 -0
- package/public/guide.html +317 -0
- package/public/index.html +1302 -0
- package/public/lessons.html +989 -0
- package/scripts/billing.js +5 -2
- package/scripts/telemetry-analytics.js +41 -1
- package/src/api/server.js +282 -0
package/scripts/billing.js
CHANGED
|
@@ -2068,8 +2068,11 @@ function buildCheckoutSessionPayload({ successUrl, cancelUrl, customerEmail, che
|
|
|
2068
2068
|
packId: pack ? pack.id : null,
|
|
2069
2069
|
credits: pack ? pack.credits : null,
|
|
2070
2070
|
}),
|
|
2071
|
-
// 7-day free trial for subscriptions —
|
|
2072
|
-
...(pack ? {} : {
|
|
2071
|
+
// 7-day free trial for subscriptions — don't require card upfront
|
|
2072
|
+
...(pack ? {} : {
|
|
2073
|
+
subscription_data: { trial_period_days: 7 },
|
|
2074
|
+
payment_method_collection: 'if_required',
|
|
2075
|
+
}),
|
|
2073
2076
|
};
|
|
2074
2077
|
|
|
2075
2078
|
const normalizedCustomerEmail = normalizeText(customerEmail);
|
|
@@ -15,6 +15,21 @@ const {
|
|
|
15
15
|
} = require('./feedback-paths');
|
|
16
16
|
|
|
17
17
|
const TELEMETRY_FILE_NAME = 'telemetry-pings.jsonl';
|
|
18
|
+
const MARKETING_CLICK_EVENT_TYPES = new Set([
|
|
19
|
+
'cta_click',
|
|
20
|
+
'checkout_start',
|
|
21
|
+
'checkout_bootstrap',
|
|
22
|
+
'chatgpt_gpt_open',
|
|
23
|
+
'chatgpt_gpt_click',
|
|
24
|
+
'install_guide_click',
|
|
25
|
+
'install_click',
|
|
26
|
+
'pro_page_click',
|
|
27
|
+
'pro_checkout_start',
|
|
28
|
+
'workflow_sprint_intake_click',
|
|
29
|
+
'demo_click',
|
|
30
|
+
'github_repo_click',
|
|
31
|
+
'community_landing_redirect',
|
|
32
|
+
]);
|
|
18
33
|
|
|
19
34
|
function shouldIncludeLegacyTelemetry() {
|
|
20
35
|
if (
|
|
@@ -64,6 +79,10 @@ function safeRate(num, den) {
|
|
|
64
79
|
return Number((num / den).toFixed(4));
|
|
65
80
|
}
|
|
66
81
|
|
|
82
|
+
function isMarketingClickEvent(eventType) {
|
|
83
|
+
return MARKETING_CLICK_EVENT_TYPES.has(String(eventType || '').toLowerCase());
|
|
84
|
+
}
|
|
85
|
+
|
|
67
86
|
function getTelemetryPath(feedbackDir) {
|
|
68
87
|
return path.join(feedbackDir, TELEMETRY_FILE_NAME);
|
|
69
88
|
}
|
|
@@ -288,6 +307,8 @@ function sanitizeTelemetryPayload(payload = {}, headers = {}) {
|
|
|
288
307
|
ctaId: pickFirstText(raw.ctaId),
|
|
289
308
|
ctaPlacement: pickFirstText(raw.ctaPlacement),
|
|
290
309
|
planId: pickFirstText(raw.planId),
|
|
310
|
+
linkSlug: pickFirstText(raw.linkSlug, raw.destinationSlug),
|
|
311
|
+
destinationPath: pickFirstText(raw.destinationPath),
|
|
291
312
|
pipelineStatus: pickFirstText(raw.pipelineStatus, raw.workflowSprintStatus, raw.status),
|
|
292
313
|
reasonCode,
|
|
293
314
|
reasonDetail: pickFirstText(raw.reasonDetail, raw.reasonText, raw.otherReason, raw.notes),
|
|
@@ -365,6 +386,7 @@ function summarizeRecentEvents(events) {
|
|
|
365
386
|
utmCampaign: entry.utmCampaign || null,
|
|
366
387
|
ctaId: entry.ctaId || null,
|
|
367
388
|
page: entry.page || null,
|
|
389
|
+
linkSlug: entry.linkSlug || null,
|
|
368
390
|
reasonCode: entry.reasonCode || null,
|
|
369
391
|
creator: entry.creator || null,
|
|
370
392
|
community: entry.community || null,
|
|
@@ -474,7 +496,7 @@ function getTelemetrySummary(feedbackDir, options = {}) {
|
|
|
474
496
|
if (entry.attributionTagged) attributedPageViews += 1;
|
|
475
497
|
}
|
|
476
498
|
|
|
477
|
-
if ((entry.eventType || entry.event)
|
|
499
|
+
if (isMarketingClickEvent(entry.eventType || entry.event)) {
|
|
478
500
|
ctaClicks += 1;
|
|
479
501
|
incrementCounter(ctaClicksBySource, entry.source);
|
|
480
502
|
incrementCounter(ctaClicksByCampaign, entry.utmCampaign);
|
|
@@ -601,6 +623,10 @@ function getTelemetrySummary(feedbackDir, options = {}) {
|
|
|
601
623
|
pageViewsByTrafficChannel[channelKey] || 0
|
|
602
624
|
);
|
|
603
625
|
}
|
|
626
|
+
const installCopies = byEventType.install_copy || 0;
|
|
627
|
+
const gptOpens = (byEventType.chatgpt_gpt_open || 0) + (byEventType.chatgpt_gpt_click || 0);
|
|
628
|
+
const trialEmails = byEventType.trial_email_captured || 0;
|
|
629
|
+
const proConversions = checkoutPaidConfirmations;
|
|
604
630
|
|
|
605
631
|
return {
|
|
606
632
|
window: serializeAnalyticsWindow(analyticsWindow),
|
|
@@ -608,6 +634,19 @@ function getTelemetrySummary(feedbackDir, options = {}) {
|
|
|
608
634
|
latestSeenAt,
|
|
609
635
|
byClientType,
|
|
610
636
|
byEventType,
|
|
637
|
+
conversionFunnel: {
|
|
638
|
+
landingViews: pageViews,
|
|
639
|
+
installCopies,
|
|
640
|
+
gptOpens,
|
|
641
|
+
checkoutStarts,
|
|
642
|
+
trialEmails,
|
|
643
|
+
proConversions,
|
|
644
|
+
landingToInstallCopyRate: safeRate(installCopies, pageViews),
|
|
645
|
+
landingToGptOpenRate: safeRate(gptOpens, pageViews),
|
|
646
|
+
landingToCheckoutRate: safeRate(checkoutStarts, pageViews),
|
|
647
|
+
checkoutToTrialEmailRate: safeRate(trialEmails, checkoutStarts),
|
|
648
|
+
checkoutToProConversionRate: safeRate(proConversions, checkoutStarts),
|
|
649
|
+
},
|
|
611
650
|
web: {
|
|
612
651
|
totalEvents: webEvents,
|
|
613
652
|
uniqueVisitors: webVisitors.size,
|
|
@@ -712,6 +751,7 @@ function getTelemetryAnalytics(feedbackDir, options = {}) {
|
|
|
712
751
|
latestSeenAt: summary.latestSeenAt,
|
|
713
752
|
byClientType: summary.byClientType,
|
|
714
753
|
byEventType: summary.byEventType,
|
|
754
|
+
conversionFunnel: summary.conversionFunnel,
|
|
715
755
|
visitors: {
|
|
716
756
|
totalEvents: summary.web.totalEvents,
|
|
717
757
|
uniqueVisitors: summary.web.uniqueVisitors,
|
package/src/api/server.js
CHANGED
|
@@ -227,6 +227,123 @@ const BUILD_METADATA = resolveBuildMetadata();
|
|
|
227
227
|
const TERMINAL_JOB_STATUSES = new Set(['completed', 'failed', 'cancelled']);
|
|
228
228
|
const IDLE_JOB_STATUSES = new Set(['queued', 'paused', 'resume_requested']);
|
|
229
229
|
const JOB_CONTROL_ACTIONS = new Set(['pause', 'cancel', 'resume']);
|
|
230
|
+
const TRACKED_LINK_QUERY_KEYS = [
|
|
231
|
+
'utm_source',
|
|
232
|
+
'utm_medium',
|
|
233
|
+
'utm_campaign',
|
|
234
|
+
'utm_content',
|
|
235
|
+
'utm_term',
|
|
236
|
+
'source',
|
|
237
|
+
'creator',
|
|
238
|
+
'creator_handle',
|
|
239
|
+
'community',
|
|
240
|
+
'subreddit',
|
|
241
|
+
'post_id',
|
|
242
|
+
'comment_id',
|
|
243
|
+
'campaign_variant',
|
|
244
|
+
'offer_code',
|
|
245
|
+
'acquisition_id',
|
|
246
|
+
'visitor_id',
|
|
247
|
+
'session_id',
|
|
248
|
+
'visitor_session_id',
|
|
249
|
+
'install_id',
|
|
250
|
+
'trace_id',
|
|
251
|
+
'cta_id',
|
|
252
|
+
'cta_placement',
|
|
253
|
+
'plan_id',
|
|
254
|
+
'billing_cycle',
|
|
255
|
+
'seat_count',
|
|
256
|
+
'landing_path',
|
|
257
|
+
'referrer_host',
|
|
258
|
+
];
|
|
259
|
+
const TRACKED_LINK_TARGETS = Object.freeze({
|
|
260
|
+
gpt: {
|
|
261
|
+
href: 'https://chatgpt.com/g/g-69dcfd1cd5f881918ae31874631d6f08-thumbgate',
|
|
262
|
+
external: true,
|
|
263
|
+
ctaId: 'go_gpt',
|
|
264
|
+
ctaPlacement: 'link_router',
|
|
265
|
+
eventType: 'chatgpt_gpt_open',
|
|
266
|
+
defaults: {
|
|
267
|
+
utm_source: 'website',
|
|
268
|
+
utm_medium: 'link_router',
|
|
269
|
+
utm_campaign: 'chatgpt_gpt',
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
pro: {
|
|
273
|
+
path: '/checkout/pro',
|
|
274
|
+
ctaId: 'go_pro',
|
|
275
|
+
ctaPlacement: 'link_router',
|
|
276
|
+
eventType: 'cta_click',
|
|
277
|
+
defaults: {
|
|
278
|
+
utm_source: 'website',
|
|
279
|
+
utm_medium: 'link_router',
|
|
280
|
+
utm_campaign: 'pro_upgrade',
|
|
281
|
+
plan_id: 'pro',
|
|
282
|
+
billing_cycle: 'monthly',
|
|
283
|
+
},
|
|
284
|
+
allowCustomerEmail: true,
|
|
285
|
+
},
|
|
286
|
+
install: {
|
|
287
|
+
path: '/guide',
|
|
288
|
+
ctaId: 'go_install',
|
|
289
|
+
ctaPlacement: 'link_router',
|
|
290
|
+
eventType: 'install_guide_click',
|
|
291
|
+
defaults: {
|
|
292
|
+
utm_source: 'website',
|
|
293
|
+
utm_medium: 'link_router',
|
|
294
|
+
utm_campaign: 'install_free',
|
|
295
|
+
plan_id: 'free',
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
reddit: {
|
|
299
|
+
path: '/',
|
|
300
|
+
ctaId: 'go_reddit',
|
|
301
|
+
ctaPlacement: 'link_router',
|
|
302
|
+
eventType: 'community_landing_redirect',
|
|
303
|
+
defaults: {
|
|
304
|
+
utm_source: 'reddit',
|
|
305
|
+
utm_medium: 'organic_social',
|
|
306
|
+
utm_campaign: 'first_party_redirect',
|
|
307
|
+
campaign_variant: 'reddit_shortlink',
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
linkedin: {
|
|
311
|
+
path: '/',
|
|
312
|
+
ctaId: 'go_linkedin',
|
|
313
|
+
ctaPlacement: 'link_router',
|
|
314
|
+
eventType: 'community_landing_redirect',
|
|
315
|
+
defaults: {
|
|
316
|
+
utm_source: 'linkedin',
|
|
317
|
+
utm_medium: 'organic_social',
|
|
318
|
+
utm_campaign: 'first_party_redirect',
|
|
319
|
+
campaign_variant: 'linkedin_shortlink',
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
x: {
|
|
323
|
+
path: '/',
|
|
324
|
+
ctaId: 'go_x',
|
|
325
|
+
ctaPlacement: 'link_router',
|
|
326
|
+
eventType: 'community_landing_redirect',
|
|
327
|
+
defaults: {
|
|
328
|
+
utm_source: 'x',
|
|
329
|
+
utm_medium: 'organic_social',
|
|
330
|
+
utm_campaign: 'first_party_redirect',
|
|
331
|
+
campaign_variant: 'x_shortlink',
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
github: {
|
|
335
|
+
href: 'https://github.com/IgorGanapolsky/ThumbGate',
|
|
336
|
+
external: true,
|
|
337
|
+
ctaId: 'go_github',
|
|
338
|
+
ctaPlacement: 'link_router',
|
|
339
|
+
eventType: 'github_repo_click',
|
|
340
|
+
defaults: {
|
|
341
|
+
utm_source: 'website',
|
|
342
|
+
utm_medium: 'link_router',
|
|
343
|
+
utm_campaign: 'github_repo',
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
});
|
|
230
347
|
|
|
231
348
|
// ---------------------------------------------------------------------------
|
|
232
349
|
// Stripe event tracking helpers
|
|
@@ -841,6 +958,130 @@ function buildCheckoutBootstrapBody(parsed, req, journeyState = resolveJourneySt
|
|
|
841
958
|
};
|
|
842
959
|
}
|
|
843
960
|
|
|
961
|
+
function normalizeTrackedLinkSlug(value) {
|
|
962
|
+
return String(value || '').trim().toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function getTrackedLinkTarget(slug) {
|
|
966
|
+
const normalizedSlug = normalizeTrackedLinkSlug(slug);
|
|
967
|
+
return TRACKED_LINK_TARGETS[normalizedSlug]
|
|
968
|
+
? { slug: normalizedSlug, ...TRACKED_LINK_TARGETS[normalizedSlug] }
|
|
969
|
+
: null;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function appendTrackedLinkQueryParams(destinationUrl, parsed, target) {
|
|
973
|
+
const params = parsed.searchParams;
|
|
974
|
+
for (const [key, value] of Object.entries(target.defaults || {})) {
|
|
975
|
+
if (!destinationUrl.searchParams.has(key)) {
|
|
976
|
+
appendQueryParam(destinationUrl, key, value);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
for (const key of TRACKED_LINK_QUERY_KEYS) {
|
|
980
|
+
const value = params.get(key);
|
|
981
|
+
if (value && value.trim()) {
|
|
982
|
+
destinationUrl.searchParams.set(key, value.trim());
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if (target.allowCustomerEmail) {
|
|
986
|
+
appendQueryParam(destinationUrl, 'customer_email', params.get('customer_email'));
|
|
987
|
+
}
|
|
988
|
+
if (!destinationUrl.searchParams.has('cta_id')) {
|
|
989
|
+
appendQueryParam(destinationUrl, 'cta_id', target.ctaId);
|
|
990
|
+
}
|
|
991
|
+
if (!destinationUrl.searchParams.has('cta_placement')) {
|
|
992
|
+
appendQueryParam(destinationUrl, 'cta_placement', target.ctaPlacement);
|
|
993
|
+
}
|
|
994
|
+
if (!destinationUrl.searchParams.has('landing_path')) {
|
|
995
|
+
appendQueryParam(destinationUrl, 'landing_path', `/go/${target.slug}`);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function buildTrackedLinkDestination(target, hostedConfig, parsed) {
|
|
1000
|
+
const destinationUrl = target.href
|
|
1001
|
+
? new URL(target.href)
|
|
1002
|
+
: new URL(target.path || '/', hostedConfig.appOrigin);
|
|
1003
|
+
appendTrackedLinkQueryParams(destinationUrl, parsed, target);
|
|
1004
|
+
return destinationUrl;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function buildTrackedLinkAttribution(target, parsed, req, journeyState, destinationUrl) {
|
|
1008
|
+
const params = parsed.searchParams;
|
|
1009
|
+
const referrer = pickFirstText(params.get('referrer'), req.headers.referer, req.headers.referrer);
|
|
1010
|
+
const referrerHost = pickFirstText(params.get('referrer_host'), parseReferrerHost(referrer));
|
|
1011
|
+
const source = pickFirstText(
|
|
1012
|
+
params.get('source'),
|
|
1013
|
+
params.get('utm_source'),
|
|
1014
|
+
target.defaults && target.defaults.utm_source,
|
|
1015
|
+
inferSource(referrerHost)
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
return {
|
|
1019
|
+
eventType: target.eventType || 'cta_click',
|
|
1020
|
+
clientType: 'web',
|
|
1021
|
+
acquisitionId: journeyState.acquisitionId,
|
|
1022
|
+
visitorId: journeyState.visitorId,
|
|
1023
|
+
sessionId: journeyState.sessionId,
|
|
1024
|
+
installId: pickFirstText(params.get('install_id')),
|
|
1025
|
+
traceId: pickFirstText(params.get('trace_id')),
|
|
1026
|
+
source,
|
|
1027
|
+
utmSource: pickFirstText(params.get('utm_source'), source),
|
|
1028
|
+
utmMedium: pickFirstText(params.get('utm_medium'), target.defaults && target.defaults.utm_medium, 'link_router'),
|
|
1029
|
+
utmCampaign: pickFirstText(params.get('utm_campaign'), target.defaults && target.defaults.utm_campaign, 'first_party_redirect'),
|
|
1030
|
+
utmContent: pickFirstText(params.get('utm_content')),
|
|
1031
|
+
utmTerm: pickFirstText(params.get('utm_term')),
|
|
1032
|
+
creator: pickFirstText(params.get('creator'), params.get('creator_handle')),
|
|
1033
|
+
community: pickFirstText(params.get('community'), params.get('subreddit')),
|
|
1034
|
+
postId: pickFirstText(params.get('post_id')),
|
|
1035
|
+
commentId: pickFirstText(params.get('comment_id')),
|
|
1036
|
+
campaignVariant: pickFirstText(params.get('campaign_variant'), target.defaults && target.defaults.campaign_variant),
|
|
1037
|
+
offerCode: pickFirstText(params.get('offer_code')),
|
|
1038
|
+
ctaId: pickFirstText(params.get('cta_id'), target.ctaId),
|
|
1039
|
+
ctaPlacement: pickFirstText(params.get('cta_placement'), target.ctaPlacement),
|
|
1040
|
+
planId: pickFirstText(params.get('plan_id'), target.defaults && target.defaults.plan_id),
|
|
1041
|
+
landingPath: pickFirstText(params.get('landing_path'), `/go/${target.slug}`),
|
|
1042
|
+
page: `/go/${target.slug}`,
|
|
1043
|
+
referrer,
|
|
1044
|
+
referrerHost,
|
|
1045
|
+
destinationSlug: target.slug,
|
|
1046
|
+
destinationPath: target.external ? destinationUrl.host : destinationUrl.pathname,
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function serveTrackedLinkRedirect({ req, res, parsed, hostedConfig, isHeadRequest, slug }) {
|
|
1051
|
+
const target = getTrackedLinkTarget(slug);
|
|
1052
|
+
if (!target) {
|
|
1053
|
+
sendJson(res, 404, {
|
|
1054
|
+
error: 'Tracked link not found',
|
|
1055
|
+
allowed: Object.keys(TRACKED_LINK_TARGETS),
|
|
1056
|
+
}, {}, {
|
|
1057
|
+
headOnly: isHeadRequest,
|
|
1058
|
+
});
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const { FEEDBACK_DIR } = getFeedbackPaths();
|
|
1063
|
+
const journeyState = resolveJourneyState(req, parsed);
|
|
1064
|
+
const destinationUrl = buildTrackedLinkDestination(target, hostedConfig, parsed);
|
|
1065
|
+
if (!isHeadRequest) {
|
|
1066
|
+
appendBestEffortTelemetry(
|
|
1067
|
+
FEEDBACK_DIR,
|
|
1068
|
+
buildTrackedLinkAttribution(target, parsed, req, journeyState, destinationUrl),
|
|
1069
|
+
req.headers,
|
|
1070
|
+
`tracked_link_redirect:${target.slug}`
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
res.writeHead(302, {
|
|
1075
|
+
...(journeyState.setCookieHeaders.length ? { 'Set-Cookie': journeyState.setCookieHeaders } : {}),
|
|
1076
|
+
'Cache-Control': 'no-store',
|
|
1077
|
+
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
1078
|
+
'X-Robots-Tag': 'noindex,nofollow',
|
|
1079
|
+
'X-ThumbGate-Link-Slug': target.slug,
|
|
1080
|
+
Location: destinationUrl.toString(),
|
|
1081
|
+
});
|
|
1082
|
+
res.end();
|
|
1083
|
+
}
|
|
1084
|
+
|
|
844
1085
|
function resolveCheckoutOfferSummary(metadata = {}) {
|
|
845
1086
|
const planId = normalizePlanId(metadata.planId);
|
|
846
1087
|
const billingCycle = normalizeBillingCycle(metadata.billingCycle);
|
|
@@ -2697,6 +2938,34 @@ function createApiServer() {
|
|
|
2697
2938
|
attribution,
|
|
2698
2939
|
}) + '\n');
|
|
2699
2940
|
}
|
|
2941
|
+
const journeyState = resolveJourneyState(req, parsed);
|
|
2942
|
+
appendBestEffortTelemetry(getFeedbackPaths().FEEDBACK_DIR, {
|
|
2943
|
+
eventType: 'trial_email_captured',
|
|
2944
|
+
clientType: 'web',
|
|
2945
|
+
acquisitionId: journeyState.acquisitionId,
|
|
2946
|
+
visitorId: journeyState.visitorId,
|
|
2947
|
+
sessionId: journeyState.sessionId,
|
|
2948
|
+
source: attribution.source || 'landing-page',
|
|
2949
|
+
utmSource: attribution.source || null,
|
|
2950
|
+
utmMedium: attribution.medium || 'newsletter',
|
|
2951
|
+
utmCampaign: attribution.campaign || 'trial_email_capture',
|
|
2952
|
+
utmContent: attribution.content || null,
|
|
2953
|
+
utmTerm: attribution.term || null,
|
|
2954
|
+
creator: attribution.creator || null,
|
|
2955
|
+
community: attribution.community || null,
|
|
2956
|
+
postId: attribution.postId || null,
|
|
2957
|
+
commentId: attribution.commentId || null,
|
|
2958
|
+
campaignVariant: attribution.campaignVariant || null,
|
|
2959
|
+
offerCode: attribution.offerCode || null,
|
|
2960
|
+
ctaId: 'trial_email',
|
|
2961
|
+
ctaPlacement: landingPath === '/pro' ? 'pro_email_form' : 'homepage_email_form',
|
|
2962
|
+
planId: 'pro',
|
|
2963
|
+
pipelineStatus: duplicate ? 'duplicate' : 'accepted',
|
|
2964
|
+
page: landingPath,
|
|
2965
|
+
landingPath,
|
|
2966
|
+
referrer: referrer || null,
|
|
2967
|
+
referrerHost,
|
|
2968
|
+
}, req.headers, 'trial_email_captured');
|
|
2700
2969
|
if (wantsJson) {
|
|
2701
2970
|
sendJson(res, 200, {
|
|
2702
2971
|
accepted: true,
|
|
@@ -2750,6 +3019,19 @@ function createApiServer() {
|
|
|
2750
3019
|
}
|
|
2751
3020
|
|
|
2752
3021
|
// Public endpoints — no auth required
|
|
3022
|
+
const trackedLinkMatch = pathname.match(/^\/go\/([^/]+)$/);
|
|
3023
|
+
if (isGetLikeRequest && trackedLinkMatch) {
|
|
3024
|
+
serveTrackedLinkRedirect({
|
|
3025
|
+
req,
|
|
3026
|
+
res,
|
|
3027
|
+
parsed,
|
|
3028
|
+
hostedConfig,
|
|
3029
|
+
isHeadRequest,
|
|
3030
|
+
slug: trackedLinkMatch[1],
|
|
3031
|
+
});
|
|
3032
|
+
return;
|
|
3033
|
+
}
|
|
3034
|
+
|
|
2753
3035
|
if (isGetLikeRequest && pathname === '/robots.txt') {
|
|
2754
3036
|
sendText(res, 200, renderRobotsTxt(hostedConfig), {
|
|
2755
3037
|
'Content-Type': 'text/plain; charset=utf-8',
|