astro-tractstack 2.0.38 → 2.0.40

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/index.js CHANGED
@@ -10,7 +10,7 @@ function b(t) {
10
10
  }
11
11
  function g(t, e) {
12
12
  e.info("TractStack configuration applied"), t.enableMultiTenant && e.info("Multi-tenant mode enabled"), t.includeExamples && e.info("Example components will be included");
13
- const c = process.env.PUBLIC_GO_BACKEND, o = process.env.PUBLIC_TENANTID;
13
+ const c = process.env.PUBLIC_GO_BACKEND, r = process.env.PUBLIC_TENANTID;
14
14
  if (!c)
15
15
  e.warn("PUBLIC_GO_BACKEND not set - this will be required at runtime");
16
16
  else
@@ -19,11 +19,11 @@ function g(t, e) {
19
19
  } catch {
20
20
  e.error(`PUBLIC_GO_BACKEND is not a valid URL: ${c}`);
21
21
  }
22
- return o ? /^[a-zA-Z0-9_-]+$/.test(o) ? e.info(`Tenant ID validated: ${o}`) : e.error(`PUBLIC_TENANTID contains invalid characters: ${o}`) : e.warn("PUBLIC_TENANTID not set - this will be required at runtime"), t;
22
+ return r ? /^[a-zA-Z0-9_-]+$/.test(r) ? e.info(`Tenant ID validated: ${r}`) : e.error(`PUBLIC_TENANTID contains invalid characters: ${r}`) : e.warn("PUBLIC_TENANTID not set - this will be required at runtime"), t;
23
23
  }
