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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.0.35",
3
+ "version": "2.0.37",
4
4
  "description": "Astro integration for TractStack - redeeming the web from boring experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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: goBackend,
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
- // Parse production subdomain pattern: {tenantId}.sandbox.{platform}.com
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
- // Only set locals.tenant when we have a trusted subdomain
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: parts[0],
88
+ id: tenantId,
37
89
  domain: hostname,
38
90
  isMultiTenant: true,
39
91
  isLocalhost: false,
40
92
  };
41
93
 
42
- // Set header for backend communication
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
  }