@uptrademedia/site-kit 1.2.5 → 1.2.7
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/{chunk-TJUON7TH.mjs → chunk-24HSWZ42.mjs} +44 -22
- package/dist/chunk-24HSWZ42.mjs.map +1 -0
- package/dist/chunk-3VHHDNLH.mjs +1 -0
- package/dist/chunk-3WMD3TE6.js +1 -0
- package/dist/chunk-5TTUNB63.js +1 -0
- package/dist/chunk-7557OTHW.js +62 -0
- package/dist/chunk-7557OTHW.js.map +1 -0
- package/dist/chunk-FR6DV5QX.js +1 -0
- package/dist/chunk-G7RSD56P.js +1 -0
- package/dist/chunk-GCJXQ4AG.mjs +59 -0
- package/dist/chunk-GCJXQ4AG.mjs.map +1 -0
- package/dist/chunk-GHSZWROI.js +1 -0
- package/dist/chunk-IARDGI5N.mjs +1 -0
- package/dist/chunk-KUGMH4ZF.js +1 -0
- package/dist/chunk-LBHEVL6U.js +1 -0
- package/dist/chunk-UJQ73OS6.js +1 -0
- package/dist/{chunk-V7QPQBFG.js → chunk-WJD3MZGY.js} +44 -22
- package/dist/chunk-WJD3MZGY.js.map +1 -0
- package/dist/commerce/index.js +1 -0
- package/dist/commerce/index.mjs +1 -0
- package/dist/forms/index.d.mts +1 -1
- package/dist/forms/index.d.ts +1 -1
- package/dist/forms/index.js +31 -9
- package/dist/forms/index.js.map +1 -1
- package/dist/forms/index.mjs +31 -9
- package/dist/forms/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/dist/redirects/index.d.mts +15 -9
- package/dist/redirects/index.d.ts +15 -9
- package/dist/redirects/index.js +6 -5
- package/dist/redirects/index.mjs +2 -1
- package/dist/robots/index.d.mts +12 -20
- package/dist/robots/index.d.ts +12 -20
- package/dist/robots/index.js +37 -19
- package/dist/robots/index.js.map +1 -1
- package/dist/robots/index.mjs +37 -19
- package/dist/robots/index.mjs.map +1 -1
- package/dist/site-config/index.d.mts +24 -0
- package/dist/site-config/index.d.ts +24 -0
- package/dist/site-config/index.js +17 -0
- package/dist/site-config/index.js.map +1 -0
- package/dist/site-config/index.mjs +4 -0
- package/dist/site-config/index.mjs.map +1 -0
- package/dist/sitemap/index.d.mts +2 -2
- package/dist/sitemap/index.d.ts +2 -2
- package/dist/sitemap/index.js +8 -1
- package/dist/sitemap/index.js.map +1 -1
- package/dist/sitemap/index.mjs +8 -1
- package/dist/sitemap/index.mjs.map +1 -1
- package/dist/{types-BYSB7zNY.d.mts → types-mqEAmRhJ.d.mts} +3 -0
- package/dist/{types-BYSB7zNY.d.ts → types-mqEAmRhJ.d.ts} +3 -0
- package/package.json +6 -1
- package/dist/chunk-TJUON7TH.mjs.map +0 -1
- package/dist/chunk-V7QPQBFG.js.map +0 -1
|
@@ -1,33 +1,54 @@
|
|
|
1
|
+
import { getSiteConfig } from './chunk-GCJXQ4AG.mjs';
|
|
1
2
|
import { NextResponse } from 'next/server';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
var
|
|
5
|
-
|
|
4
|
+
var DEFAULT_PORTAL_URL = "https://api.uptrademedia.com";
|
|
5
|
+
var rulesCache = /* @__PURE__ */ new Map();
|
|
6
|
+
function getRedirectCacheKey(apiKey, domain) {
|
|
7
|
+
if (apiKey) return `key:${apiKey}`;
|
|
8
|
+
return `domain:${domain || ""}`;
|
|
9
|
+
}
|
|
6
10
|
async function fetchRedirectRules(config) {
|
|
7
11
|
const now = Date.now();
|
|
8
12
|
const cacheSeconds = config.cacheSeconds ?? 300;
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL;
|
|
14
|
+
const rawKey = config.apiKey ?? (typeof process !== "undefined" && process.env ? process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY : void 0);
|
|
15
|
+
const apiKey = typeof rawKey === "string" ? rawKey : void 0;
|
|
16
|
+
let domain = config.domain;
|
|
17
|
+
if (!apiKey && !domain) {
|
|
18
|
+
const siteConfig = await getSiteConfig({ apiUrl: baseUrl });
|
|
19
|
+
domain = siteConfig?.domain;
|
|
20
|
+
}
|
|
21
|
+
const cacheKey = getRedirectCacheKey(apiKey, domain);
|
|
22
|
+
const cached = rulesCache.get(cacheKey);
|
|
23
|
+
if (cached && now < cached.expiry) {
|
|
24
|
+
return cached.rules;
|
|
11
25
|
}
|
|
12
26
|
try {
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
27
|
+
let url;
|
|
28
|
+
const headers = { "Content-Type": "application/json" };
|
|
29
|
+
if (apiKey) {
|
|
30
|
+
url = `${baseUrl}/api/public/seo/redirects`;
|
|
31
|
+
headers["x-api-key"] = apiKey;
|
|
32
|
+
} else if (domain) {
|
|
33
|
+
url = `${baseUrl}/api/public/seo/redirects?domain=${encodeURIComponent(domain)}`;
|
|
34
|
+
} else {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
16
37
|
const res = await fetch(url, {
|
|
17
|
-
headers
|
|
38
|
+
headers,
|
|
18
39
|
next: { revalidate: cacheSeconds }
|
|
19
40
|
});
|
|
20
41
|
if (!res.ok) {
|
|
21
42
|
console.error(`[site-kit] Failed to fetch redirects: ${res.status}`);
|
|
22
|
-
return
|
|
43
|
+
return cached?.rules ?? [];
|
|
23
44
|
}
|
|
24
45
|
const data = await res.json();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return
|
|
46
|
+
const rules = (data.redirects || []).filter((r) => r.is_enabled);
|
|
47
|
+
rulesCache.set(cacheKey, { rules, expiry: now + cacheSeconds * 1e3 });
|
|
48
|
+
return rules;
|
|
28
49
|
} catch (error) {
|
|
29
50
|
console.error("[site-kit] Error fetching redirects:", error);
|
|
30
|
-
return
|
|
51
|
+
return cached?.rules ?? [];
|
|
31
52
|
}
|
|
32
53
|
}
|
|
33
54
|
async function handleManagedRedirects(request, config) {
|
|
@@ -49,18 +70,20 @@ async function handleManagedRedirects(request, config) {
|
|
|
49
70
|
request.nextUrl.searchParams.forEach((value, key) => {
|
|
50
71
|
redirectUrl.searchParams.set(key, value);
|
|
51
72
|
});
|
|
52
|
-
trackRedirectHit(config, match.from_path).catch(() => {
|
|
73
|
+
trackRedirectHit(config, match.from_path, config.domain).catch(() => {
|
|
53
74
|
});
|
|
54
75
|
const statusCode = parseInt(match.redirect_type);
|
|
55
76
|
return NextResponse.redirect(redirectUrl, statusCode);
|
|
56
77
|
}
|
|
57
|
-
async function trackRedirectHit(config, fromPath) {
|
|
78
|
+
async function trackRedirectHit(config, fromPath, domainForHit) {
|
|
58
79
|
try {
|
|
59
|
-
const baseUrl = config.portalApiUrl ||
|
|
80
|
+
const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL;
|
|
81
|
+
const domain = domainForHit ?? config.domain ?? (await getSiteConfig({ apiKey: config.apiKey, apiUrl: baseUrl }))?.domain;
|
|
82
|
+
if (!domain) return;
|
|
60
83
|
await fetch(`${baseUrl}/api/public/seo/redirects/hit`, {
|
|
61
84
|
method: "POST",
|
|
62
85
|
headers: { "Content-Type": "application/json" },
|
|
63
|
-
body: JSON.stringify({ domain
|
|
86
|
+
body: JSON.stringify({ domain, from_path: fromPath })
|
|
64
87
|
});
|
|
65
88
|
} catch {
|
|
66
89
|
}
|
|
@@ -74,10 +97,9 @@ async function generateNextRedirects(config) {
|
|
|
74
97
|
}));
|
|
75
98
|
}
|
|
76
99
|
function clearRedirectCache() {
|
|
77
|
-
|
|
78
|
-
cacheExpiry = 0;
|
|
100
|
+
rulesCache.clear();
|
|
79
101
|
}
|
|
80
102
|
|
|
81
103
|
export { clearRedirectCache, fetchRedirectRules, generateNextRedirects, handleManagedRedirects };
|
|
82
|
-
//# sourceMappingURL=chunk-
|
|
83
|
-
//# sourceMappingURL=chunk-
|
|
104
|
+
//# sourceMappingURL=chunk-24HSWZ42.mjs.map
|
|
105
|
+
//# sourceMappingURL=chunk-24HSWZ42.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/redirects/index.ts"],"names":[],"mappings":"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","file":"chunk-24HSWZ42.mjs","sourcesContent":["/**\n * Managed Redirects - Next.js Middleware Helper\n * \n * Fetches redirect rules from Portal and applies them.\n * Supports API key only (domain resolved from Portal) or domain-based lookup.\n * \n * Usage in middleware.ts (API key only):\n * \n * import { handleManagedRedirects } from '@uptrade/site-kit/redirects'\n * \n * export async function middleware(request: NextRequest) {\n * const redirect = await handleManagedRedirects(request, {\n * apiKey: process.env.NEXT_PUBLIC_UPTRADE_API_KEY,\n * portalApiUrl: process.env.NEXT_PUBLIC_UPTRADE_API_URL,\n * })\n * if (redirect) return redirect\n * return NextResponse.next()\n * }\n * \n * Or with domain (legacy):\n * const redirect = await handleManagedRedirects(request, { domain: 'example.com' })\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport { getSiteConfig } from '../site-config'\n\nexport interface RedirectRule {\n from_path: string\n to_path: string\n redirect_type: '301' | '302' | '307' | '308'\n is_enabled: boolean\n}\n\nexport interface RedirectConfig {\n /** Domain to fetch redirects for (optional when apiKey is set) */\n domain?: string\n /** Project API key; when set, redirects are fetched by key (no domain needed) */\n apiKey?: string\n portalApiUrl?: string\n cacheSeconds?: number\n}\n\nconst DEFAULT_PORTAL_URL = 'https://api.uptrademedia.com'\n\n// Cache for redirect rules keyed by apiKey or 'domain:'+domain\nconst rulesCache = new Map<string, { rules: RedirectRule[]; expiry: number }>()\n\nfunction getRedirectCacheKey(apiKey: string | undefined, domain: string | undefined): string {\n if (apiKey) return `key:${apiKey}`\n return `domain:${domain || ''}`\n}\n\n/**\n * Fetch redirect rules from Portal API.\n * When apiKey is set (or from env), uses key-based endpoint (no domain required).\n * Otherwise uses domain query (or domain from getSiteConfig when only env API key is set).\n */\nexport async function fetchRedirectRules(config: RedirectConfig): Promise<RedirectRule[]> {\n const now = Date.now()\n const cacheSeconds = config.cacheSeconds ?? 300\n const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL\n\n const rawKey = config.apiKey ?? (typeof process !== 'undefined' && process.env ? (process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY) : undefined)\n const apiKey = typeof rawKey === 'string' ? rawKey : undefined\n let domain = config.domain\n\n if (!apiKey && !domain) {\n const siteConfig = await getSiteConfig({ apiUrl: baseUrl })\n domain = siteConfig?.domain\n }\n\n const cacheKey = getRedirectCacheKey(apiKey, domain)\n const cached = rulesCache.get(cacheKey)\n if (cached && now < cached.expiry) {\n return cached.rules\n }\n\n try {\n let url: string\n const headers: Record<string, string> = { 'Content-Type': 'application/json' }\n\n if (apiKey) {\n url = `${baseUrl}/api/public/seo/redirects`\n headers['x-api-key'] = apiKey\n } else if (domain) {\n url = `${baseUrl}/api/public/seo/redirects?domain=${encodeURIComponent(domain)}`\n } else {\n return []\n }\n\n const res = await fetch(url, {\n headers,\n next: { revalidate: cacheSeconds },\n })\n\n if (!res.ok) {\n console.error(`[site-kit] Failed to fetch redirects: ${res.status}`)\n return cached?.rules ?? []\n }\n\n const data = await res.json()\n const rules = (data.redirects || []).filter((r: RedirectRule) => r.is_enabled)\n rulesCache.set(cacheKey, { rules, expiry: now + cacheSeconds * 1000 })\n return rules\n } catch (error) {\n console.error('[site-kit] Error fetching redirects:', error)\n return cached?.rules ?? []\n }\n}\n\n/**\n * Handle managed redirects in middleware\n * Returns a NextResponse.redirect if a match is found, otherwise undefined\n */\nexport async function handleManagedRedirects(\n request: NextRequest,\n config: RedirectConfig,\n): Promise<NextResponse | undefined> {\n const pathname = request.nextUrl.pathname\n \n // Skip for static assets and API routes\n if (\n pathname.startsWith('/_next') ||\n pathname.startsWith('/api') ||\n pathname.includes('.') // Has file extension\n ) {\n return undefined\n }\n\n const rules = await fetchRedirectRules(config)\n \n // Find matching redirect\n const match = rules.find(r => {\n // Exact match\n if (r.from_path === pathname) return true\n \n // Match with trailing slash variance\n const normalizedFrom = r.from_path.endsWith('/') \n ? r.from_path.slice(0, -1) \n : r.from_path\n const normalizedPath = pathname.endsWith('/') \n ? pathname.slice(0, -1) \n : pathname\n \n return normalizedFrom === normalizedPath\n })\n\n if (!match) {\n return undefined\n }\n\n // Build redirect URL\n const redirectUrl = new URL(match.to_path, request.url)\n \n // Preserve query params\n request.nextUrl.searchParams.forEach((value, key) => {\n redirectUrl.searchParams.set(key, value)\n })\n\n // Track hit (fire and forget); domain may be from config or resolved by apiKey\n trackRedirectHit(config, match.from_path, config.domain).catch(() => {})\n\n // Return redirect response\n const statusCode = parseInt(match.redirect_type) as 301 | 302 | 307 | 308\n return NextResponse.redirect(redirectUrl, statusCode)\n}\n\n/**\n * Track redirect hit for analytics (needs domain; resolved from config or getSiteConfig when using apiKey)\n */\nasync function trackRedirectHit(config: RedirectConfig, fromPath: string, domainForHit?: string): Promise<void> {\n try {\n const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL\n const domain = domainForHit ?? config.domain ?? (await getSiteConfig({ apiKey: config.apiKey, apiUrl: baseUrl }))?.domain\n if (!domain) return\n await fetch(`${baseUrl}/api/public/seo/redirects/hit`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ domain, from_path: fromPath }),\n })\n } catch {\n // Ignore tracking errors\n }\n}\n\n/**\n * Generate Next.js redirects config from Portal\n * Use this in next.config.js for build-time redirects\n * \n * Usage in next.config.js:\n * \n * const { generateNextRedirects } = require('@uptrade/site-kit/redirects')\n * \n * module.exports = {\n * async redirects() {\n * return generateNextRedirects({\n * domain: 'example.com',\n * portalApiUrl: process.env.PORTAL_API_URL,\n * })\n * }\n * }\n */\nexport async function generateNextRedirects(config: RedirectConfig): Promise<Array<{\n source: string\n destination: string\n permanent: boolean\n}>> {\n const rules = await fetchRedirectRules({ ...config, cacheSeconds: 0 })\n \n return rules.map(r => ({\n source: r.from_path,\n destination: r.to_path,\n permanent: r.redirect_type === '301' || r.redirect_type === '308',\n }))\n}\n\n/**\n * Clear redirect cache (useful for development)\n */\nexport function clearRedirectCache(): void {\n rulesCache.clear()\n}\n"]}
|
package/dist/chunk-3VHHDNLH.mjs
CHANGED
package/dist/chunk-3WMD3TE6.js
CHANGED
package/dist/chunk-5TTUNB63.js
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/site-config/index.ts
|
|
4
|
+
var DEFAULT_API_URL = "https://api.uptrademedia.com";
|
|
5
|
+
var CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
6
|
+
var cache = /* @__PURE__ */ new Map();
|
|
7
|
+
function getApiKey(options) {
|
|
8
|
+
if (options?.apiKey) return options.apiKey;
|
|
9
|
+
if (typeof process !== "undefined" && process.env) {
|
|
10
|
+
return process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY;
|
|
11
|
+
}
|
|
12
|
+
return void 0;
|
|
13
|
+
}
|
|
14
|
+
function getApiUrl(options) {
|
|
15
|
+
if (options?.apiUrl) return options.apiUrl.replace(/\/$/, "");
|
|
16
|
+
if (typeof process !== "undefined" && process.env) {
|
|
17
|
+
const u = process.env.NEXT_PUBLIC_UPTRADE_API_URL || process.env.UPTRADE_API_URL;
|
|
18
|
+
if (u) return u.replace(/\/$/, "");
|
|
19
|
+
}
|
|
20
|
+
return DEFAULT_API_URL;
|
|
21
|
+
}
|
|
22
|
+
async function getSiteConfig(options) {
|
|
23
|
+
const apiKey = getApiKey(options);
|
|
24
|
+
if (!apiKey) return null;
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const cached = cache.get(apiKey);
|
|
27
|
+
if (cached && now < cached.expiry) {
|
|
28
|
+
return { site_url: cached.site_url, domain: cached.domain };
|
|
29
|
+
}
|
|
30
|
+
const apiUrl = getApiUrl(options);
|
|
31
|
+
const url = `${apiUrl}/api/public/seo/project-info`;
|
|
32
|
+
try {
|
|
33
|
+
const res = await fetch(url, {
|
|
34
|
+
headers: {
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
"x-api-key": apiKey
|
|
37
|
+
},
|
|
38
|
+
next: { revalidate: 600 }
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) return null;
|
|
41
|
+
const data = await res.json();
|
|
42
|
+
const site_url = data.site_url ?? (data.project_domain ? `https://${String(data.project_domain).replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/$/, "")}` : null);
|
|
43
|
+
const domain = data.domain ?? (data.project_domain ? String(data.project_domain).replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/$/, "") : null);
|
|
44
|
+
if (!site_url || !domain || typeof site_url !== "string" || typeof domain !== "string") return null;
|
|
45
|
+
cache.set(apiKey, {
|
|
46
|
+
site_url,
|
|
47
|
+
domain,
|
|
48
|
+
expiry: now + CACHE_TTL_MS
|
|
49
|
+
});
|
|
50
|
+
return { site_url, domain };
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function clearSiteConfigCache() {
|
|
56
|
+
cache.clear();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
exports.clearSiteConfigCache = clearSiteConfigCache;
|
|
60
|
+
exports.getSiteConfig = getSiteConfig;
|
|
61
|
+
//# sourceMappingURL=chunk-7557OTHW.js.map
|
|
62
|
+
//# sourceMappingURL=chunk-7557OTHW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/site-config/index.ts"],"names":[],"mappings":"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","file":"chunk-7557OTHW.js","sourcesContent":["/**\n * Site config from Portal (project-info) by API key.\n * Resolves site_url and domain so consumers only need NEXT_PUBLIC_UPTRADE_API_KEY.\n */\n\nexport interface SiteConfig {\n site_url: string\n domain: string\n}\n\nexport interface GetSiteConfigOptions {\n apiKey?: string\n apiUrl?: string\n}\n\nconst DEFAULT_API_URL = 'https://api.uptrademedia.com'\nconst CACHE_TTL_MS = 10 * 60 * 1000 // 10 minutes\n\ninterface CacheEntry {\n site_url: string\n domain: string\n expiry: number\n}\nconst cache = new Map<string, CacheEntry>()\n\nfunction getApiKey(options?: GetSiteConfigOptions): string | undefined {\n if (options?.apiKey) return options.apiKey\n if (typeof process !== 'undefined' && process.env) {\n return process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY\n }\n return undefined\n}\n\nfunction getApiUrl(options?: GetSiteConfigOptions): string {\n if (options?.apiUrl) return options.apiUrl.replace(/\\/$/, '')\n if (typeof process !== 'undefined' && process.env) {\n const u = process.env.NEXT_PUBLIC_UPTRADE_API_URL || process.env.UPTRADE_API_URL\n if (u) return u.replace(/\\/$/, '')\n }\n return DEFAULT_API_URL\n}\n\n/**\n * Fetch site_url and domain from Portal project-info for the given API key.\n * Uses in-memory cache (TTL 10 min) keyed by apiKey.\n * Safe for Edge (middleware) and Node (route handlers).\n */\nexport async function getSiteConfig(\n options?: GetSiteConfigOptions\n): Promise<SiteConfig | null> {\n const apiKey = getApiKey(options)\n if (!apiKey) return null\n\n const now = Date.now()\n const cached = cache.get(apiKey)\n if (cached && now < cached.expiry) {\n return { site_url: cached.site_url, domain: cached.domain }\n }\n\n const apiUrl = getApiUrl(options)\n const url = `${apiUrl}/api/public/seo/project-info`\n\n try {\n const res = await fetch(url, {\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n next: { revalidate: 600 },\n })\n\n if (!res.ok) return null\n\n const data = await res.json()\n const site_url = data.site_url ?? (data.project_domain ? `https://${String(data.project_domain).replace(/^(https?:\\/\\/)?(www\\.)?/, '').replace(/\\/$/, '')}` : null)\n const domain = data.domain ?? (data.project_domain ? String(data.project_domain).replace(/^(https?:\\/\\/)?(www\\.)?/, '').replace(/\\/$/, '') : null)\n\n if (!site_url || !domain || typeof site_url !== 'string' || typeof domain !== 'string') return null\n\n cache.set(apiKey, {\n site_url,\n domain,\n expiry: now + CACHE_TTL_MS,\n })\n\n return { site_url, domain }\n } catch {\n return null\n }\n}\n\n/**\n * Clear in-memory site config cache (e.g. for tests).\n */\nexport function clearSiteConfigCache(): void {\n cache.clear()\n}\n"]}
|
package/dist/chunk-FR6DV5QX.js
CHANGED
package/dist/chunk-G7RSD56P.js
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/site-config/index.ts
|
|
2
|
+
var DEFAULT_API_URL = "https://api.uptrademedia.com";
|
|
3
|
+
var CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
4
|
+
var cache = /* @__PURE__ */ new Map();
|
|
5
|
+
function getApiKey(options) {
|
|
6
|
+
if (options?.apiKey) return options.apiKey;
|
|
7
|
+
if (typeof process !== "undefined" && process.env) {
|
|
8
|
+
return process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY;
|
|
9
|
+
}
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
function getApiUrl(options) {
|
|
13
|
+
if (options?.apiUrl) return options.apiUrl.replace(/\/$/, "");
|
|
14
|
+
if (typeof process !== "undefined" && process.env) {
|
|
15
|
+
const u = process.env.NEXT_PUBLIC_UPTRADE_API_URL || process.env.UPTRADE_API_URL;
|
|
16
|
+
if (u) return u.replace(/\/$/, "");
|
|
17
|
+
}
|
|
18
|
+
return DEFAULT_API_URL;
|
|
19
|
+
}
|
|
20
|
+
async function getSiteConfig(options) {
|
|
21
|
+
const apiKey = getApiKey(options);
|
|
22
|
+
if (!apiKey) return null;
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
const cached = cache.get(apiKey);
|
|
25
|
+
if (cached && now < cached.expiry) {
|
|
26
|
+
return { site_url: cached.site_url, domain: cached.domain };
|
|
27
|
+
}
|
|
28
|
+
const apiUrl = getApiUrl(options);
|
|
29
|
+
const url = `${apiUrl}/api/public/seo/project-info`;
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(url, {
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
"x-api-key": apiKey
|
|
35
|
+
},
|
|
36
|
+
next: { revalidate: 600 }
|
|
37
|
+
});
|
|
38
|
+
if (!res.ok) return null;
|
|
39
|
+
const data = await res.json();
|
|
40
|
+
const site_url = data.site_url ?? (data.project_domain ? `https://${String(data.project_domain).replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/$/, "")}` : null);
|
|
41
|
+
const domain = data.domain ?? (data.project_domain ? String(data.project_domain).replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/$/, "") : null);
|
|
42
|
+
if (!site_url || !domain || typeof site_url !== "string" || typeof domain !== "string") return null;
|
|
43
|
+
cache.set(apiKey, {
|
|
44
|
+
site_url,
|
|
45
|
+
domain,
|
|
46
|
+
expiry: now + CACHE_TTL_MS
|
|
47
|
+
});
|
|
48
|
+
return { site_url, domain };
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function clearSiteConfigCache() {
|
|
54
|
+
cache.clear();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { clearSiteConfigCache, getSiteConfig };
|
|
58
|
+
//# sourceMappingURL=chunk-GCJXQ4AG.mjs.map
|
|
59
|
+
//# sourceMappingURL=chunk-GCJXQ4AG.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/site-config/index.ts"],"names":[],"mappings":"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","file":"chunk-GCJXQ4AG.mjs","sourcesContent":["/**\n * Site config from Portal (project-info) by API key.\n * Resolves site_url and domain so consumers only need NEXT_PUBLIC_UPTRADE_API_KEY.\n */\n\nexport interface SiteConfig {\n site_url: string\n domain: string\n}\n\nexport interface GetSiteConfigOptions {\n apiKey?: string\n apiUrl?: string\n}\n\nconst DEFAULT_API_URL = 'https://api.uptrademedia.com'\nconst CACHE_TTL_MS = 10 * 60 * 1000 // 10 minutes\n\ninterface CacheEntry {\n site_url: string\n domain: string\n expiry: number\n}\nconst cache = new Map<string, CacheEntry>()\n\nfunction getApiKey(options?: GetSiteConfigOptions): string | undefined {\n if (options?.apiKey) return options.apiKey\n if (typeof process !== 'undefined' && process.env) {\n return process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY\n }\n return undefined\n}\n\nfunction getApiUrl(options?: GetSiteConfigOptions): string {\n if (options?.apiUrl) return options.apiUrl.replace(/\\/$/, '')\n if (typeof process !== 'undefined' && process.env) {\n const u = process.env.NEXT_PUBLIC_UPTRADE_API_URL || process.env.UPTRADE_API_URL\n if (u) return u.replace(/\\/$/, '')\n }\n return DEFAULT_API_URL\n}\n\n/**\n * Fetch site_url and domain from Portal project-info for the given API key.\n * Uses in-memory cache (TTL 10 min) keyed by apiKey.\n * Safe for Edge (middleware) and Node (route handlers).\n */\nexport async function getSiteConfig(\n options?: GetSiteConfigOptions\n): Promise<SiteConfig | null> {\n const apiKey = getApiKey(options)\n if (!apiKey) return null\n\n const now = Date.now()\n const cached = cache.get(apiKey)\n if (cached && now < cached.expiry) {\n return { site_url: cached.site_url, domain: cached.domain }\n }\n\n const apiUrl = getApiUrl(options)\n const url = `${apiUrl}/api/public/seo/project-info`\n\n try {\n const res = await fetch(url, {\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n next: { revalidate: 600 },\n })\n\n if (!res.ok) return null\n\n const data = await res.json()\n const site_url = data.site_url ?? (data.project_domain ? `https://${String(data.project_domain).replace(/^(https?:\\/\\/)?(www\\.)?/, '').replace(/\\/$/, '')}` : null)\n const domain = data.domain ?? (data.project_domain ? String(data.project_domain).replace(/^(https?:\\/\\/)?(www\\.)?/, '').replace(/\\/$/, '') : null)\n\n if (!site_url || !domain || typeof site_url !== 'string' || typeof domain !== 'string') return null\n\n cache.set(apiKey, {\n site_url,\n domain,\n expiry: now + CACHE_TTL_MS,\n })\n\n return { site_url, domain }\n } catch {\n return null\n }\n}\n\n/**\n * Clear in-memory site config cache (e.g. for tests).\n */\nexport function clearSiteConfigCache(): void {\n cache.clear()\n}\n"]}
|
package/dist/chunk-GHSZWROI.js
CHANGED
package/dist/chunk-IARDGI5N.mjs
CHANGED
package/dist/chunk-KUGMH4ZF.js
CHANGED
package/dist/chunk-LBHEVL6U.js
CHANGED
package/dist/chunk-UJQ73OS6.js
CHANGED
|
@@ -1,35 +1,56 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var chunk7557OTHW_js = require('./chunk-7557OTHW.js');
|
|
3
4
|
var server = require('next/server');
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
var
|
|
7
|
-
|
|
6
|
+
var DEFAULT_PORTAL_URL = "https://api.uptrademedia.com";
|
|
7
|
+
var rulesCache = /* @__PURE__ */ new Map();
|
|
8
|
+
function getRedirectCacheKey(apiKey, domain) {
|
|
9
|
+
if (apiKey) return `key:${apiKey}`;
|
|
10
|
+
return `domain:${domain || ""}`;
|
|
11
|
+
}
|
|
8
12
|
async function fetchRedirectRules(config) {
|
|
9
13
|
const now = Date.now();
|
|
10
14
|
const cacheSeconds = config.cacheSeconds ?? 300;
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL;
|
|
16
|
+
const rawKey = config.apiKey ?? (typeof process !== "undefined" && process.env ? process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY : void 0);
|
|
17
|
+
const apiKey = typeof rawKey === "string" ? rawKey : void 0;
|
|
18
|
+
let domain = config.domain;
|
|
19
|
+
if (!apiKey && !domain) {
|
|
20
|
+
const siteConfig = await chunk7557OTHW_js.getSiteConfig({ apiUrl: baseUrl });
|
|
21
|
+
domain = siteConfig?.domain;
|
|
22
|
+
}
|
|
23
|
+
const cacheKey = getRedirectCacheKey(apiKey, domain);
|
|
24
|
+
const cached = rulesCache.get(cacheKey);
|
|
25
|
+
if (cached && now < cached.expiry) {
|
|
26
|
+
return cached.rules;
|
|
13
27
|
}
|
|
14
28
|
try {
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
29
|
+
let url;
|
|
30
|
+
const headers = { "Content-Type": "application/json" };
|
|
31
|
+
if (apiKey) {
|
|
32
|
+
url = `${baseUrl}/api/public/seo/redirects`;
|
|
33
|
+
headers["x-api-key"] = apiKey;
|
|
34
|
+
} else if (domain) {
|
|
35
|
+
url = `${baseUrl}/api/public/seo/redirects?domain=${encodeURIComponent(domain)}`;
|
|
36
|
+
} else {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
18
39
|
const res = await fetch(url, {
|
|
19
|
-
headers
|
|
40
|
+
headers,
|
|
20
41
|
next: { revalidate: cacheSeconds }
|
|
21
42
|
});
|
|
22
43
|
if (!res.ok) {
|
|
23
44
|
console.error(`[site-kit] Failed to fetch redirects: ${res.status}`);
|
|
24
|
-
return
|
|
45
|
+
return cached?.rules ?? [];
|
|
25
46
|
}
|
|
26
47
|
const data = await res.json();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return
|
|
48
|
+
const rules = (data.redirects || []).filter((r) => r.is_enabled);
|
|
49
|
+
rulesCache.set(cacheKey, { rules, expiry: now + cacheSeconds * 1e3 });
|
|
50
|
+
return rules;
|
|
30
51
|
} catch (error) {
|
|
31
52
|
console.error("[site-kit] Error fetching redirects:", error);
|
|
32
|
-
return
|
|
53
|
+
return cached?.rules ?? [];
|
|
33
54
|
}
|
|
34
55
|
}
|
|
35
56
|
async function handleManagedRedirects(request, config) {
|
|
@@ -51,18 +72,20 @@ async function handleManagedRedirects(request, config) {
|
|
|
51
72
|
request.nextUrl.searchParams.forEach((value, key) => {
|
|
52
73
|
redirectUrl.searchParams.set(key, value);
|
|
53
74
|
});
|
|
54
|
-
trackRedirectHit(config, match.from_path).catch(() => {
|
|
75
|
+
trackRedirectHit(config, match.from_path, config.domain).catch(() => {
|
|
55
76
|
});
|
|
56
77
|
const statusCode = parseInt(match.redirect_type);
|
|
57
78
|
return server.NextResponse.redirect(redirectUrl, statusCode);
|
|
58
79
|
}
|
|
59
|
-
async function trackRedirectHit(config, fromPath) {
|
|
80
|
+
async function trackRedirectHit(config, fromPath, domainForHit) {
|
|
60
81
|
try {
|
|
61
|
-
const baseUrl = config.portalApiUrl ||
|
|
82
|
+
const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL;
|
|
83
|
+
const domain = domainForHit ?? config.domain ?? (await chunk7557OTHW_js.getSiteConfig({ apiKey: config.apiKey, apiUrl: baseUrl }))?.domain;
|
|
84
|
+
if (!domain) return;
|
|
62
85
|
await fetch(`${baseUrl}/api/public/seo/redirects/hit`, {
|
|
63
86
|
method: "POST",
|
|
64
87
|
headers: { "Content-Type": "application/json" },
|
|
65
|
-
body: JSON.stringify({ domain
|
|
88
|
+
body: JSON.stringify({ domain, from_path: fromPath })
|
|
66
89
|
});
|
|
67
90
|
} catch {
|
|
68
91
|
}
|
|
@@ -76,13 +99,12 @@ async function generateNextRedirects(config) {
|
|
|
76
99
|
}));
|
|
77
100
|
}
|
|
78
101
|
function clearRedirectCache() {
|
|
79
|
-
|
|
80
|
-
cacheExpiry = 0;
|
|
102
|
+
rulesCache.clear();
|
|
81
103
|
}
|
|
82
104
|
|
|
83
105
|
exports.clearRedirectCache = clearRedirectCache;
|
|
84
106
|
exports.fetchRedirectRules = fetchRedirectRules;
|
|
85
107
|
exports.generateNextRedirects = generateNextRedirects;
|
|
86
108
|
exports.handleManagedRedirects = handleManagedRedirects;
|
|
87
|
-
//# sourceMappingURL=chunk-
|
|
88
|
-
//# sourceMappingURL=chunk-
|
|
109
|
+
//# sourceMappingURL=chunk-WJD3MZGY.js.map
|
|
110
|
+
//# sourceMappingURL=chunk-WJD3MZGY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/redirects/index.ts"],"names":["getSiteConfig","NextResponse"],"mappings":"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","file":"chunk-WJD3MZGY.js","sourcesContent":["/**\n * Managed Redirects - Next.js Middleware Helper\n * \n * Fetches redirect rules from Portal and applies them.\n * Supports API key only (domain resolved from Portal) or domain-based lookup.\n * \n * Usage in middleware.ts (API key only):\n * \n * import { handleManagedRedirects } from '@uptrade/site-kit/redirects'\n * \n * export async function middleware(request: NextRequest) {\n * const redirect = await handleManagedRedirects(request, {\n * apiKey: process.env.NEXT_PUBLIC_UPTRADE_API_KEY,\n * portalApiUrl: process.env.NEXT_PUBLIC_UPTRADE_API_URL,\n * })\n * if (redirect) return redirect\n * return NextResponse.next()\n * }\n * \n * Or with domain (legacy):\n * const redirect = await handleManagedRedirects(request, { domain: 'example.com' })\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport { getSiteConfig } from '../site-config'\n\nexport interface RedirectRule {\n from_path: string\n to_path: string\n redirect_type: '301' | '302' | '307' | '308'\n is_enabled: boolean\n}\n\nexport interface RedirectConfig {\n /** Domain to fetch redirects for (optional when apiKey is set) */\n domain?: string\n /** Project API key; when set, redirects are fetched by key (no domain needed) */\n apiKey?: string\n portalApiUrl?: string\n cacheSeconds?: number\n}\n\nconst DEFAULT_PORTAL_URL = 'https://api.uptrademedia.com'\n\n// Cache for redirect rules keyed by apiKey or 'domain:'+domain\nconst rulesCache = new Map<string, { rules: RedirectRule[]; expiry: number }>()\n\nfunction getRedirectCacheKey(apiKey: string | undefined, domain: string | undefined): string {\n if (apiKey) return `key:${apiKey}`\n return `domain:${domain || ''}`\n}\n\n/**\n * Fetch redirect rules from Portal API.\n * When apiKey is set (or from env), uses key-based endpoint (no domain required).\n * Otherwise uses domain query (or domain from getSiteConfig when only env API key is set).\n */\nexport async function fetchRedirectRules(config: RedirectConfig): Promise<RedirectRule[]> {\n const now = Date.now()\n const cacheSeconds = config.cacheSeconds ?? 300\n const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL\n\n const rawKey = config.apiKey ?? (typeof process !== 'undefined' && process.env ? (process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY) : undefined)\n const apiKey = typeof rawKey === 'string' ? rawKey : undefined\n let domain = config.domain\n\n if (!apiKey && !domain) {\n const siteConfig = await getSiteConfig({ apiUrl: baseUrl })\n domain = siteConfig?.domain\n }\n\n const cacheKey = getRedirectCacheKey(apiKey, domain)\n const cached = rulesCache.get(cacheKey)\n if (cached && now < cached.expiry) {\n return cached.rules\n }\n\n try {\n let url: string\n const headers: Record<string, string> = { 'Content-Type': 'application/json' }\n\n if (apiKey) {\n url = `${baseUrl}/api/public/seo/redirects`\n headers['x-api-key'] = apiKey\n } else if (domain) {\n url = `${baseUrl}/api/public/seo/redirects?domain=${encodeURIComponent(domain)}`\n } else {\n return []\n }\n\n const res = await fetch(url, {\n headers,\n next: { revalidate: cacheSeconds },\n })\n\n if (!res.ok) {\n console.error(`[site-kit] Failed to fetch redirects: ${res.status}`)\n return cached?.rules ?? []\n }\n\n const data = await res.json()\n const rules = (data.redirects || []).filter((r: RedirectRule) => r.is_enabled)\n rulesCache.set(cacheKey, { rules, expiry: now + cacheSeconds * 1000 })\n return rules\n } catch (error) {\n console.error('[site-kit] Error fetching redirects:', error)\n return cached?.rules ?? []\n }\n}\n\n/**\n * Handle managed redirects in middleware\n * Returns a NextResponse.redirect if a match is found, otherwise undefined\n */\nexport async function handleManagedRedirects(\n request: NextRequest,\n config: RedirectConfig,\n): Promise<NextResponse | undefined> {\n const pathname = request.nextUrl.pathname\n \n // Skip for static assets and API routes\n if (\n pathname.startsWith('/_next') ||\n pathname.startsWith('/api') ||\n pathname.includes('.') // Has file extension\n ) {\n return undefined\n }\n\n const rules = await fetchRedirectRules(config)\n \n // Find matching redirect\n const match = rules.find(r => {\n // Exact match\n if (r.from_path === pathname) return true\n \n // Match with trailing slash variance\n const normalizedFrom = r.from_path.endsWith('/') \n ? r.from_path.slice(0, -1) \n : r.from_path\n const normalizedPath = pathname.endsWith('/') \n ? pathname.slice(0, -1) \n : pathname\n \n return normalizedFrom === normalizedPath\n })\n\n if (!match) {\n return undefined\n }\n\n // Build redirect URL\n const redirectUrl = new URL(match.to_path, request.url)\n \n // Preserve query params\n request.nextUrl.searchParams.forEach((value, key) => {\n redirectUrl.searchParams.set(key, value)\n })\n\n // Track hit (fire and forget); domain may be from config or resolved by apiKey\n trackRedirectHit(config, match.from_path, config.domain).catch(() => {})\n\n // Return redirect response\n const statusCode = parseInt(match.redirect_type) as 301 | 302 | 307 | 308\n return NextResponse.redirect(redirectUrl, statusCode)\n}\n\n/**\n * Track redirect hit for analytics (needs domain; resolved from config or getSiteConfig when using apiKey)\n */\nasync function trackRedirectHit(config: RedirectConfig, fromPath: string, domainForHit?: string): Promise<void> {\n try {\n const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL\n const domain = domainForHit ?? config.domain ?? (await getSiteConfig({ apiKey: config.apiKey, apiUrl: baseUrl }))?.domain\n if (!domain) return\n await fetch(`${baseUrl}/api/public/seo/redirects/hit`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ domain, from_path: fromPath }),\n })\n } catch {\n // Ignore tracking errors\n }\n}\n\n/**\n * Generate Next.js redirects config from Portal\n * Use this in next.config.js for build-time redirects\n * \n * Usage in next.config.js:\n * \n * const { generateNextRedirects } = require('@uptrade/site-kit/redirects')\n * \n * module.exports = {\n * async redirects() {\n * return generateNextRedirects({\n * domain: 'example.com',\n * portalApiUrl: process.env.PORTAL_API_URL,\n * })\n * }\n * }\n */\nexport async function generateNextRedirects(config: RedirectConfig): Promise<Array<{\n source: string\n destination: string\n permanent: boolean\n}>> {\n const rules = await fetchRedirectRules({ ...config, cacheSeconds: 0 })\n \n return rules.map(r => ({\n source: r.from_path,\n destination: r.to_path,\n permanent: r.redirect_type === '301' || r.redirect_type === '308',\n }))\n}\n\n/**\n * Clear redirect cache (useful for development)\n */\nexport function clearRedirectCache(): void {\n rulesCache.clear()\n}\n"]}
|
package/dist/commerce/index.js
CHANGED
package/dist/commerce/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
export { CalendarView, CheckoutForm, EventCalendar, EventEmbed, EventModal, EventTile, EventsWidget, OfferingCard, OfferingList, ProductDetail, ProductEmbed, ProductGrid, ProductPage, RegistrationForm, UpcomingEvents, createCheckoutSession, fetchActiveProcessor, fetchCategories, fetchLatestOffering, fetchNextEvent, fetchOffering, fetchOfferings, fetchProcessorConfig, fetchProductBySlug, fetchProducts, fetchProductsPublic, fetchServices, fetchShippingRates, fetchUpcomingEvents, formatDate, formatDateRange, formatDateTime, formatPrice, formatTime, getOfferingUrl, getRelativeTimeUntil, getSpotsRemaining, isEventSoldOut, registerForEvent, useEventModal, validateAddress } from '../chunk-IARDGI5N.mjs';
|
|
2
3
|
import '../chunk-4XPGGLVP.mjs';
|
|
3
4
|
//# sourceMappingURL=index.mjs.map
|
package/dist/forms/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FormSubmission, M as ManagedFormConfig, a as FormField$2, b as ManagedFormProps, c as FormRenderProps } from '../types-
|
|
1
|
+
import { F as FormSubmission, M as ManagedFormConfig, a as FormField$2, b as ManagedFormProps, c as FormRenderProps } from '../types-mqEAmRhJ.mjs';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
import React__default from 'react';
|
|
4
4
|
|
package/dist/forms/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FormSubmission, M as ManagedFormConfig, a as FormField$2, b as ManagedFormProps, c as FormRenderProps } from '../types-
|
|
1
|
+
import { F as FormSubmission, M as ManagedFormConfig, a as FormField$2, b as ManagedFormProps, c as FormRenderProps } from '../types-mqEAmRhJ.js';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
import React__default from 'react';
|
|
4
4
|
|
package/dist/forms/index.js
CHANGED
|
@@ -174,8 +174,9 @@ function getDeviceType() {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// src/forms/recaptcha.ts
|
|
177
|
-
function getSiteKey() {
|
|
177
|
+
function getSiteKey(explicitSiteKey) {
|
|
178
178
|
if (typeof window === "undefined") return void 0;
|
|
179
|
+
if (explicitSiteKey) return explicitSiteKey;
|
|
179
180
|
const win = window;
|
|
180
181
|
return win.__SITE_KIT_RECAPTCHA_SITE_KEY__ ?? process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY;
|
|
181
182
|
}
|
|
@@ -207,8 +208,8 @@ function loadScript(siteKey) {
|
|
|
207
208
|
document.head.appendChild(script);
|
|
208
209
|
});
|
|
209
210
|
}
|
|
210
|
-
async function getRecaptchaToken() {
|
|
211
|
-
const siteKey = getSiteKey();
|
|
211
|
+
async function getRecaptchaToken(explicitSiteKey) {
|
|
212
|
+
const siteKey = getSiteKey(explicitSiteKey);
|
|
212
213
|
if (!siteKey || typeof window === "undefined") return null;
|
|
213
214
|
try {
|
|
214
215
|
const grecaptcha = await loadScript(siteKey);
|
|
@@ -283,6 +284,10 @@ function useForm(formIdOrSlug, options = {}) {
|
|
|
283
284
|
}
|
|
284
285
|
const data = await response.json();
|
|
285
286
|
if (!data) throw new Error("Form not found");
|
|
287
|
+
if (typeof window !== "undefined" && data.recaptcha_site_key) {
|
|
288
|
+
;
|
|
289
|
+
window.__SITE_KIT_RECAPTCHA_SITE_KEY__ = data.recaptcha_site_key;
|
|
290
|
+
}
|
|
286
291
|
if (data.steps) {
|
|
287
292
|
data.steps.sort((a, b) => (a.step_number || 0) - (b.step_number || 0));
|
|
288
293
|
}
|
|
@@ -443,11 +448,18 @@ function useForm(formIdOrSlug, options = {}) {
|
|
|
443
448
|
if (!apiKey) {
|
|
444
449
|
throw new Error("API key is required. Set NEXT_PUBLIC_UPTRADE_API_KEY in your .env");
|
|
445
450
|
}
|
|
446
|
-
const
|
|
451
|
+
const honeypotFieldName = form.honeypot_field || "website";
|
|
452
|
+
const recaptchaToken = form.recaptcha_enabled ? await getRecaptchaToken(form.recaptcha_site_key) : null;
|
|
453
|
+
if (form.recaptcha_enabled && !recaptchaToken) {
|
|
454
|
+
throw new Error("reCAPTCHA is required but could not be initialized.");
|
|
455
|
+
}
|
|
447
456
|
const utm = getUTMParams();
|
|
448
457
|
const submission = {
|
|
449
458
|
formId: form.id,
|
|
450
|
-
data:
|
|
459
|
+
data: {
|
|
460
|
+
...values,
|
|
461
|
+
...form.honeypot_enabled ? { [honeypotFieldName]: "" } : {}
|
|
462
|
+
},
|
|
451
463
|
metadata: {
|
|
452
464
|
pageUrl: typeof window !== "undefined" ? window.location.href : null,
|
|
453
465
|
referrer: typeof document !== "undefined" ? document.referrer || null : null,
|
|
@@ -797,6 +809,8 @@ function FormClient({
|
|
|
797
809
|
const [step, setStep] = react.useState(1);
|
|
798
810
|
const [isSubmitting, setIsSubmitting] = react.useState(false);
|
|
799
811
|
const [isComplete, setIsComplete] = react.useState(false);
|
|
812
|
+
const [honeypotValue, setHoneypotValue] = react.useState("");
|
|
813
|
+
const honeypotFieldName = config.honeypot_field || "website";
|
|
800
814
|
const { trackStepChange, trackComplete } = useFormTracking({
|
|
801
815
|
formId: config.id,
|
|
802
816
|
totalSteps: config.total_steps
|
|
@@ -920,12 +934,18 @@ function FormClient({
|
|
|
920
934
|
if (!apiKey) {
|
|
921
935
|
throw new Error("API key is required. Set NEXT_PUBLIC_UPTRADE_API_KEY in your .env");
|
|
922
936
|
}
|
|
923
|
-
const recaptchaToken = await getRecaptchaToken();
|
|
937
|
+
const recaptchaToken = config.recaptcha_enabled ? await getRecaptchaToken(config.recaptcha_site_key) : null;
|
|
938
|
+
if (config.recaptcha_enabled && !recaptchaToken) {
|
|
939
|
+
throw new Error("reCAPTCHA is required but could not be initialized.");
|
|
940
|
+
}
|
|
924
941
|
const utm = getUTMParams2();
|
|
925
942
|
const submission = {
|
|
926
943
|
form_id: config.id,
|
|
927
944
|
project_id: config.project_id,
|
|
928
|
-
data:
|
|
945
|
+
data: {
|
|
946
|
+
...values,
|
|
947
|
+
...config.honeypot_enabled ? { [honeypotFieldName]: honeypotValue } : {}
|
|
948
|
+
},
|
|
929
949
|
routing_type: config.form_type,
|
|
930
950
|
status: "new",
|
|
931
951
|
metadata: {
|
|
@@ -964,7 +984,7 @@ function FormClient({
|
|
|
964
984
|
} finally {
|
|
965
985
|
setIsSubmitting(false);
|
|
966
986
|
}
|
|
967
|
-
}, [config, values, validateStep, trackComplete, onSuccess, onError]);
|
|
987
|
+
}, [config, honeypotFieldName, honeypotValue, values, validateStep, trackComplete, onSuccess, onError]);
|
|
968
988
|
const progress = react.useMemo(() => {
|
|
969
989
|
return Math.round(step / config.total_steps * 100);
|
|
970
990
|
}, [step, config.total_steps]);
|
|
@@ -1052,7 +1072,7 @@ function FormClient({
|
|
|
1052
1072
|
"input",
|
|
1053
1073
|
{
|
|
1054
1074
|
type: "text",
|
|
1055
|
-
name:
|
|
1075
|
+
name: honeypotFieldName,
|
|
1056
1076
|
tabIndex: -1,
|
|
1057
1077
|
autoComplete: "off",
|
|
1058
1078
|
style: {
|
|
@@ -1061,7 +1081,9 @@ function FormClient({
|
|
|
1061
1081
|
opacity: 0,
|
|
1062
1082
|
pointerEvents: "none"
|
|
1063
1083
|
},
|
|
1084
|
+
value: honeypotValue,
|
|
1064
1085
|
onChange: (e) => {
|
|
1086
|
+
setHoneypotValue(e.target.value);
|
|
1065
1087
|
if (e.target.value) {
|
|
1066
1088
|
console.warn("[Forms] Honeypot triggered");
|
|
1067
1089
|
}
|