mirror of
				https://github.com/cinnyapp/cinny.git
				synced 2025-11-04 14:30:29 +03:00 
			
		
		
		
	* rework general settings * account settings - WIP * add missing key prop * add object url hook * extract wide modal styles * profile settings and image editor - WIP * add outline style to upload card * remove file param from bind upload atom hook * add compact variant to upload card * add compact upload card renderer * add option to update profile avatar * add option to change profile displayname * allow displayname change based on capabilities check * rearrange settings components into folders * add system notification settings * add initial page param in settings * convert account data hook to typescript * add push rule hook * add notification mode hook * add notification mode switcher component * add all messages notification settings options * add special messages notification settings * add keyword notifications * add ignored users section * improve ignore user list strings * add about settings * add access token option in about settings * add developer tools settings * add expand button to account data dev tool option * update folds * fix editable active element textarea check * do not close dialog when editable element in focus * add text area plugins * add text area intent handler hook * add newline intent mod in text area * add next line hotkey in text area intent hook * add syntax error position dom utility function * add account data editor * add button to send new account data in dev tools * improve custom emoji plugin * add more custom emojis hooks * add text util css * add word break in setting tile title and description * emojis and sticker user settings - WIP * view image packs from settings * emoji pack editing - WIP * add option to edit pack meta * change saved changes message * add image edit and delete controls * add option to upload pack images and apply changes * fix state event type when updating image pack * lazy load pack image tile img * hide upload image button when user can not edit pack * add option to add or remove global image packs * upgrade to rust crypto (#2168) * update matrix js sdk * remove dead code * use rust crypto * update setPowerLevel usage * fix types * fix deprecated isRoomEncrypted method uses * fix deprecated room.currentState uses * fix deprecated import/export room keys func * fix merge issues in image pack file * fix remaining issues in image pack file * start indexedDBStore * update package lock and vite-plugin-top-level-await * user session settings - WIP * add useAsync hook * add password stage uia * add uia flow matrix error hook * add UIA action component * add options to delete sessions * add sso uia stage * fix SSO stage complete error * encryption - WIP * update user settings encryption terminology * add default variant to password input * use password input in uia password stage * add options for local backup in user settings * remove typo in import local backup password input label * online backup - WIP * fix uia sso action * move access token settings from about to developer tools * merge encryption tab into sessions and rename it to devices * add device placeholder tile * add logout dialog * add logout button for current device * move other devices in component * render unverified device verification tile * add learn more section for current device verification * add device verification status badge * add info card component * add index file for password input component * add types for secret storage * add component to access secret storage key * manual verification - WIP * update matrix-js-sdk to v35 * add manual verification * use react query for device list * show unverified tab on sidebar * fix device list updates * add session key details to current device * render restore encryption backup * fix loading state of restore backup * fix unverified tab settings closes after verification * key backup tile - WIP * fix unverified tab badge * rename session key to device key in device tile * improve backup restore functionality * fix restore button enabled after layout reload during restoring backup * update backup info on status change * add backup disconnection failures * add device verification using sas * restore backup after verification * show option to logout on startup error screen * fix key backup hook update on decryption key cached * add option to enable device verification * add device verification reset dialog * add logout button in settings drawer * add encrypted message lost on logout * fix backup restore never finish with 0 keys * fix setup dialog hides when enabling device verification * show backup details in menu * update setup device verification body copy * replace deprecated method * fix displayname appear as mxid in settings * remove old refactored codes * fix types
		
			
				
	
	
		
			335 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			335 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import React, { MouseEventHandler, useCallback, useState } from 'react';
 | 
						|
import {
 | 
						|
  Badge,
 | 
						|
  Box,
 | 
						|
  Button,
 | 
						|
  Chip,
 | 
						|
  config,
 | 
						|
  Icon,
 | 
						|
  Icons,
 | 
						|
  Spinner,
 | 
						|
  Text,
 | 
						|
  Overlay,
 | 
						|
  OverlayBackdrop,
 | 
						|
  OverlayCenter,
 | 
						|
  IconButton,
 | 
						|
  RectCords,
 | 
						|
  PopOut,
 | 
						|
  Menu,
 | 
						|
  MenuItem,
 | 
						|
} from 'folds';
 | 
						|
import FocusTrap from 'focus-trap-react';
 | 
						|
import { CryptoApi, VerificationRequest } from 'matrix-js-sdk/lib/crypto-api';
 | 
						|
import { VerificationStatus } from '../../../hooks/useDeviceVerificationStatus';
 | 
						|
import { InfoCard } from '../../../components/info-card';
 | 
						|
import { ManualVerificationTile } from '../../../components/ManualVerification';
 | 
						|
import { SecretStorageKeyContent } from '../../../../types/matrix/accountData';
 | 
						|
import { AsyncState, AsyncStatus, useAsync } from '../../../hooks/useAsyncCallback';
 | 
						|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
 | 
						|
import { DeviceVerification } from '../../../components/DeviceVerification';
 | 
						|
import {
 | 
						|
  DeviceVerificationReset,
 | 
						|
  DeviceVerificationSetup,
 | 
						|
} from '../../../components/DeviceVerificationSetup';
 | 
						|
import { stopPropagation } from '../../../utils/keyboard';
 | 
						|
 | 
						|
type VerificationStatusBadgeProps = {
 | 
						|
  verificationStatus: VerificationStatus;
 | 
						|
  otherUnverifiedCount?: number;
 | 
						|
};
 | 
						|
export function VerificationStatusBadge({
 | 
						|
  verificationStatus,
 | 
						|
  otherUnverifiedCount,
 | 
						|
}: VerificationStatusBadgeProps) {
 | 
						|
  if (
 | 
						|
    verificationStatus === VerificationStatus.Unknown ||
 | 
						|
    typeof otherUnverifiedCount !== 'number'
 | 
						|
  ) {
 | 
						|
    return <Spinner size="400" variant="Secondary" />;
 | 
						|
  }
 | 
						|
  if (verificationStatus === VerificationStatus.Unverified) {
 | 
						|
    return (
 | 
						|
      <Badge variant="Critical" fill="Solid" size="500">
 | 
						|
        <Text size="L400">Unverified</Text>
 | 
						|
      </Badge>
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  if (otherUnverifiedCount > 0) {
 | 
						|
    return (
 | 
						|
      <Badge variant="Warning" fill="Solid" size="500">
 | 
						|
        <Text size="L400">{otherUnverifiedCount} Unverified</Text>
 | 
						|
      </Badge>
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  return (
 | 
						|
    <Badge variant="Success" fill="Solid" size="500">
 | 
						|
      <Text size="L400">Verified</Text>
 | 
						|
    </Badge>
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
function LearnStartVerificationFromOtherDevice() {
 | 
						|
  return (
 | 
						|
    <Box direction="Column">
 | 
						|
      <Text size="T200">Steps to verify from other device.</Text>
 | 
						|
      <Text as="div" size="T200">
 | 
						|
        <ul style={{ margin: `${config.space.S100} 0` }}>
 | 
						|
          <li>Open your other verified device.</li>
 | 
						|
          <li>
 | 
						|
            Open <i>Settings</i>.
 | 
						|
          </li>
 | 
						|
          <li>
 | 
						|
            Find this device in <i>Devices/Sessions</i> section.
 | 
						|
          </li>
 | 
						|
          <li>Initiate verification.</li>
 | 
						|
        </ul>
 | 
						|
      </Text>
 | 
						|
      <Text size="T200">
 | 
						|
        If you do not have any verified device press the <i>"Verify Manually"</i> button.
 | 
						|
      </Text>
 | 
						|
    </Box>
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
type VerifyCurrentDeviceTileProps = {
 | 
						|
  secretStorageKeyId: string;
 | 
						|
  secretStorageKeyContent: SecretStorageKeyContent;
 | 
						|
};
 | 
						|
export function VerifyCurrentDeviceTile({
 | 
						|
  secretStorageKeyId,
 | 
						|
  secretStorageKeyContent,
 | 
						|
}: VerifyCurrentDeviceTileProps) {
 | 
						|
  const [learnMore, setLearnMore] = useState(false);
 | 
						|
 | 
						|
  const [manualVerification, setManualVerification] = useState(false);
 | 
						|
  const handleCancelVerification = () => setManualVerification(false);
 | 
						|
 | 
						|
  return (
 | 
						|
    <>
 | 
						|
      <InfoCard
 | 
						|
        variant="Critical"
 | 
						|
        title="Unverified"
 | 
						|
        description={
 | 
						|
          <>
 | 
						|
            Start verification from other device or verify manually.{' '}
 | 
						|
            <Text as="a" size="T200" onClick={() => setLearnMore(!learnMore)}>
 | 
						|
              <b>{learnMore ? 'View Less' : 'Learn More'}</b>
 | 
						|
            </Text>
 | 
						|
          </>
 | 
						|
        }
 | 
						|
        after={
 | 
						|
          !manualVerification && (
 | 
						|
            <Button
 | 
						|
              size="300"
 | 
						|
              variant="Critical"
 | 
						|
              fill="Soft"
 | 
						|
              radii="300"
 | 
						|
              outlined
 | 
						|
              onClick={() => setManualVerification(true)}
 | 
						|
            >
 | 
						|
              <Text as="span" size="B300">
 | 
						|
                Verify Manually
 | 
						|
              </Text>
 | 
						|
            </Button>
 | 
						|
          )
 | 
						|
        }
 | 
						|
      >
 | 
						|
        {learnMore && <LearnStartVerificationFromOtherDevice />}
 | 
						|
      </InfoCard>
 | 
						|
      {manualVerification && (
 | 
						|
        <ManualVerificationTile
 | 
						|
          secretStorageKeyId={secretStorageKeyId}
 | 
						|
          secretStorageKeyContent={secretStorageKeyContent}
 | 
						|
          options={
 | 
						|
            <Chip
 | 
						|
              type="button"
 | 
						|
              variant="Secondary"
 | 
						|
              fill="Soft"
 | 
						|
              radii="Pill"
 | 
						|
              onClick={handleCancelVerification}
 | 
						|
            >
 | 
						|
              <Icon size="100" src={Icons.Cross} />
 | 
						|
            </Chip>
 | 
						|
          }
 | 
						|
        />
 | 
						|
      )}
 | 
						|
    </>
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
type VerifyOtherDeviceTileProps = {
 | 
						|
  crypto: CryptoApi;
 | 
						|
  deviceId: string;
 | 
						|
};
 | 
						|
export function VerifyOtherDeviceTile({ crypto, deviceId }: VerifyOtherDeviceTileProps) {
 | 
						|
  const mx = useMatrixClient();
 | 
						|
  const [requestState, setRequestState] = useState<AsyncState<VerificationRequest, Error>>({
 | 
						|
    status: AsyncStatus.Idle,
 | 
						|
  });
 | 
						|
 | 
						|
  const requestVerification = useAsync<VerificationRequest, Error, []>(
 | 
						|
    useCallback(() => {
 | 
						|
      const requestPromise = crypto.requestDeviceVerification(mx.getSafeUserId(), deviceId);
 | 
						|
      return requestPromise;
 | 
						|
    }, [mx, crypto, deviceId]),
 | 
						|
    setRequestState
 | 
						|
  );
 | 
						|
 | 
						|
  const handleExit = useCallback(() => {
 | 
						|
    setRequestState({
 | 
						|
      status: AsyncStatus.Idle,
 | 
						|
    });
 | 
						|
  }, []);
 | 
						|
 | 
						|
  const requesting = requestState.status === AsyncStatus.Loading;
 | 
						|
  return (
 | 
						|
    <InfoCard
 | 
						|
      variant="Warning"
 | 
						|
      title="Unverified"
 | 
						|
      description="Verify device identity and grant access to encrypted messages."
 | 
						|
      after={
 | 
						|
        <Button
 | 
						|
          size="300"
 | 
						|
          variant="Warning"
 | 
						|
          radii="300"
 | 
						|
          onClick={requestVerification}
 | 
						|
          before={requesting && <Spinner size="100" variant="Warning" fill="Solid" />}
 | 
						|
          disabled={requesting}
 | 
						|
        >
 | 
						|
          <Text as="span" size="B300">
 | 
						|
            Verify
 | 
						|
          </Text>
 | 
						|
        </Button>
 | 
						|
      }
 | 
						|
    >
 | 
						|
      {requestState.status === AsyncStatus.Error && (
 | 
						|
        <Text size="T200">{requestState.error.message}</Text>
 | 
						|
      )}
 | 
						|
      {requestState.status === AsyncStatus.Success && (
 | 
						|
        <DeviceVerification request={requestState.data} onExit={handleExit} />
 | 
						|
      )}
 | 
						|
    </InfoCard>
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
type EnableVerificationProps = {
 | 
						|
  visible: boolean;
 | 
						|
};
 | 
						|
export function EnableVerification({ visible }: EnableVerificationProps) {
 | 
						|
  const [open, setOpen] = useState(false);
 | 
						|
 | 
						|
  const handleCancel = useCallback(() => setOpen(false), []);
 | 
						|
 | 
						|
  return (
 | 
						|
    <>
 | 
						|
      {visible && (
 | 
						|
        <Button size="300" radii="300" onClick={() => setOpen(true)}>
 | 
						|
          <Text as="span" size="B300">
 | 
						|
            Enable
 | 
						|
          </Text>
 | 
						|
        </Button>
 | 
						|
      )}
 | 
						|
      {open && (
 | 
						|
        <Overlay open backdrop={<OverlayBackdrop />}>
 | 
						|
          <OverlayCenter>
 | 
						|
            <FocusTrap
 | 
						|
              focusTrapOptions={{
 | 
						|
                initialFocus: false,
 | 
						|
                clickOutsideDeactivates: false,
 | 
						|
                escapeDeactivates: false,
 | 
						|
              }}
 | 
						|
            >
 | 
						|
              <DeviceVerificationSetup onCancel={handleCancel} />
 | 
						|
            </FocusTrap>
 | 
						|
          </OverlayCenter>
 | 
						|
        </Overlay>
 | 
						|
      )}
 | 
						|
    </>
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
export function DeviceVerificationOptions() {
 | 
						|
  const [menuCords, setMenuCords] = useState<RectCords>();
 | 
						|
 | 
						|
  const [reset, setReset] = useState(false);
 | 
						|
 | 
						|
  const handleCancelReset = useCallback(() => {
 | 
						|
    setReset(false);
 | 
						|
  }, []);
 | 
						|
 | 
						|
  const handleMenu: MouseEventHandler<HTMLButtonElement> = (event) => {
 | 
						|
    setMenuCords(event.currentTarget.getBoundingClientRect());
 | 
						|
  };
 | 
						|
 | 
						|
  const handleReset = () => {
 | 
						|
    setMenuCords(undefined);
 | 
						|
    setReset(true);
 | 
						|
  };
 | 
						|
 | 
						|
  return (
 | 
						|
    <>
 | 
						|
      <IconButton
 | 
						|
        aria-pressed={!!menuCords}
 | 
						|
        variant="SurfaceVariant"
 | 
						|
        size="300"
 | 
						|
        radii="300"
 | 
						|
        onClick={handleMenu}
 | 
						|
      >
 | 
						|
        <Icon size="100" src={Icons.VerticalDots} />
 | 
						|
      </IconButton>
 | 
						|
      <PopOut
 | 
						|
        anchor={menuCords}
 | 
						|
        offset={5}
 | 
						|
        position="Bottom"
 | 
						|
        align="Center"
 | 
						|
        content={
 | 
						|
          <FocusTrap
 | 
						|
            focusTrapOptions={{
 | 
						|
              initialFocus: false,
 | 
						|
              onDeactivate: () => setMenuCords(undefined),
 | 
						|
              clickOutsideDeactivates: true,
 | 
						|
              isKeyForward: (evt: KeyboardEvent) =>
 | 
						|
                evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
 | 
						|
              isKeyBackward: (evt: KeyboardEvent) =>
 | 
						|
                evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
 | 
						|
              escapeDeactivates: stopPropagation,
 | 
						|
            }}
 | 
						|
          >
 | 
						|
            <Menu>
 | 
						|
              <Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
 | 
						|
                <MenuItem
 | 
						|
                  variant="Critical"
 | 
						|
                  onClick={handleReset}
 | 
						|
                  size="300"
 | 
						|
                  radii="300"
 | 
						|
                  fill="None"
 | 
						|
                >
 | 
						|
                  <Text as="span" size="T300" truncate>
 | 
						|
                    Reset
 | 
						|
                  </Text>
 | 
						|
                </MenuItem>
 | 
						|
              </Box>
 | 
						|
            </Menu>
 | 
						|
          </FocusTrap>
 | 
						|
        }
 | 
						|
      />
 | 
						|
      {reset && (
 | 
						|
        <Overlay open backdrop={<OverlayBackdrop />}>
 | 
						|
          <OverlayCenter>
 | 
						|
            <FocusTrap
 | 
						|
              focusTrapOptions={{
 | 
						|
                initialFocus: false,
 | 
						|
                clickOutsideDeactivates: false,
 | 
						|
                escapeDeactivates: false,
 | 
						|
              }}
 | 
						|
            >
 | 
						|
              <DeviceVerificationReset onCancel={handleCancelReset} />
 | 
						|
            </FocusTrap>
 | 
						|
          </OverlayCenter>
 | 
						|
        </Overlay>
 | 
						|
      )}
 | 
						|
    </>
 | 
						|
  );
 | 
						|
}
 |