Files
cinny/src/index.tsx
T
Lotus CIandClaude Opus 5.5 e80b170fd1 fix(sw): only register the service worker on http(s) pages
register() rejects on non-http(s) origins, and the rejection was
unhandled. The desktop app's debug build loads from tauri://localhost,
which surfaced as a Sentry "serviceWorker.register() must be called with
a script URL whose protocol is either HTTP or HTTPS". Skip registration
there, and catch any other failure (e.g. SWs disabled) with a warning.
The app works without a SW.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-23 22:46:05 -04:00

131 lines
5.2 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 { cleanupSearchCacheIfSignedOut } from './client/initMatrix';
document.body.classList.add(configClass, varsClass);
// Register Service Worker
// Service workers only register on http(s) pages. The desktop app loads from
// `tauri://localhost` in debug builds (and on any platform where the localhost
// plugin isn't used), where register() rejects: that surfaced in Sentry as an
// unhandled "must be called with a script URL whose protocol is either HTTP or
// HTTPS". Skip it there; the app works without a SW, only authenticated media
// falls back to the client's own fetch.
const swProtocolOk = window.location.protocol === 'https:' || window.location.protocol === 'http:';
if ('serviceWorker' in navigator && swProtocolOk) {
const swUrl =
import.meta.env.MODE === 'production'
? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js`
: `/dev-sw.js?dev-sw`;
const sendSessionToSW = () => {
const session = getFallbackSession();
pushSessionToSW(session?.baseUrl, session?.accessToken);
};
// The dev worker is an ES module (it imports workbox), so it must be
// registered as one — otherwise "script evaluation failed" and the dev
// client has no SW: authenticated media 401s and notification clicks
// never route. Production sw.js is a classic bundled script.
navigator.serviceWorker
.register(swUrl, import.meta.env.MODE === 'production' ? undefined : { type: 'module' })
.then(sendSessionToSW)
.catch((err) => {
// e.g. a private window with SWs disabled; the app still works.
console.warn('Service worker registration failed', err);
});
navigator.serviceWorker.ready.then(sendSessionToSW);
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.
// [Gitea #45] If a logout's search-index wipe lost the multi-tab race (another
// tab still held the DB), finish it now that no session exists.
if (!getFallbackSession()) {
cleanupSearchCacheIfSignedOut().catch(() => undefined);
}
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();