2026-07-10 12:24:50 +03:00
|
|
|
import { createConfiguredWebAPIs, getDesktopRelayRestoreReady } from './runtimeConfig';
|
2026-01-22 01:19:24 +02:00
|
|
|
import { registerSW } from 'virtual:pwa-register';
|
|
|
|
|
|
2025-12-07 19:32:53 +02:00
|
|
|
import type { RuntimeAPIs } from '@openchamber/ui/lib/api/types';
|
2026-06-02 00:43:05 +03:00
|
|
|
import { getStoredMobileLayoutPreference } from '@openchamber/ui/lib/mobileLayoutPreference';
|
|
|
|
|
import type { HostedSurface } from '@openchamber/ui/lib/runtimeSurface';
|
2026-07-30 12:20:54 +03:00
|
|
|
import {
|
|
|
|
|
isEmbeddedSessionChat,
|
|
|
|
|
requestEmbeddedSessionRuntimeBootstrap,
|
|
|
|
|
} from '@openchamber/ui/components/layout/contextPanelEmbeddedChat';
|
2025-12-07 19:32:53 +02:00
|
|
|
import '@openchamber/ui/index.css';
|
|
|
|
|
import '@openchamber/ui/styles/fonts';
|
|
|
|
|
|
|
|
|
|
declare global {
|
|
|
|
|
interface Window {
|
|
|
|
|
__OPENCHAMBER_RUNTIME_APIS__?: RuntimeAPIs;
|
2026-06-02 00:43:05 +03:00
|
|
|
__OPENCHAMBER_SURFACE__?: HostedSurface;
|
2025-12-07 19:32:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 00:43:05 +03:00
|
|
|
const isCoarsePointer = (): boolean => {
|
|
|
|
|
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return window.matchMedia('(pointer: coarse)').matches;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const detectHostedSurface = (): HostedSurface => {
|
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
|
const override = params.get('surface');
|
|
|
|
|
if (override === 'mobile') return 'mobile';
|
|
|
|
|
if (override === 'desktop') return 'desktop';
|
|
|
|
|
|
|
|
|
|
const width = Math.min(window.innerWidth || 0, window.screen?.width || window.innerWidth || 0);
|
|
|
|
|
const touchPoints = navigator.maxTouchPoints || 0;
|
|
|
|
|
const likelyPhone = width > 0 && width <= 760 && (touchPoints > 0 || isCoarsePointer());
|
|
|
|
|
return likelyPhone && getStoredMobileLayoutPreference() === 'new' ? 'mobile' : 'desktop';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const hostedSurface = detectHostedSurface();
|
|
|
|
|
window.__OPENCHAMBER_SURFACE__ = hostedSurface;
|
2026-01-22 01:19:24 +02:00
|
|
|
|
2026-04-25 20:44:17 +03:00
|
|
|
type PrerenderingDocument = Document & {
|
|
|
|
|
prerendering?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const canUseServiceWorker = (): boolean => {
|
|
|
|
|
if (!('serviceWorker' in navigator)) return false;
|
|
|
|
|
if (!window.isSecureContext) return false;
|
|
|
|
|
if (window.location.protocol !== 'http:' && window.location.protocol !== 'https:') return false;
|
|
|
|
|
|
|
|
|
|
const documentState = document as PrerenderingDocument;
|
|
|
|
|
if (documentState.prerendering || String(document.visibilityState) === 'prerender') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const runWhenDocumentCanRegisterServiceWorker = (task: () => void): void => {
|
|
|
|
|
let completed = false;
|
|
|
|
|
const run = () => {
|
|
|
|
|
if (completed) return;
|
|
|
|
|
if (canUseServiceWorker()) {
|
|
|
|
|
completed = true;
|
|
|
|
|
task();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const afterLoad = () => {
|
|
|
|
|
setTimeout(run, 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (document.readyState === 'complete') {
|
|
|
|
|
afterLoad();
|
|
|
|
|
} else {
|
|
|
|
|
window.addEventListener('load', afterLoad, { once: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const documentState = document as PrerenderingDocument;
|
|
|
|
|
if (documentState.prerendering || String(document.visibilityState) === 'prerender') {
|
|
|
|
|
document.addEventListener('visibilitychange', run, { once: true });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const registerPwaServiceWorker = (): void => {
|
|
|
|
|
runWhenDocumentCanRegisterServiceWorker(() => {
|
|
|
|
|
try {
|
|
|
|
|
registerSW({
|
|
|
|
|
onRegisterError(error: unknown) {
|
|
|
|
|
console.warn('[PWA] service worker registration skipped:', error);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('[PWA] service worker registration skipped:', error);
|
|
|
|
|
}
|
2026-02-27 20:45:03 +02:00
|
|
|
});
|
2026-04-25 20:44:17 +03:00
|
|
|
};
|
2026-01-22 01:19:24 +02:00
|
|
|
|
2026-04-25 20:44:17 +03:00
|
|
|
const unregisterDevelopmentServiceWorkers = (): void => {
|
|
|
|
|
runWhenDocumentCanRegisterServiceWorker(() => {
|
|
|
|
|
void navigator.serviceWorker.getRegistrations()
|
|
|
|
|
.then((registrations) => Promise.all(registrations.map((registration) => registration.unregister())))
|
|
|
|
|
.catch(() => {});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-30 12:20:54 +03:00
|
|
|
const start = async (): Promise<void> => {
|
|
|
|
|
const embeddedBootstrap = isEmbeddedSessionChat()
|
|
|
|
|
? await requestEmbeddedSessionRuntimeBootstrap()
|
|
|
|
|
: null;
|
|
|
|
|
window.__OPENCHAMBER_RUNTIME_APIS__ = createConfiguredWebAPIs(embeddedBootstrap);
|
|
|
|
|
|
|
|
|
|
if (hostedSurface === 'mobile') {
|
|
|
|
|
const { renderMobileApp } = await import('@openchamber/ui/apps/renderMobileApp');
|
|
|
|
|
renderMobileApp(window.__OPENCHAMBER_RUNTIME_APIS__);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hold the render until a desktop relay-host restore has picked its transport.
|
|
|
|
|
await getDesktopRelayRestoreReady();
|
|
|
|
|
await import('@openchamber/ui/main');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void start();
|
2026-04-25 20:44:17 +03:00
|
|
|
|
|
|
|
|
if (import.meta.env.PROD) {
|
|
|
|
|
registerPwaServiceWorker();
|
|
|
|
|
} else {
|
|
|
|
|
unregisterDevelopmentServiceWorkers();
|
|
|
|
|
}
|