Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion src/app/features/settings/developer-tools/DevelopTools.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useState } from 'react';
import { Box, Text, IconButton, Icon, Icons, Scroll, Switch, Button } from 'folds';
import { Box, Text, IconButton, Icon, Icons, Scroll, Switch, Button, Spinner, color } from 'folds';
import { KnownMembership } from 'matrix-js-sdk/lib/types';
import { Page, PageContent, PageHeader } from '$components/page';
import { SequenceCard } from '$components/sequence-card';
import { SettingTile } from '$components/setting-tile';
Expand All @@ -9,6 +10,7 @@ import { useMatrixClient } from '$hooks/useMatrixClient';
import { AccountDataEditor, AccountDataSubmitCallback } from '$components/AccountDataEditor';
import { copyToClipboard } from '$utils/dom';
import { SequenceCardStyle } from '$features/settings/styles.css';
import { AsyncStatus, useAsyncCallback } from '$hooks/useAsyncCallback';
import { AccountData } from './AccountData';
import { SyncDiagnostics } from './SyncDiagnostics';
import { DebugLogViewer } from './DebugLogViewer';
Expand All @@ -23,6 +25,33 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
const [expand, setExpend] = useState(false);
const [accountDataType, setAccountDataType] = useState<string | null>();

const [rotateState, rotateAllSessions] = useAsyncCallback<
{ rotated: number; total: number },
Error,
[]
>(
useCallback(async () => {
const crypto = mx.getCrypto();
if (!crypto) throw new Error('Crypto module not available');

const encryptedRooms = mx
.getRooms()
.filter(
(room) =>
room.getMyMembership() === KnownMembership.Join && mx.isRoomEncrypted(room.roomId)
);

await Promise.all(encryptedRooms.map((room) => crypto.forceDiscardSession(room.roomId)));
const rotated = encryptedRooms.length;

// Proactively start session creation + key sharing with all devices
// (including bridge bots). fire-and-forget per room.
encryptedRooms.forEach((room) => crypto.prepareToEncrypt(room));

return { rotated, total: encryptedRooms.length };
}, [mx])
);

const submitAccountData: AccountDataSubmitCallback = useCallback(
async (type, content) => {
// TODO: remove cast once account data typing is unified.
Expand Down Expand Up @@ -115,6 +144,56 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
)}
</Box>
{developerTools && <SyncDiagnostics />}
{developerTools && (
<Box direction="Column" gap="100">
<Text size="L400">Encryption</Text>
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="400"
>
<SettingTile
title="Rotate Encryption Sessions"
description="Discard current Megolm sessions and begin sharing new keys with all room members. Key delivery happens in the background — send a message in each affected room to confirm the bridge has received the new keys."
after={
<Button
onClick={rotateAllSessions}
variant="Secondary"
fill="Soft"
size="300"
radii="300"
outlined
disabled={rotateState.status === AsyncStatus.Loading}
before={
rotateState.status === AsyncStatus.Loading && (
<Spinner size="100" variant="Secondary" />
)
}
>
<Text size="B300">
{rotateState.status === AsyncStatus.Loading ? 'Rotating…' : 'Rotate'}
</Text>
</Button>
}
>
{rotateState.status === AsyncStatus.Success && (
<Text size="T200" style={{ color: color.Success.Main }}>
Sessions discarded for {rotateState.data.rotated} of{' '}
{rotateState.data.total} encrypted rooms. Key sharing is starting in the
background — send a message in an affected room to confirm delivery to
bridges.
</Text>
)}
{rotateState.status === AsyncStatus.Error && (
<Text size="T200" style={{ color: color.Critical.Main }}>
{rotateState.error.message}
</Text>
)}
</SettingTile>
</SequenceCard>
</Box>
)}
{developerTools && (
<AccountData
expand={expand}
Expand Down
6 changes: 6 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
return;
}

if (window.confirm('A new version of the app is available. Refresh to update?')) {

Check warning on line 54 in src/index.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected confirm
if (registration.waiting) {
registration.waiting.postMessage({ type: 'SKIP_WAITING_AND_CLAIM' });
}
Expand Down Expand Up @@ -83,6 +83,12 @@
pushSessionToSW(active?.baseUrl, active?.accessToken, active?.userId);
};

// Push the session synchronously before React renders so the SW has a fresh
// token before any <img> fetch events arrive. If navigator.serviceWorker.controller
// is already set (normal page reload), this eliminates the race where
// preloadedSession (potentially stale) would be used for early thumbnail fetches.
sendSessionToSW();

navigator.serviceWorker
.register(swUrl)
.then(sendSessionToSW)
Expand Down
Loading