1// Backend / transport target resolution for the atome commit pipeline (extracted 2// from atome_commit.js, RF): Fastify state-current miss cache, local-Axum / loopback 3// / cross-origin classification, Fastify mirror + auth window-flag management, 4// project-id / actor resolution, and the canonical backend-preference / base-url / 5// token resolvers. Pure-ish DAG layer (state + url + runtime + serverUrls + adapters); 6// never calls the transport/request layer, so it has no import cycle with apiRequest. 7import { 8 getBrowserSameOriginServerUrl, 9 canUseFastifyPrimaryOnLocalAxumPage, 10 getCloudServerPort, 11 getCloudServerUrl, 12 isLocalAxumPage, 13 getLocalServerPort, 14 getLocalServerUrl, 15 isCurrentLoopbackPagePort, 16 resolveCanonicalFastifyHttpBase 17} from '#squirrel/apis/serverUrls.js'; 18import { FastifyAdapter, TauriAdapter, CONFIG } from '#squirrel/apis/unified/adole.js'; 19import { isTauriRuntime, persistenceTraceLog } from './atome_commit_runtime.js'; 20import { 21 normalizeBaseUrl, 22 isCrossOriginUrl, 23 isLoopbackHttpUrl 24} from './atome_commit_url.js'; 25import { 26 FASTIFY_MIRROR_CORS_BLOCK_MS, 27 FASTIFY_STATE_CURRENT_MISSING_CACHE_MS, 28 fastifyStateCurrentMissingUntil 29} from './atome_commit_state.js'; 30 31export const readFastifyStateCurrentMissing = (atomeId) => { 32 const key = String(atomeId || '').trim(); 33 if (!key) return false; 34 const until = Number(fastifyStateCurrentMissingUntil.get(key) || 0); 35 if (!Number.isFinite(until) || until <= Date.now()) { 36 fastifyStateCurrentMissingUntil.delete(key); 37 return false; 38 } 39 return true; 40}; 41 42export const markFastifyStateCurrentMissing = (atomeId) => { 43 const key = String(atomeId || '').trim(); 44 if (!key) return; 45 fastifyStateCurrentMissingUntil.set(key, Date.now() + FASTIFY_STATE_CURRENT_MISSING_CACHE_MS); 46}; 47 48export const clearFastifyStateCurrentMissing = (atomeId = null) => { 49 const key = String(atomeId || '').trim(); 50 if (key) { 51 fastifyStateCurrentMissingUntil.delete(key); 52 return; 53 } 54 fastifyStateCurrentMissingUntil.clear(); 55}; 56 57export const resolvePinnedLocalAxumBaseUrl = () => { 58 const localBase = normalizeBaseUrl(getLocalServerUrl()); 59 if (localBase) return localBase; 60 if (typeof window === 'undefined') return ''; 61 const host = String(window.location?.hostname || '').trim().toLowerCase(); 62 const protocol = String(window.location?.protocol || '').trim().toLowerCase() === 'https:' ? 'https:' : 'http:'; 63 const localPort = getLocalServerPort(); 64 if (!host || !Number.isFinite(Number(localPort)) || Number(localPort) <= 0) return ''; 65 const resolvedHost = host === '0.0.0.0' ? '127.0.0.1' : host; 66 return normalizeBaseUrl(`${protocol}//${resolvedHost}:${localPort}`); 67}; 68 69export const markFastifyMirrorTemporarilyBlocked = (reason = 'cors_fetch_failure', baseUrl = '') => { 70 if (typeof window === 'undefined') return; 71 const until = Date.now() + FASTIFY_MIRROR_CORS_BLOCK_MS; 72 try { 73 window.__SQUIRREL_FASTIFY_MIRROR_BLOCKED_UNTIL__ = until; 74 window.__SQUIRREL_FASTIFY_MIRROR_BLOCK_REASON__ = String(reason || 'cors_fetch_failure'); 75 window.__SQUIRREL_FASTIFY_MIRROR_BLOCK_BASE__ = String(baseUrl || '').trim() || null; 76 } catch (_) { } 77}; 78 79export const isFastifyMirrorTemporarilyBlocked = () => { 80 if (typeof window === 'undefined') return false; 81 const until = Number(window.__SQUIRREL_FASTIFY_MIRROR_BLOCKED_UNTIL__ || 0); 82 return Number.isFinite(until) && until > Date.now(); 83}; 84 85export const readLocalTauriHttpPort = () => { 86 if (typeof window === 'undefined') return null; 87 const localUrl = getLocalServerUrl(); 88 if (!localUrl && !isTauriRuntime()) return null; 89 return getLocalServerPort(); 90}; 91 92export const canUseLocalAxumHttpBackend = () => { 93 if (isTauriRuntime()) return true; 94 if (typeof window === 'undefined') return false; 95 if (isLocalAxumPage()) return true; 96 const localPort = getLocalServerPort(); 97 return isCurrentLoopbackPagePort(localPort); 98}; 99 100export const isInvalidFastifyLoopbackBase = (baseUrl) => { 101 if (typeof baseUrl !== 'string' || !baseUrl.trim()) return false;
102 if (!canUseLocalAxumHttpBackend()) return false; 103 const localPort = readLocalTauriHttpPort(); 104 if (!localPort) return false; 105 try { 106 const parsed = new URL(baseUrl.trim()); 107 if (!isLoopbackHttpUrl(baseUrl)) return false; 108 const port = Number(parsed.port || (parsed.protocol === 'https:' ? 443 : 80)); 109 return Number.isFinite(port) && port === localPort; 110 } catch (_) { 111 return false; 112 } 113}; 114 115export const resolveExpectedFastifyLoopbackPort = () => { 116 return getCloudServerPort(); 117}; 118 119export const readCurrentPagePort = () => { 120 if (typeof window === 'undefined') return null; 121 const protocol = String(window.location?.protocol || '').toLowerCase(); 122 const defaultPort = protocol === 'https:' ? 443 : 80; 123 const parsed = Number(window.location?.port || defaultPort); 124 if (!Number.isFinite(parsed) || parsed <= 0) return defaultPort; 125 return parsed; 126}; 127 128export const resolveSameOriginBrowserBaseUrl = () => { 129 if (typeof window === 'undefined') return ''; 130 const origin = normalizeBaseUrl(window.location?.origin || ''); 131 return origin || ''; 132}; 133 134export const resolveBrowserFastifySameOriginBaseUrl = () => { 135 const sameOriginBase = getBrowserSameOriginServerUrl(); 136 return normalizeBaseUrl(sameOriginBase || ''); 137}; 138 139export const isLikelyTauriLoopbackBaseForWeb = (baseUrl) => { 140 if (typeof baseUrl !== 'string' || !baseUrl.trim()) return false; 141 if (isTauriRuntime()) return false; 142 if (!isLoopbackHttpUrl(baseUrl)) return false; 143 try { 144 const parsed = new URL(baseUrl.trim()); 145 const port = Number(parsed.port || (parsed.protocol === 'https:' ? 443 : 80)); 146 if (!Number.isFinite(port) || port <= 0) return false; 147 const tauriPort = readLocalTauriHttpPort() || 3000; 148 const expectedFastifyPort = resolveExpectedFastifyLoopbackPort(); 149 return port === tauriPort && port !== expectedFastifyPort; 150 } catch (_) { 151 return false; 152 } 153}; 154 155export const isBlockedCrossOriginLoopbackFastifyBaseForBrowser = (baseUrl) => { 156 if (typeof baseUrl !== 'string' || !baseUrl.trim()) return false; 157 if (typeof window === 'undefined') return false; 158 if (isTauriRuntime()) return false; 159 if (canUseExplicitFastifyPrimaryOnLocalAxumPage()) return false; 160 return isLoopbackHttpUrl(baseUrl) && isCrossOriginUrl(baseUrl); 161}; 162 163export const canMirrorFastifyFromTauri = (baseUrl) => { 164 if (!isTauriRuntime()) return true; 165 if (isInvalidFastifyLoopbackBase(baseUrl)) return false; 166 if (isFastifyMirrorTemporarilyBlocked()) return false; 167 if (window.__SQUIRREL_DISABLE_FASTIFY_MIRROR__ === true) return false; 168 if (isLoopbackHttpUrl(baseUrl) && isCrossOriginUrl(baseUrl)) { 169 return false; 170 } 171 if ( 172 window.__SQUIRREL_BLOCK_FASTIFY_MIRROR_CROSS_ORIGIN__ === true 173 && isLoopbackHttpUrl(baseUrl) 174 && isCrossOriginUrl(baseUrl) 175 ) { 176 return false; 177 } 178 return true; 179}; 180 181export const resolveProjectId = (defaultProjectId = null) => { 182 if (defaultProjectId) return defaultProjectId; 183 if (typeof window === 'undefined') return null; 184 if (window.__currentProject?.id) return window.__currentProject.id; 185 const api = window.AdoleAPI; 186 if (api?.projects?.getCurrentId) return api.projects.getCurrentId(); 187 if (api?.projects?.getCurrent) { 188 const current = api.projects.getCurrent(); 189 return current?.id || current?.projectId || null; 190 } 191 return null; 192}; 193 194export const resolveActor = (input) => { 195 if (input) return input; 196 if (typeof window === 'undefined') return null; 197 const authGateUserId = window.__authCheckResult?.userId || null; 198 if (authGateUserId) { 199 return { type: 'user', id: authGateUserId }; 200 } 201 const api = window.AdoleAPI; 202 const info = api?.auth?.getCurrentInfo?.(); 203 const id = info?.id || info?.user_id || info?.userId || info?.atome_id || info?.atomeId || null; 204 return { type: 'user', id }; 205}; 206 207export const isFastifyAuthMarkedInvalid = () => { 208 if (typeof window === 'undefined') return false; 209 return window.__SQUIRREL_FASTIFY_AUTH_INVALID__ === true; 210}; 211 212export const clearFastifyStoredTokens = () => { 213 try { FastifyAdapter?.clearToken?.(); } catch (_) { } 214 if (typeof localStorage !== 'undefined') { 215 try { localStorage.removeItem(CONFIG?.FASTIFY_TOKEN_KEY || 'cloud_auth_token'); } catch (_) { } 216 } 217 if (typeof sessionStorage !== 'undefined') { 218 try { sessionStorage.removeItem(CONFIG?.FASTIFY_TOKEN_KEY || 'cloud_auth_token'); } catch (_) { } 219 } 220}; 221 222export const invalidateFastifyAuth = () => { 223 clearFastifyStoredTokens(); 224 try { 225 if (typeof window !== 'undefined') window.__SQUIRREL_FASTIFY_AUTH_INVALID__ = true; 226 } catch (_) { } 227 try { 228 if (typeof window !== 'undefined') window.__SQUIRREL_FASTIFY_MIRROR_READY__ = false;
229 } catch (_) { } 230}; 231 232export const isFastifyMirrorReady = () => { 233 if (typeof window === 'undefined') return false; 234 return window.__SQUIRREL_FASTIFY_MIRROR_READY__ === true; 235}; 236 237export const allowFastifyPrimaryOnLocalAxumPage = () => { 238 if (typeof window === 'undefined') return false; 239 return window.__SQUIRREL_ALLOW_FASTIFY_PRIMARY_ON_LOCAL_AXUM__ === true; 240}; 241 242export const resetFastifyPrimaryOnLocalAxumOverride = () => { 243 if (typeof window === 'undefined') return; 244 try { window.__SQUIRREL_ALLOW_FASTIFY_PRIMARY_ON_LOCAL_AXUM__ = false; } catch (_) { } 245 try { window.__SQUIRREL_FASTIFY_URL__ = ''; } catch (_) { } 246 try { window.__SQUIRREL_TAURI_FASTIFY_URL__ = ''; } catch (_) { } 247 try { window.__SQUIRREL_FASTIFY_WS_API_URL__ = ''; } catch (_) { } 248 try { window.__SQUIRREL_FASTIFY_WS_SYNC_URL__ = ''; } catch (_) { } 249 try { 250 if (window.__SQUIRREL_AUTH_SOURCE__ === 'fastify') window.__SQUIRREL_AUTH_SOURCE__ = 'tauri'; 251 if (window.__SQUIRREL_DATA_SOURCE__ === 'fastify') window.__SQUIRREL_DATA_SOURCE__ = 'tauri'; 252 if (window.__SQUIRREL_PROFILE_SOURCE__ === 'fastify') window.__SQUIRREL_PROFILE_SOURCE__ = 'tauri'; 253 } catch (_) { } 254 try { localStorage.removeItem('squirrel_tauri_fastify_url_override'); } catch (_) { } 255}; 256 257export const canUseExplicitFastifyPrimaryOnLocalAxumPage = () => { 258 return canUseFastifyPrimaryOnLocalAxumPage(); 259}; 260 261export const traceBackendDecision = (stage, data = {}) => { 262 persistenceTraceLog(`backend:${stage}`, data); 263}; 264 265export const resolveBackendPreference = (preferred = null) => { 266 if (preferred === 'tauri') return preferred; 267 if (preferred === 'fastify') { 268 const tauriRuntime = isTauriRuntime(); 269 const browserOnLocalAxum = !tauriRuntime && isLocalAxumPage(); 270 if (browserOnLocalAxum && !canUseExplicitFastifyPrimaryOnLocalAxumPage()) { 271 traceBackendDecision('resolve_preference:explicit_fastify_blocked_on_local_axum', { 272 pageOrigin: typeof window !== 'undefined' ? window.location?.origin || null : null, 273 authSource: typeof window !== 'undefined' ? window.__SQUIRREL_AUTH_SOURCE__ || null : null, 274 dataSource: typeof window !== 'undefined' ? window.__SQUIRREL_DATA_SOURCE__ || null : null 275 }); 276 resetFastifyPrimaryOnLocalAxumOverride(); 277 return 'tauri'; 278 } 279 return preferred; 280 } 281 const tauriRuntime = isTauriRuntime(); 282 const browserOnLocalAxum = !tauriRuntime && isLocalAxumPage(); 283 const configuredSource = (() => { 284 if (typeof window === 'undefined') return null; 285 const value = window.__SQUIRREL_DATA_SOURCE__ || window.__SQUIRREL_AUTH_SOURCE__ || null; 286 const normalized = String(value || '').trim().toLowerCase(); 287 if (normalized === 'tauri' || normalized === 'fastify') return normalized; 288 return null; 289 })(); 290 const tauriToken = (() => { 291 const direct = TauriAdapter?.getToken?.(); 292 if (direct) return direct; 293 if (typeof localStorage === 'undefined') return null; 294 const key = CONFIG?.TAURI_TOKEN_KEY || 'local_auth_token'; 295 return localStorage.getItem(key); 296 })(); 297 let fastifyToken = (() => { 298 const direct = FastifyAdapter?.getToken?.(); 299 if (direct) return direct; 300 if (typeof localStorage === 'undefined') return null; 301 const key = CONFIG?.FASTIFY_TOKEN_KEY || 'cloud_auth_token'; 302 const token = localStorage.getItem(key); 303 if (token) return token; 304 return null; 305 })(); 306 if (typeof window !== 'undefined' && window.__SQUIRREL_FASTIFY_AUTH_INVALID__ === true) { 307 fastifyToken = null; 308 } 309 310 traceBackendDecision('resolve_preference:input', { 311 preferred, 312 tauriRuntime, 313 browserOnLocalAxum, 314 configuredSource, 315 hasTauriToken: !!tauriToken, 316 hasFastifyToken: !!fastifyToken, 317 rawAllowFastifyPrimaryOnLocalAxum: allowFastifyPrimaryOnLocalAxumPage(), 318 canUseExplicitFastifyPrimaryOnLocalAxumPage: canUseExplicitFastifyPrimaryOnLocalAxumPage(), 319 pageOrigin: typeof window !== 'undefined' ? window.location?.origin || null : null, 320 authSource: typeof window !== 'undefined' ? window.__SQUIRREL_AUTH_SOURCE__ || null : null, 321 dataSource: typeof window !== 'undefined' ? window.__SQUIRREL_DATA_SOURCE__ || null : null 322 }); 323 324 // Tauri runtime contract: local commits must stay on Axum by default. 325 if (tauriRuntime) { 326 const allowFastifyPrimary = typeof window !== 'undefined' 327 && window.__SQUIRREL_ALLOW_FASTIFY_PRIMARY_IN_TAURI__ === true; 328 if (configuredSource === 'tauri') return 'tauri'; 329 if (configuredSource === 'fastify' && allowFastifyPrimary) return 'fastify'; 330 if (tauriToken) return 'tauri'; 331 if (allowFastifyPrimary && fastifyToken) return 'fastify'; 332 return 'tauri'; 333 } 334 335 if (browserOnLocalAxum) { 336 if (configuredSource === 'fastify') { 337 if (canUseExplicitFastifyPrimaryOnLocalAxumPage()) { 338 traceBackendDecision('resolve_preference:local_axum_fastify_primary', { 339 configuredSource, 340 pageOrigin: typeof window !== 'undefined' ? window.location?.origin || null : null, 341 cloudBase: getCloudServerUrl() || null 342 }); 343 return 'fastify'; 344 } 345 traceBackendDecision('resolve_preference:local_axum_fastify_reset', { 346 configuredSource, 347 pageOrigin: typeof window !== 'undefined' ? window.location?.origin || null : null, 348 cloudBase: getCloudServerUrl() || null, 349 rawAllowFastifyPrimaryOnLocalAxum: allowFastifyPrimaryOnLocalAxumPage() 350 }); 351 resetFastifyPrimaryOnLocalAxumOverride(); 352 } 353 traceBackendDecision('resolve_preference:local_axum_force_tauri', { 354 configuredSource, 355 pageOrigin: typeof window !== 'undefined' ? window.location?.origin || null : null, 356 cloudBase: getCloudServerUrl() || null 357 }); 358 return 'tauri'; 359 } 360 361 // Browser/web runtime must never route mutations to Axum local endpoints. 362 // Keep backend selection cloud-oriented outside Tauri. 363 if (configuredSource === 'fastify') return 'fastify'; 364 return 'fastify'; 365}; 366 367export const resolveBaseUrl = (backend = null) => { 368 const resolvedBackend = resolveBackendPreference(backend); 369 if (resolvedBackend === 'tauri') { 370 const adapterBase = TauriAdapter?.baseUrl; 371 if (typeof adapterBase === 'string' && adapterBase.trim()) { 372 const normalized = normalizeBaseUrl(adapterBase); 373 if (normalized) return normalized; 374 } 375 const localBase = getLocalServerUrl(); 376 if (localBase) return normalizeBaseUrl(localBase); 377 const localPort = readLocalTauriHttpPort(); 378 if (localPort) return normalizeBaseUrl(`http://127.0.0.1:${localPort}`); 379 return normalizeBaseUrl(CONFIG?.TAURI_BASE_URL || 'http://127.0.0.1:3000'); 380 } 381 382 const browserSameOriginFastifyBase = resolveBrowserFastifySameOriginBaseUrl(); 383 if (browserSameOriginFastifyBase) { 384 return browserSameOriginFastifyBase; 385 } 386 387 const adapterBase = FastifyAdapter?.baseUrl; 388 if (typeof adapterBase === 'string' && adapterBase.trim()) { 389 const normalized = normalizeBaseUrl(resolveCanonicalFastifyHttpBase(adapterBase)); 390 if ( 391 normalized 392 && !isInvalidFastifyLoopbackBase(normalized) 393 && !isLikelyTauriLoopbackBaseForWeb(normalized) 394 && !isBlockedCrossOriginLoopbackFastifyBaseForBrowser(normalized) 395 ) { 396 return normalized; 397 } 398 } 399 if (typeof window === 'undefined') return ''; 400 const explicit = window.__SQUIRREL_FASTIFY_URL__; 401 if (typeof explicit === 'string' && explicit.trim()) { 402 const normalized = normalizeBaseUrl(resolveCanonicalFastifyHttpBase(explicit)); 403 if ( 404 normalized 405 && !isInvalidFastifyLoopbackBase(normalized) 406 && !isLikelyTauriLoopbackBaseForWeb(normalized) 407 && !isBlockedCrossOriginLoopbackFastifyBaseForBrowser(normalized) 408 ) { 409 return normalized; 410 } 411 try { localStorage.removeItem('squirrel_tauri_fastify_url_override'); } catch (_) { } 412 } 413 const cloudBase = getCloudServerUrl(); 414 if (cloudBase) { 415 const normalized = normalizeBaseUrl(resolveCanonicalFastifyHttpBase(cloudBase)); 416 if ( 417 normalized 418 && !isInvalidFastifyLoopbackBase(normalized) 419 && !isLikelyTauriLoopbackBaseForWeb(normalized) 420 && !isBlockedCrossOriginLoopbackFastifyBaseForBrowser(normalized) 421 ) { 422 return normalized; 423 } 424 } 425 // In Tauri mode, default to local Fastify server on port 3001 426 if (isTauriRuntime()) { 427 const config = window.__SQUIRREL_SERVER_CONFIG__; 428 const host = config?.fastify?.host || '127.0.0.1'; 429 let port = Number(config?.fastify?.port || 3001); 430 if (!Number.isFinite(port) || port <= 0) port = 3001; 431 const localPort = readLocalTauriHttpPort(); 432 const hostLower = String(host).toLowerCase(); 433 const loopbackHost = hostLower === '127.0.0.1' || hostLower === 'localhost' || hostLower === '0.0.0.0'; 434 if (localPort && loopbackHost && port === localPort) { 435 port = 3001; 436 } 437 return `http://127.0.0.1:${port}`; 438 } 439 const loc = window.location; 440 const hostname = loc?.hostname || ''; 441 if (loc?.origin && hostname) { 442 return normalizeBaseUrl(loc.origin); 443 } 444 return ''; 445}; 446 447export const resolveToken = (backend = null) => { 448 const resolvedBackend = resolveBackendPreference(backend); 449 if (resolvedBackend === 'tauri') return TauriAdapter.getToken(); 450 return isFastifyAuthMarkedInvalid() ? null : FastifyAdapter.getToken(); 451};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.