chore: exclude local resource artifacts from main sync
This commit is contained in:
294
src/app/main/previewRuntime.ts
Normal file
294
src/app/main/previewRuntime.ts
Normal file
@@ -0,0 +1,294 @@
|
||||
const PREVIEW_RUNTIME_QUERY_KEY = 'appPreviewMode';
|
||||
const PREVIEW_RUNTIME_PARENT_ORIGIN_KEY = 'previewParentOrigin';
|
||||
const PREVIEW_RUNTIME_TOKEN_QUERY_KEY = 'registeredAccessToken';
|
||||
const PREVIEW_RUNTIME_DEVICE_MODE_QUERY_KEY = 'previewDeviceMode';
|
||||
const PREVIEW_TARGET_TYPE_QUERY_KEY = 'previewTargetType';
|
||||
const PREVIEW_TARGET_COMPONENT_ID_QUERY_KEY = 'previewComponentId';
|
||||
const PREVIEW_TARGET_SAMPLE_ID_QUERY_KEY = 'previewSampleId';
|
||||
const PREVIEW_RUNTIME_CACHE_RESET_MARKER_KEY = 'work-app.preview-runtime.cache-reset.v1';
|
||||
const PREVIEW_RUNTIME_CACHE_RESET_PARAM_KEY = '__previewRuntimeCacheReset';
|
||||
const PREVIEW_RUNTIME_PRESERVED_QUERY_KEYS = [
|
||||
PREVIEW_RUNTIME_QUERY_KEY,
|
||||
PREVIEW_RUNTIME_PARENT_ORIGIN_KEY,
|
||||
PREVIEW_RUNTIME_TOKEN_QUERY_KEY,
|
||||
PREVIEW_RUNTIME_DEVICE_MODE_QUERY_KEY,
|
||||
] as const;
|
||||
|
||||
export type PreviewTargetDescriptor =
|
||||
| {
|
||||
type: 'widget';
|
||||
componentId: string;
|
||||
sampleId?: string;
|
||||
}
|
||||
| null;
|
||||
|
||||
export function isPreviewRuntime() {
|
||||
if (typeof window === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return new URLSearchParams(window.location.search).get(PREVIEW_RUNTIME_QUERY_KEY) === '1';
|
||||
}
|
||||
|
||||
export function isPreviewAppOrigin() {
|
||||
if (typeof window === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return window.location.origin === resolvePreviewAppOrigin();
|
||||
}
|
||||
|
||||
function readPreviewRuntimeCacheResetMarker() {
|
||||
if (typeof window === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
return window.sessionStorage.getItem(PREVIEW_RUNTIME_CACHE_RESET_MARKER_KEY)?.trim() ?? '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function writePreviewRuntimeCacheResetMarker(value: string) {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (value) {
|
||||
window.sessionStorage.setItem(PREVIEW_RUNTIME_CACHE_RESET_MARKER_KEY, value);
|
||||
} else {
|
||||
window.sessionStorage.removeItem(PREVIEW_RUNTIME_CACHE_RESET_MARKER_KEY);
|
||||
}
|
||||
} catch {
|
||||
// Ignore storage access failures in restricted runtimes.
|
||||
}
|
||||
}
|
||||
|
||||
function buildPreviewRuntimeLocationKey() {
|
||||
if (typeof window === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `${window.location.origin}${window.location.pathname}`;
|
||||
}
|
||||
|
||||
function buildPreviewRuntimeCacheResetUrl() {
|
||||
const nextUrl = new URL(window.location.href);
|
||||
nextUrl.searchParams.set(PREVIEW_RUNTIME_CACHE_RESET_PARAM_KEY, `${Date.now()}`);
|
||||
return nextUrl.toString();
|
||||
}
|
||||
|
||||
async function clearPreviewRuntimeServiceWorkersAndCaches() {
|
||||
if (typeof window === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
|
||||
if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
|
||||
try {
|
||||
const registrations = await navigator.serviceWorker.getRegistrations();
|
||||
if (registrations.length > 0) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
await Promise.all(registrations.map((registration) => registration.unregister().catch(() => false)));
|
||||
} catch {
|
||||
// Ignore cleanup failure and continue.
|
||||
}
|
||||
}
|
||||
|
||||
if ('caches' in window) {
|
||||
try {
|
||||
const cacheKeys = await caches.keys();
|
||||
if (cacheKeys.length > 0) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
await Promise.all(cacheKeys.map((cacheKey) => caches.delete(cacheKey).catch(() => false)));
|
||||
} catch {
|
||||
// Ignore cache cleanup failure and continue.
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
export async function ensurePreviewRuntimeFreshState() {
|
||||
if (typeof window === 'undefined' || (!isPreviewRuntime() && !isPreviewAppOrigin())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentLocationKey = buildPreviewRuntimeLocationKey();
|
||||
const cleanedLocationKey = readPreviewRuntimeCacheResetMarker();
|
||||
const resetSearchParam = new URL(window.location.href).searchParams.get(PREVIEW_RUNTIME_CACHE_RESET_PARAM_KEY)?.trim() ?? '';
|
||||
|
||||
if (cleanedLocationKey === currentLocationKey && !resetSearchParam) {
|
||||
return;
|
||||
}
|
||||
|
||||
const changed = await clearPreviewRuntimeServiceWorkersAndCaches();
|
||||
|
||||
if (!changed) {
|
||||
if (resetSearchParam) {
|
||||
const nextUrl = new URL(window.location.href);
|
||||
nextUrl.searchParams.delete(PREVIEW_RUNTIME_CACHE_RESET_PARAM_KEY);
|
||||
window.history.replaceState(window.history.state, '', `${nextUrl.pathname}${nextUrl.search}${nextUrl.hash}`);
|
||||
}
|
||||
|
||||
writePreviewRuntimeCacheResetMarker(currentLocationKey);
|
||||
return;
|
||||
}
|
||||
|
||||
writePreviewRuntimeCacheResetMarker(currentLocationKey);
|
||||
window.location.replace(buildPreviewRuntimeCacheResetUrl());
|
||||
await new Promise(() => {
|
||||
// Keep the bootstrap suspended until the browser navigates away.
|
||||
});
|
||||
}
|
||||
|
||||
export function getPreviewRuntimeParentOrigin() {
|
||||
if (typeof window === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return new URLSearchParams(window.location.search).get(PREVIEW_RUNTIME_PARENT_ORIGIN_KEY)?.trim() ?? '';
|
||||
}
|
||||
|
||||
export function readPreviewRuntimeDeviceModeFromUrl(): 'desktop' | 'mobile' | null {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = new URLSearchParams(window.location.search).get(PREVIEW_RUNTIME_DEVICE_MODE_QUERY_KEY)?.trim() ?? '';
|
||||
|
||||
if (value === 'desktop' || value === 'mobile') {
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function readPreviewRuntimeTokenFromUrl() {
|
||||
if (typeof window === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return new URLSearchParams(window.location.search).get(PREVIEW_RUNTIME_TOKEN_QUERY_KEY)?.trim() ?? '';
|
||||
}
|
||||
|
||||
export function clearPreviewRuntimeTokenFromUrl() {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
|
||||
if (!url.searchParams.has(PREVIEW_RUNTIME_TOKEN_QUERY_KEY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
url.searchParams.delete(PREVIEW_RUNTIME_TOKEN_QUERY_KEY);
|
||||
window.history.replaceState(window.history.state, '', `${url.pathname}${url.search}${url.hash}`);
|
||||
}
|
||||
|
||||
export function resolvePreviewAppOrigin() {
|
||||
return 'https://preview.sm-home.cloud';
|
||||
}
|
||||
|
||||
export function appendPreviewRuntimeSearch(path: string, sourceSearch = '') {
|
||||
if (!path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const [rawPathname, rawHash = ''] = path.split('#', 2);
|
||||
const [pathname, rawSearch = ''] = rawPathname.split('?', 2);
|
||||
const nextSearchParams = new URLSearchParams(rawSearch);
|
||||
const sourceSearchParams = new URLSearchParams(sourceSearch.startsWith('?') ? sourceSearch.slice(1) : sourceSearch);
|
||||
|
||||
PREVIEW_RUNTIME_PRESERVED_QUERY_KEYS.forEach((key) => {
|
||||
const value = sourceSearchParams.get(key)?.trim() ?? '';
|
||||
|
||||
if (value) {
|
||||
nextSearchParams.set(key, value);
|
||||
return;
|
||||
}
|
||||
|
||||
nextSearchParams.delete(key);
|
||||
});
|
||||
|
||||
const nextSearch = nextSearchParams.toString();
|
||||
const nextHash = rawHash ? `#${rawHash}` : '';
|
||||
|
||||
return `${pathname}${nextSearch ? `?${nextSearch}` : ''}${nextHash}`;
|
||||
}
|
||||
|
||||
export function readPreviewTargetDescriptorFromUrl(): PreviewTargetDescriptor {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const targetType = searchParams.get(PREVIEW_TARGET_TYPE_QUERY_KEY)?.trim() ?? '';
|
||||
|
||||
if (targetType !== 'widget') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const componentId = searchParams.get(PREVIEW_TARGET_COMPONENT_ID_QUERY_KEY)?.trim() ?? '';
|
||||
const sampleId = searchParams.get(PREVIEW_TARGET_SAMPLE_ID_QUERY_KEY)?.trim() ?? '';
|
||||
|
||||
if (!componentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'widget',
|
||||
componentId,
|
||||
sampleId: sampleId || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildPreviewRuntimeUrl(
|
||||
pathname: string,
|
||||
search = '',
|
||||
token = '',
|
||||
targetDescriptor: PreviewTargetDescriptor = null,
|
||||
deviceMode: 'desktop' | 'mobile' = 'desktop',
|
||||
) {
|
||||
const targetUrl = new URL(pathname || '/', resolvePreviewAppOrigin());
|
||||
|
||||
if (search) {
|
||||
const sourceParams = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
|
||||
sourceParams.forEach((value, key) => {
|
||||
targetUrl.searchParams.set(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
targetUrl.searchParams.set(PREVIEW_RUNTIME_QUERY_KEY, '1');
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
targetUrl.searchParams.set(PREVIEW_RUNTIME_PARENT_ORIGIN_KEY, window.location.origin);
|
||||
}
|
||||
|
||||
targetUrl.searchParams.set(PREVIEW_RUNTIME_DEVICE_MODE_QUERY_KEY, deviceMode);
|
||||
|
||||
if (token.trim()) {
|
||||
targetUrl.searchParams.set(PREVIEW_RUNTIME_TOKEN_QUERY_KEY, token.trim());
|
||||
}
|
||||
|
||||
if (targetDescriptor?.type === 'widget') {
|
||||
targetUrl.searchParams.set(PREVIEW_TARGET_TYPE_QUERY_KEY, 'widget');
|
||||
targetUrl.searchParams.set(PREVIEW_TARGET_COMPONENT_ID_QUERY_KEY, targetDescriptor.componentId);
|
||||
|
||||
if (targetDescriptor.sampleId?.trim()) {
|
||||
targetUrl.searchParams.set(PREVIEW_TARGET_SAMPLE_ID_QUERY_KEY, targetDescriptor.sampleId.trim());
|
||||
} else {
|
||||
targetUrl.searchParams.delete(PREVIEW_TARGET_SAMPLE_ID_QUERY_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
return targetUrl.toString();
|
||||
}
|
||||
Reference in New Issue
Block a user