24
24
  async function w(t, e, c) {
25
25
  e.info("TractStack: Injecting template files");
26
- const o = [
26
+ const r = [
27
27
  // Core Configuration
28
28
  {
29
29
  src: t("../templates/env.example"),
@@ -751,6 +751,10 @@ async function w(t, e, c) {
751
751
  src: t("../templates/src/utils/api.ts"),
752
752
  dest: "src/utils/api.ts"
753
753
  },
754
+ {
755
+ src: t("../templates/src/utils/tenantResolver.ts"),
756
+ dest: "src/utils/tenantResolver.ts"
757
+ },
754
758
  {
755
759
  src: t("../templates/src/utils/api/brandConfig.ts"),
756
760
  dest: "src/utils/api/brandConfig.ts"
@@ -2144,12 +2148,12 @@ async function w(t, e, c) {
2144
2148
  }
2145
2149
  ] : []
2146
2150
  ];
2147
- for (const s of o)
2151
+ for (const s of r)
2148
2152
  try {
2149
2153
  const p = i(s.dest);
2150
2154
  n(p) || x(p, { recursive: !0 });
2151
- const r = !s.protected && (s.dest === "tailwind.config.cjs" || s.dest.startsWith("src/components/codehooks/") || s.dest.startsWith("src/components/widgets/") || s.dest.startsWith("src/") || s.dest.startsWith("public/client/") || s.dest === ".gitignore");
2152
- if (!n(s.dest) || r)
2155
+ const o = !s.protected && (s.dest === "tailwind.config.cjs" || s.dest.startsWith("src/components/codehooks/") || s.dest.startsWith("src/components/widgets/") || s.dest.startsWith("src/") || s.dest.startsWith("public/client/") || s.dest === ".gitignore");
2156
+ if (!n(s.dest) || o)
2153
2157
  if (n(s.src))
2154
2158
  k(s.src, s.dest), e.info(`Updated ${s.dest}`);
2155
2159
  else {
@@ -2158,8 +2162,8 @@ async function w(t, e, c) {
2158
2162
  }
2159
2163
  else s.protected ? e.info(`Protected: ${s.dest} (skipped overwrite)`) : e.info(`Skipped existing ${s.dest}`);
2160
2164
  } catch (p) {
2161
- const r = p instanceof Error ? p.message : String(p);
2162
- e.error(`Failed to create ${s.dest}: ${r}`);
2165
+ const o = p instanceof Error ? p.message : String(p);
2166
+ e.error(`Failed to create ${s.dest}: ${o}`);
2163
2167
  }
2164
2168
  }
2165
2169
  function _(t) {
@@ -2178,7 +2182,7 @@ function C(t = {}) {
2178
2182
  return {
2179
2183
  name: "astro-tractstack",
2180
2184
  hooks: {
2181
- "astro:config:setup": async ({ config: c, updateConfig: o, logger: s }) => {
2185
+ "astro:config:setup": async ({ config: c, updateConfig: r, logger: s }) => {
2182
2186
  g(t, s);
2183
2187
  const p = t.enableMultiTenant || !1;
2184
2188
  if (s.info(
@@ -2198,7 +2202,7 @@ function C(t = {}) {
2198
2202
  ), new Error(
2199
2203
  "TractStack requires an SSR adapter. Please add @astrojs/node adapter to your astro.config.mjs"
2200
2204
  );
2201
- o({
2205
+ r({
2202
2206
  vite: {
2203
2207
  define: {
2204
2208
  __TRACTSTACK_VERSION__: JSON.stringify("2.0.0-alpha.1"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.0.38",
3
+ "version": "2.0.40",
4
4
  "description": "Astro integration for TractStack - redeeming the web from boring experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,6 +24,7 @@ import { DirectInjectStep } from './steps/DirectInjectStep';
24
24
  import BooleanToggle from '@/components/form/BooleanToggle';
25
25
  import EnumSelect from '@/components/form/EnumSelect';
26
26
  import type { StoryFragmentNode } from '@/types/compositorTypes';
27
+ import { TractStackAPI } from '@/utils/api'; // <--- IMPORT ADDED
27
28
 
28
29
  type Step =
29
30
  | 'initial'
@@ -51,9 +52,10 @@ const callAskLemurAPI = async (
51
52
  expectJson: boolean,
52
53
  isSandboxMode: boolean
53
54
  ): Promise<string> => {
54
- const goBackend =
55
- import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
56
- const tenantId = import.meta.env.PUBLIC_TENANTID || 'default';
55
+ // FIX: Use the centralized API class to ensure correct Tenant ID resolution
56
+ const api = new TractStackAPI();
57
+ const tenantId = api.getTenantId(); // Gets correct ID from window config
58
+
57
59
  const requestBody = {
58
60
  prompt,
59
61
  input_text: context,
@@ -62,43 +64,47 @@ const callAskLemurAPI = async (
62
64
  max_tokens: 2000,
63
65
  };
64
66
 
65
- let response: Response;
67
+ let resultData: any;
68
+
66
69
  if (isSandboxMode) {
67
- response = await fetch(`/api/sandbox`, {
70
+ // Sandbox mode still uses local fetch, but we pass the correct tenant ID
71
+ const response = await fetch(`/api/sandbox`, {
68
72
  method: 'POST',
69
73
  headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': tenantId },
70
74
  credentials: 'include',
71
75
  body: JSON.stringify({ action: 'askLemur', payload: requestBody }),
72
76
  });
77
+
78
+ if (!response.ok) {
79
+ const errorText = await response.text();
80
+ throw new Error(`Sandbox API failed: ${response.status} ${errorText}`);
81
+ }
82
+
83
+ const json = await response.json();
84
+ if (!json.success) {
85
+ throw new Error(json.error || 'Sandbox generation failed');
86
+ }
87
+ resultData = json.data; // { response: ... }
73
88
  } else {
74
- response = await fetch(`${goBackend}/api/v1/aai/askLemur`, {
75
- method: 'POST',
76
- headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': tenantId },
77
- credentials: 'include',
78
- body: JSON.stringify(requestBody),
79
- });
80
- }
89
+ // Production mode: Use the robust API class
90
+ const response = await api.post('/api/v1/aai/askLemur', requestBody);
81
91
 
82
- if (!response.ok) {
83
- const errorText = await response.text();
84
- let backendError = `API call failed: ${response.status} ${response.statusText}`;
85
- try {
86
- const errorJson = JSON.parse(errorText);
87
- if (errorJson?.error) backendError = errorJson.error;
88
- } catch (e) {
89
- /* ignore */
92
+ if (!response.success) {
93
+ throw new Error(
94
+ response.error || 'Generation failed to return valid response.'
95
+ );
90
96
  }
91
- throw new Error(backendError);
97
+
98
+ // TractStackAPI unwraps the 'data' field automatically
99
+ resultData = response.data; // { response: ... }
92
100
  }
93
101
 
94
- const result = (await response.json()) as GenerationResponse;
95
- if (!result.success || !result.data?.response) {
96
- throw new Error(
97
- result.error || 'Generation failed to return valid response.'
98
- );
102
+ if (!resultData?.response) {
103
+ throw new Error('Generation failed to return a response object.');
99
104
  }
100
105
 
101
- let rawResponseData = result.data.response;
106
+ let rawResponseData = resultData.response;
107
+
102
108
  if (expectJson && typeof rawResponseData === 'object') {
103
109
  return JSON.stringify(rawResponseData);
104
110
  }
@@ -10,6 +10,7 @@ import {
10
10
  } from '@/components/compositor/preview/PaneSnapshotGenerator';
11
11
  import { PaneAddMode, type StoryFragmentNode } from '@/types/compositorTypes';
12
12
  import type { FullContentMapItem } from '@/types/tractstack';
13
+ import { TractStackAPI } from '@/utils/api';
13
14
 
14
15
  interface AddPaneReUsePanelProps {
15
16
  nodeId: string;
@@ -127,23 +128,15 @@ const AddPaneReUsePanel = ({
127
128
  const fetchFragments = async () => {
128
129
  try {
129
130
  const paneIds = visiblePreviews.map((preview) => preview.pane.id);
131
+ const api = new TractStackAPI();
130
132
 
131
- const goBackend =
132
- import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
133
- const response = await fetch(`${goBackend}/api/v1/fragments/panes`, {
134
- method: 'POST',
135
- headers: {
136
- 'Content-Type': 'application/json',
137
- 'X-Tenant-ID': import.meta.env.PUBLIC_TENANTID || 'default',
138
- },
139
- body: JSON.stringify({ paneIds }),
140
- });
133
+ const response = await api.post('/api/v1/fragments/panes', { paneIds });
141
134
 
142
- if (!response.ok) {
143
- throw new Error(`Fragment API failed: ${response.status}`);
135
+ if (!response.success) {
136
+ throw new Error(response.error || `Fragment API failed`);
144
137
  }
145
138
 
146
- const data = await response.json();
139
+ const data = response.data;
147
140
 
148
141
  setPreviews((prevPreviews) => {
149
142
  const updated = [...prevPreviews];
@@ -206,22 +199,16 @@ const AddPaneReUsePanel = ({
206
199
  if (!selectedPaneId) return;
207
200
 
208
201
  try {
209
- const goBackend =
210
- import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
211
- const response = await fetch(
212
- `${goBackend}/api/v1/nodes/panes/${selectedPaneId}/template`,
213
- {
214
- headers: {
215
- 'X-Tenant-ID': import.meta.env.PUBLIC_TENANTID || 'default',
216
- },
217
- }
202
+ const api = new TractStackAPI();
203
+ const response = await api.get(
204
+ `/api/v1/nodes/panes/${selectedPaneId}/template`
218
205
  );
219
206
 
220
- if (!response.ok) {
221
- throw new Error(`Template API failed: ${response.status}`);
207
+ if (!response.success) {
208
+ throw new Error(response.error || `Template API failed`);
222
209
  }
223
210
 
224
- const templateData = await response.json();
211
+ const templateData = response.data;
225
212
  const ctx = getCtx();
226
213
 
227
214
  // Find storyfragment
@@ -7,6 +7,7 @@ import { getBrandConfig } from '@/utils/api/brandConfig';
7
7
  import { freshInstallStore } from '@/stores/backend';
8
8
  import type { MenuNode } from '@/types/tractstack';
9
9
  import type { ImpressionNode } from '@/types/compositorTypes';
10
+ import { resolveTenantId } from '@/utils/tenantResolver';
10
11
  export interface Props {
11
12
  title: string;
12
13
  slug?: string;
@@ -49,8 +50,17 @@ const {
49
50
 
50
51
  const isInitialized = !freshInstallStore.get().needsSetup;
51
52
  const goBackend = import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
52
- const tenantId =
53
- Astro.locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
53
+ // Resolve tenant explicitly "Above the Fences" to guarantee the client gets the truth
54
+ const resolution = await resolveTenantId(Astro.request);
55
+ const tenantId = resolution.id;
56
+ if (!Astro.locals.tenant) {
57
+ Astro.locals.tenant = {
58
+ id: tenantId,
59
+ domain: Astro.url.hostname,
60
+ isMultiTenant: true,
61
+ isLocalhost: false,
62
+ };
63
+ }
54
64
  const brandConfig = propBrandConfig || (await getBrandConfig(tenantId));
55
65
  const cssBasePath = isInitialized ? '/media/css' : '/styles';
56
66
  const fontBasePath = isInitialized ? '/media/fonts' : '/fonts';
@@ -1,4 +1,5 @@
1
1
  import type { APIContext, MiddlewareNext } from '@/types/astro';
2
+ import { resolveTenantId } from '@/utils/tenantResolver';
2
3
 
3
4
  interface Locals {
4
5
  tenant?: {
@@ -9,14 +10,6 @@ interface Locals {
9
10
  };
10
11
  }
11
12
 
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
-
20
13
  export async function onRequest(
21
14
  context: APIContext & { locals: Locals },
22
15
  next: MiddlewareNext
@@ -25,74 +18,22 @@ export async function onRequest(
25
18
  import.meta.env.PUBLIC_ENABLE_MULTI_TENANT === 'true';
26
19
 
27
20
  if (isMultiTenantEnabled && !import.meta.env.DEV) {
28
- const hostname =
29
- context.request.headers.get('x-forwarded-host') ||
30
- context.request.headers.get('host');
31
-
32
- if (hostname) {
33
- let tenantId: string | null = null;
34
-
35
- // Strategy 1: Regex Pattern (Fastest)
36
- const parts = hostname.split('.');
37
- if (
38
- parts.length === 3 &&
39
- ['freewebpress', 'tractstack'].includes(parts[1]) &&
40
- parts[2] === 'com'
41
- ) {
42
- tenantId = parts[0];
43
- } else if (
44
- parts.length >= 4 &&
45
- parts[1] === 'sandbox' &&
46
- ['freewebpress', 'tractstack'].includes(parts[2]) &&
47
- parts[3] === 'com'
48
- ) {
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) {
87
- context.locals.tenant = {
88
- id: tenantId,
89
- domain: hostname,
90
- isMultiTenant: true,
91
- isLocalhost: false,
92
- };
93
-
94
- context.request.headers.set('X-Tenant-ID', tenantId);
95
- }
21
+ // Use the shared utility to resolve the tenant ID
22
+ const resolution = await resolveTenantId(context.request);
23
+
24
+ if (resolution.id && resolution.id !== 'default') {
25
+ const hostname =
26
+ context.request.headers.get('x-forwarded-host') ||
27
+ context.request.headers.get('host');
28
+
29
+ context.locals.tenant = {
30
+ id: resolution.id,
31
+ domain: hostname,
32
+ isMultiTenant: true,
33
+ isLocalhost: false,
34
+ };
35
+
36
+ context.request.headers.set('X-Tenant-ID', resolution.id);
96
37
  }
97
38
  }
98
39
 
@@ -20,7 +20,6 @@ export interface TractStackEvent {
20
20
  }
21
21
 
22
22
  function getConfig() {
23
- // Server-side safety check
24
23
  if (typeof window === 'undefined') {
25
24
  return {
26
25
  goBackend: import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080',
@@ -40,47 +39,26 @@ function getConfig() {
40
39
  };
41
40
  }
42
41
 
43
- //function getTenantFromDomain(): string {
44
- // if (typeof window === 'undefined') return 'default';
45
- //
46
- // const hostname = window.location.hostname;
47
- //
48
- // //if (hostname === 'localhost' || hostname === '127.0.0.1') {
49
- // // return 'default';
50
- // //}
51
- //
52
- // const parts = hostname.split('.');
53
- // if (
54
- // parts.length >= 4 &&
55
- // parts[1] === 'sandbox' &&
56
- // ['tractstack', 'freewebpress'].includes(parts[2]) &&
57
- // parts[3] === 'com'
58
- // ) {
59
- // return parts[0];
60
- // }
61
- //
62
- // return 'default';
63
- //}
64
-
65
42
  export class TractStackAPI {
66
- private baseUrl: string;
67
- private tenantId: string;
43
+ private explicitTenantId?: string;
68
44
 
69
45
  constructor(tenantId?: string) {
70
- const config = getConfig();
71
- this.baseUrl = config.goBackend;
72
- this.tenantId = tenantId || config.tenantId;
46
+ this.explicitTenantId = tenantId;
73
47
  }
74
48
 
75
49
  async request<T = any>(
76
50
  endpoint: string,
77
51
  options: RequestInit = {}
78
52
  ): Promise<APIResponse<T>> {
79
- const url = `${this.baseUrl}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`;
53
+ const config = getConfig();
54
+ const effectiveTenantId = this.explicitTenantId || config.tenantId;
55
+ const baseUrl = config.goBackend;
56
+
57
+ const url = `${baseUrl}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`;
80
58
 
81
59
  const defaultHeaders = {
82
60
  'Content-Type': 'application/json',
83
- 'X-Tenant-ID': this.tenantId,
61
+ 'X-Tenant-ID': effectiveTenantId,
84
62
  ...(typeof window !== 'undefined' &&
85
63
  (window as any).TRACTSTACK_CONFIG?.sessionId && {
86
64
  'X-TractStack-Session-ID': (window as any).TRACTSTACK_CONFIG
@@ -170,11 +148,12 @@ export class TractStackAPI {
170
148
  }
171
149
 
172
150
  getTenantId(): string {
173
- return this.tenantId;
151
+ const config = getConfig();
152
+ return this.explicitTenantId || config.tenantId;
174
153
  }
175
154
 
176
155
  setTenantId(tenantId: string): void {
177
- this.tenantId = tenantId;
156
+ this.explicitTenantId = tenantId;
178
157
  }
179
158
 
180
159
  async getContentMapWithTimestamp(
@@ -185,11 +164,7 @@ export class TractStackAPI {
185
164
  endpoint += `?lastUpdated=${lastUpdated}`;
186
165
  }
187
166
 
188
- // Use the raw request method to get the full response
189
167
  const response = await this.request(endpoint);
190
-
191
- // For this endpoint, the backend returns {data: [...], lastUpdated: 123} directly
192
- // So response.data IS the {data: [...], lastUpdated: 123} object
193
168
  return response as APIResponse<{ data: any[]; lastUpdated: number }>;
194
169
  }
195
170
  }
@@ -0,0 +1,113 @@
1
+ const VERBOSE = true;
2
+
3
+ interface TenantResolution {
4
+ id: string;
5
+ source: 'regex' | 'cache' | 'fetch' | 'default';
6
+ }
7
+
8
+ interface CacheEntry {
9
+ tenantId: string;
10
+ timestamp: number;
11
+ }
12
+
13
+ const domainCache = new Map<string, CacheEntry>();
14
+ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
15
+
16
+ export async function resolveTenantId(
17
+ request: Request
18
+ ): Promise<TenantResolution> {
19
+ const hostname =
20
+ request.headers.get('x-forwarded-host') || request.headers.get('host');
21
+
22
+ if (VERBOSE) console.log(`[TenantResolver] Resolving: ${hostname}`);
23
+
24
+ if (!hostname) return { id: 'default', source: 'default' };
25
+
26
+ // Strategy 1: Regex Pattern (Fastest - Zero Latency)
27
+ const parts = hostname.split('.');
28
+
29
+ // Standard Subdomain (e.g. pro.freewebpress.com or pro.freewebpress.com:443)
30
+ if (
31
+ parts.length === 3 &&
32
+ ['freewebpress', 'tractstack'].includes(parts[1]) &&
33
+ parts[2].startsWith('com')
34
+ ) {
35
+ if (VERBOSE) console.log(`[TenantResolver] Regex Match: ${parts[0]}`);
36
+ return { id: parts[0], source: 'regex' };
37
+ }
38
+
39
+ // Sandbox Subdomain (e.g. id.sandbox.freewebpress.com)
40
+ if (
41
+ parts.length >= 4 &&
42
+ parts[1] === 'sandbox' &&
43
+ ['freewebpress', 'tractstack'].includes(parts[2]) &&
44
+ parts[3].startsWith('com')
45
+ ) {
46
+ if (VERBOSE)
47
+ console.log(`[TenantResolver] Regex Sandbox Match: ${parts[0]}`);
48
+ return { id: parts[0], source: 'regex' };
49
+ }
50
+
51
+ // Strategy 2: Cache Lookup (Fast - In Memory)
52
+ const cached = domainCache.get(hostname);
53
+ if (cached) {
54
+ if (Date.now() - cached.timestamp < CACHE_TTL) {
55
+ if (VERBOSE)
56
+ console.log(`[TenantResolver] Cache Hit: ${cached.tenantId}`);
57
+ return { id: cached.tenantId, source: 'cache' };
58
+ } else {
59
+ domainCache.delete(hostname);
60
+ if (VERBOSE)
61
+ console.log(`[TenantResolver] Cache Expired for: ${hostname}`);
62
+ }
63
+ }
64
+
65
+ // Strategy 3: Backend Lookup (Fallback - Network Request)
66
+ try {
67
+ const backendUrl =
68
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:10000';
69
+ const urlObj = new URL(backendUrl);
70
+ // Force localhost to avoid Hairpin NAT / Loopback firewall blocks
71
+ const localBackend = `${urlObj.protocol}//127.0.0.1:${urlObj.port}`;
72
+
73
+ if (VERBOSE) console.log(`[TenantResolver] Fetching from: ${localBackend}`);
74
+
75
+ // Temporarily disable TLS validation because 127.0.0.1 won't match the cert
76
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
77
+
78
+ const response = await fetch(
79
+ `${localBackend}/api/v1/resolve-domain?host=${encodeURIComponent(hostname)}`
80
+ );
81
+
82
+ // Restore security immediately
83
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
84
+
85
+ if (response.ok) {
86
+ const data = await response.json();
87
+ if (data.tenantId) {
88
+ if (VERBOSE)
89
+ console.log(`[TenantResolver] Fetch Success: ${data.tenantId}`);
90
+
91
+ // Cache the result
92
+ domainCache.set(hostname, {
93
+ tenantId: data.tenantId,
94
+ timestamp: Date.now(),
95
+ });
96
+
97
+ return { id: data.tenantId, source: 'fetch' };
98
+ }
99
+ } else {
100
+ if (VERBOSE)
101
+ console.log(`[TenantResolver] Fetch Failed: ${response.status}`);
102
+ }
103
+ } catch (error) {
104
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
105
+ console.error(
106
+ `[TenantResolver] Error resolving domain ${hostname}:`,
107
+ error
108
+ );
109
+ }
110
+
111
+ if (VERBOSE) console.warn(`[TenantResolver] Failed to resolve. Defaulting.`);
112
+ return { id: 'default', source: 'default' };
113
+ }
@@ -758,6 +758,10 @@ export async function injectTemplateFiles(
758
758
  src: resolve('../templates/src/utils/api.ts'),
759
759
  dest: 'src/utils/api.ts',
760
760
  },
761
+ {
762
+ src: resolve('../templates/src/utils/tenantResolver.ts'),
763
+ dest: 'src/utils/tenantResolver.ts',
764
+ },
761
765
  {
762
766
  src: resolve('../templates/src/utils/api/brandConfig.ts'),
763
767
  dest: 'src/utils/api/brandConfig.ts',