astro-tractstack 2.0.35 → 2.0.36

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.36",
4
4
  "description": "Astro integration for TractStack - redeeming the web from boring experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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
  }