astro-tractstack 2.0.35 → 2.0.37
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/package.json
CHANGED
|
@@ -212,16 +212,35 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
|
212
212
|
// This makes the scope explicit and resolves the linter error.
|
|
213
213
|
const initialStoryfragmentId = storyfragmentId;
|
|
214
214
|
const initialSessionId = sessionId;
|
|
215
|
-
|
|
216
215
|
function updateTractstackConfig() {
|
|
217
216
|
const storyfragmentMeta = document.querySelector(
|
|
218
217
|
'meta[name="storyfragment-id"]'
|
|
219
218
|
);
|
|
220
219
|
const sessionMeta = document.querySelector('meta[name="session-id"]');
|
|
221
220
|
|
|
221
|
+
// Dynamic Backend URL Fix:
|
|
222
|
+
// If we are on a subdomain (sandbox.domain.com), force the API call to match
|
|
223
|
+
// (sandbox.domain.com:10000) so cookies are sent correctly.
|
|
224
|
+
let dynamicBackendUrl = goBackend;
|
|
225
|
+
try {
|
|
226
|
+
const currentHost = window.location.hostname;
|
|
227
|
+
const backendUrlObj = new URL(goBackend);
|
|
228
|
+
|
|
229
|
+
// If the hostnames differ (e.g. sandbox.site.com vs site.com), align them
|
|
230
|
+
if (currentHost !== backendUrlObj.hostname) {
|
|
231
|
+
backendUrlObj.hostname = currentHost;
|
|
232
|
+
dynamicBackendUrl = backendUrlObj.toString().replace(/\/$/, '');
|
|
233
|
+
}
|
|
234
|
+
} catch (e) {
|
|
235
|
+
console.warn(
|
|
236
|
+
'TractStack: Failed to construct dynamic backend URL',
|
|
237
|
+
e
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
222
241
|
window.TRACTSTACK_CONFIG = {
|
|
223
242
|
configured: true,
|
|
224
|
-
backendUrl:
|
|
243
|
+
backendUrl: dynamicBackendUrl,
|
|
225
244
|
tenantId: tenantId,
|
|
226
245
|
fontBasePath: fontBasePath,
|
|
227
246
|
// Use the meta tag if it exists (for subsequent client-side loads),
|
|
@@ -9,6 +9,14 @@ interface Locals {
|
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
interface CacheEntry {
|
|
13
|
+
tenantId: string;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const domainCache = new Map<string, CacheEntry>();
|
|
18
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
19
|
+
|
|
12
20
|
export async function onRequest(
|
|
13
21
|
context: APIContext & { locals: Locals },
|
|
14
22
|
next: MiddlewareNext
|
|
@@ -16,37 +24,77 @@ export async function onRequest(
|
|
|
16
24
|
const isMultiTenantEnabled =
|
|
17
25
|
import.meta.env.PUBLIC_ENABLE_MULTI_TENANT === 'true';
|
|
18
26
|
|
|
19
|
-
// Only set tenant in locals if we have a trusted subdomain (production multi-tenant)
|
|
20
27
|
if (isMultiTenantEnabled && !import.meta.env.DEV) {
|
|
21
28
|
const hostname =
|
|
22
29
|
context.request.headers.get('x-forwarded-host') ||
|
|
23
30
|
context.request.headers.get('host');
|
|
24
31
|
|
|
25
32
|
if (hostname) {
|
|
26
|
-
|
|
33
|
+
let tenantId: string | null = null;
|
|
34
|
+
|
|
35
|
+
// Strategy 1: Regex Pattern (Fastest)
|
|
27
36
|
const parts = hostname.split('.');
|
|
28
37
|
if (
|
|
38
|
+
parts.length === 3 &&
|
|
39
|
+
['freewebpress', 'tractstack'].includes(parts[1]) &&
|
|
40
|
+
parts[2] === 'com'
|
|
41
|
+
) {
|
|
42
|
+
tenantId = parts[0];
|
|
43
|
+
} else if (
|
|
29
44
|
parts.length >= 4 &&
|
|
30
45
|
parts[1] === 'sandbox' &&
|
|
31
46
|
['freewebpress', 'tractstack'].includes(parts[2]) &&
|
|
32
47
|
parts[3] === 'com'
|
|
33
48
|
) {
|
|
34
|
-
|
|
49
|
+
tenantId = parts[0];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Strategy 2: Cache Lookup (Fast)
|
|
53
|
+
if (!tenantId) {
|
|
54
|
+
const cached = domainCache.get(hostname);
|
|
55
|
+
if (cached) {
|
|
56
|
+
if (Date.now() - cached.timestamp < CACHE_TTL) {
|
|
57
|
+
tenantId = cached.tenantId;
|
|
58
|
+
} else {
|
|
59
|
+
domainCache.delete(hostname);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Strategy 3: Backend Lookup (Fallback)
|
|
65
|
+
if (!tenantId) {
|
|
66
|
+
try {
|
|
67
|
+
// We assume the backend is always reachable on localhost:8080 in this architecture
|
|
68
|
+
const response = await fetch(
|
|
69
|
+
`http://127.0.0.1:8080/api/v1/resolve-domain?host=${encodeURIComponent(hostname)}`
|
|
70
|
+
);
|
|
71
|
+
if (response.ok) {
|
|
72
|
+
const data = await response.json();
|
|
73
|
+
if (data.tenantId) {
|
|
74
|
+
tenantId = data.tenantId;
|
|
75
|
+
domainCache.set(hostname, {
|
|
76
|
+
tenantId: data.tenantId,
|
|
77
|
+
timestamp: Date.now(),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(`Failed to resolve domain ${hostname}:`, error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (tenantId) {
|
|
35
87
|
context.locals.tenant = {
|
|
36
|
-
id:
|
|
88
|
+
id: tenantId,
|
|
37
89
|
domain: hostname,
|
|
38
90
|
isMultiTenant: true,
|
|
39
91
|
isLocalhost: false,
|
|
40
92
|
};
|
|
41
93
|
|
|
42
|
-
|
|
43
|
-
context.request.headers.set('X-Tenant-ID', parts[0]);
|
|
94
|
+
context.request.headers.set('X-Tenant-ID', tenantId);
|
|
44
95
|
}
|
|
45
96
|
}
|
|
46
97
|
}
|
|
47
98
|
|
|
48
|
-
// If no trusted subdomain detected, leave locals.tenant undefined
|
|
49
|
-
// This allows the cascading check to fall through to PUBLIC_TENANTID
|
|
50
|
-
|
|
51
99
|
return next();
|
|
52
100
|
}
|