130 lines
4.4 KiB
TypeScript
130 lines
4.4 KiB
TypeScript
/* eslint-disable import/first */
|
|
import React from 'react';
|
|
import { createRoot } from 'react-dom/client';
|
|
import { enableMapSet } from 'immer';
|
|
import '@fontsource-variable/inter/index.css';
|
|
import 'folds/dist/style.css';
|
|
import { configClass, varsClass } from 'folds';
|
|
|
|
enableMapSet();
|
|
|
|
import './index.css';
|
|
|
|
import { trimTrailingSlash } from './app/utils/common';
|
|
import App from './app/pages/App';
|
|
|
|
// import i18n (needs to be bundled ;))
|
|
import './app/i18n';
|
|
import { pushSessionToSW } from './sw-session';
|
|
import { getFallbackSession } from './app/state/sessions';
|
|
import { register } from 'module';
|
|
|
|
document.body.classList.add(configClass, varsClass);
|
|
|
|
// Register Service Worker
|
|
if ('serviceWorker' in navigator) {
|
|
const isProduction = import.meta.env.PROD;
|
|
const swUrl = isProduction
|
|
? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js`
|
|
: `/dev-sw.js?dev-sw`;
|
|
|
|
const sendSessionToSW = () => {
|
|
const session = getFallbackSession();
|
|
pushSessionToSW(session?.baseUrl, session?.accessToken);
|
|
};
|
|
|
|
const registerServiceWorker = async () => {
|
|
try {
|
|
const registration = await navigator.serviceWorker.register(
|
|
swUrl,
|
|
isProduction
|
|
? undefined
|
|
: {
|
|
type: 'module',
|
|
scope: '/',
|
|
},
|
|
);
|
|
|
|
sendSessionToSW();
|
|
|
|
await navigator.serviceWorker.ready;
|
|
sendSessionToSW();
|
|
|
|
console.info('Service worker registered:', registration.scope);
|
|
} catch (error) {
|
|
console.error('Service worker registration failed:', error);
|
|
}
|
|
};
|
|
|
|
void registerServiceWorker();
|
|
|
|
navigator.serviceWorker.addEventListener('message', (ev) => {
|
|
const { type } = ev.data ?? {};
|
|
|
|
if (type === 'requestSession') {
|
|
sendSessionToSW();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Request persistent storage so the browser can't evict the IndexedDB
|
|
// rust-crypto store under storage pressure. Eviction (while the localStorage
|
|
// session/device-id survives) resurrects the device with a blank crypto store,
|
|
// which then re-uploads OTKs the server already holds → the "one time key
|
|
// already exists" upload storm and E2EE breakage. Only ask for sessions worth
|
|
// protecting (skip anonymous/landing visitors to avoid a needless Firefox
|
|
// prompt); check persisted() first so we don't re-prompt. Best-effort.
|
|
if (navigator.storage?.persist && getFallbackSession()) {
|
|
navigator.storage
|
|
.persisted()
|
|
.then((already) => (already ? undefined : navigator.storage.persist()))
|
|
.catch(() => undefined);
|
|
}
|
|
|
|
// Reload once if a lazy-loaded chunk is missing (stale deployment)
|
|
window.addEventListener('vite:preloadError', () => {
|
|
if (!sessionStorage.getItem('chunk-reload-attempted')) {
|
|
sessionStorage.setItem('chunk-reload-attempted', '1');
|
|
window.location.reload();
|
|
}
|
|
});
|
|
// Clear the reload flag after a successful load so future deploys can still trigger a reload
|
|
window.addEventListener('load', () => sessionStorage.removeItem('chunk-reload-attempted'));
|
|
|
|
// Filter out known-benign, high-volume matrix-js-sdk console warnings that we
|
|
// can't fix client-side and that would otherwise flood the console:
|
|
// - "Adding default global …": the SDK patches MSC3786/MSC3914 push rules on
|
|
// every login (one warn each) until Synapse ships them as server defaults.
|
|
// - "EventTimelineSet…": the SDK loudly warns whenever a decrypted event
|
|
// references a thread/room the current timeline set doesn't hold, then
|
|
// discards it harmlessly. This fires constantly in E2EE rooms with threads.
|
|
// - "Decrypted event … is not in room …": same family — a late decryption for
|
|
// an event the room's timeline no longer tracks; ignored by the SDK.
|
|
// These are informational SDK bookkeeping, not errors; real warnings still log.
|
|
{
|
|
const suppressedPrefixes = ['Adding default global ', 'EventTimelineSet'];
|
|
const _warn = console.warn.bind(console);
|
|
console.warn = (...args: unknown[]) => {
|
|
const first = args[0];
|
|
if (typeof first === 'string') {
|
|
if (suppressedPrefixes.some((p) => first.startsWith(p))) return;
|
|
if (first.startsWith('Decrypted event ') && first.includes('is not in room')) return;
|
|
}
|
|
_warn(...args);
|
|
};
|
|
}
|
|
|
|
const mountApp = () => {
|
|
const rootContainer = document.getElementById('root');
|
|
|
|
if (rootContainer === null) {
|
|
console.error('Root container element not found!');
|
|
return;
|
|
}
|
|
|
|
const root = createRoot(rootContainer);
|
|
root.render(<App />);
|
|
};
|
|
|
|
mountApp();
|