handle error in loading screen (#1823)

* handle client boot error in loading screen

* use sync state hook in client root

* add loading screen options

* removed extra condition in loading finish

* add sync connection status bar
This commit is contained in:
Ajay Bura 2024-07-22 16:17:19 +05:30 committed by GitHub
parent e046c59f7c
commit e2228a18c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 609 additions and 510 deletions

View file

@ -53,7 +53,7 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
markAsRead(room.roomId); markAsRead(mx, room.roomId);
requestClose(); requestClose();
}; };

View file

@ -11,10 +11,12 @@ import { PowerLevelsContextProvider, usePowerLevels } from '../../hooks/usePower
import { useRoom } from '../../hooks/useRoom'; import { useRoom } from '../../hooks/useRoom';
import { useKeyDown } from '../../hooks/useKeyDown'; import { useKeyDown } from '../../hooks/useKeyDown';
import { markAsRead } from '../../../client/action/notifications'; import { markAsRead } from '../../../client/action/notifications';
import { useMatrixClient } from '../../hooks/useMatrixClient';
export function Room() { export function Room() {
const { eventId } = useParams(); const { eventId } = useParams();
const room = useRoom(); const room = useRoom();
const mx = useMatrixClient();
const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer'); const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer');
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
@ -25,7 +27,7 @@ export function Room() {
useCallback( useCallback(
(evt) => { (evt) => {
if (isKeyHotkey('escape', evt)) { if (isKeyHotkey('escape', evt)) {
markAsRead(room.roomId); markAsRead(mx, room.roomId);
} }
}, },
[room.roomId] [room.roomId]

View file

@ -597,7 +597,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
// so timeline can be updated with evt like: edits, reactions etc // so timeline can be updated with evt like: edits, reactions etc
if (atBottomRef.current) { if (atBottomRef.current) {
if (document.hasFocus() && (!unreadInfo || mEvt.getSender() === mx.getUserId())) { if (document.hasFocus() && (!unreadInfo || mEvt.getSender() === mx.getUserId())) {
requestAnimationFrame(() => markAsRead(mEvt.getRoomId())); requestAnimationFrame(() => markAsRead(mx, mEvt.getRoomId()));
} }
if (document.hasFocus()) { if (document.hasFocus()) {
@ -658,15 +658,15 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const tryAutoMarkAsRead = useCallback(() => { const tryAutoMarkAsRead = useCallback(() => {
if (!unreadInfo) { if (!unreadInfo) {
requestAnimationFrame(() => markAsRead(room.roomId)); requestAnimationFrame(() => markAsRead(mx, room.roomId));
return; return;
} }
const evtTimeline = getEventTimeline(room, unreadInfo.readUptoEventId); const evtTimeline = getEventTimeline(room, unreadInfo.readUptoEventId);
const latestTimeline = evtTimeline && getFirstLinkedTimeline(evtTimeline, Direction.Forward); const latestTimeline = evtTimeline && getFirstLinkedTimeline(evtTimeline, Direction.Forward);
if (latestTimeline === room.getLiveTimeline()) { if (latestTimeline === room.getLiveTimeline()) {
requestAnimationFrame(() => markAsRead(room.roomId)); requestAnimationFrame(() => markAsRead(mx, room.roomId));
} }
}, [room, unreadInfo]); }, [mx, room, unreadInfo]);
const debounceSetAtBottom = useDebounce( const debounceSetAtBottom = useDebounce(
useCallback((entry: IntersectionObserverEntry) => { useCallback((entry: IntersectionObserverEntry) => {
@ -832,7 +832,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
}; };
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
markAsRead(room.roomId); markAsRead(mx, room.roomId);
}; };
const handleOpenReply: MouseEventHandler<HTMLButtonElement> = useCallback( const handleOpenReply: MouseEventHandler<HTMLButtonElement> = useCallback(

View file

@ -40,6 +40,7 @@ export function RoomTombstone({ roomId, body, replacementRoomId }: RoomTombstone
</Text> </Text>
)} )}
</Box> </Box>
<Box shrink="No">
{replacementRoom?.getMyMembership() === Membership.Join || {replacementRoom?.getMyMembership() === Membership.Join ||
joinState.status === AsyncStatus.Success ? ( joinState.status === AsyncStatus.Success ? (
<Button onClick={handleOpen} size="300" variant="Success" fill="Solid" radii="300"> <Button onClick={handleOpen} size="300" variant="Success" fill="Solid" radii="300">
@ -62,6 +63,7 @@ export function RoomTombstone({ roomId, body, replacementRoomId }: RoomTombstone
<Text size="B300">Join New Room</Text> <Text size="B300">Join New Room</Text>
</Button> </Button>
)} )}
</Box>
</RoomInputPlaceholder> </RoomInputPlaceholder>
); );
} }

View file

@ -74,7 +74,7 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(
const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
markAsRead(room.roomId); markAsRead(mx, room.roomId);
requestClose(); requestClose();
}; };

View file

@ -1,10 +1,9 @@
/* eslint-disable import/prefer-default-export */ /* eslint-disable import/prefer-default-export */
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useMatrixClient } from './useMatrixClient';
import initMatrix from '../../client/initMatrix';
export function useAccountData(eventType) { export function useAccountData(eventType) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const [event, setEvent] = useState(mx.getAccountData(eventType)); const [event, setEvent] = useState(mx.getAccountData(eventType));
useEffect(() => { useEffect(() => {
@ -16,7 +15,7 @@ export function useAccountData(eventType) {
return () => { return () => {
mx.removeListener('accountData', handleChange); mx.removeListener('accountData', handleChange);
}; };
}, [eventType]); }, [mx, eventType]);
return event; return event;
} }

View file

@ -92,9 +92,9 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
return; return;
} }
} }
const devices = await Promise.all(userIds.map(hasDevices)); const devices = await Promise.all(userIds.map(uid => hasDevices(mx, uid)));
const isEncrypt = devices.every((hasDevice) => hasDevice); const isEncrypt = devices.every((hasDevice) => hasDevice);
const result = await roomActions.createDM(userIds, isEncrypt); const result = await roomActions.createDM(mx, userIds, isEncrypt);
navigateRoom(result.room_id); navigateRoom(result.room_id);
}, },
}, },
@ -106,7 +106,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
const roomIds = rawIds.filter( const roomIds = rawIds.filter(
(idOrAlias) => isRoomId(idOrAlias) || isRoomAlias(idOrAlias) (idOrAlias) => isRoomId(idOrAlias) || isRoomAlias(idOrAlias)
); );
roomIds.map((id) => roomActions.join(id)); roomIds.map((id) => roomActions.join(mx, id));
}, },
}, },
[Command.Leave]: { [Command.Leave]: {
@ -127,7 +127,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
description: 'Invite user to room. Example: /invite userId1 userId2 [-r reason]', description: 'Invite user to room. Example: /invite userId1 userId2 [-r reason]',
exe: async (payload) => { exe: async (payload) => {
const { users, reason } = parseUsersAndReason(payload); const { users, reason } = parseUsersAndReason(payload);
users.map((id) => roomActions.invite(room.roomId, id, reason)); users.map((id) => mx.invite(room.roomId, id, reason));
}, },
}, },
[Command.DisInvite]: { [Command.DisInvite]: {
@ -135,7 +135,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
description: 'Disinvite user to room. Example: /disinvite userId1 userId2 [-r reason]', description: 'Disinvite user to room. Example: /disinvite userId1 userId2 [-r reason]',
exe: async (payload) => { exe: async (payload) => {
const { users, reason } = parseUsersAndReason(payload); const { users, reason } = parseUsersAndReason(payload);
users.map((id) => roomActions.kick(room.roomId, id, reason)); users.map((id) => mx.kick(room.roomId, id, reason));
}, },
}, },
[Command.Kick]: { [Command.Kick]: {
@ -143,7 +143,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
description: 'Kick user from room. Example: /kick userId1 userId2 [-r reason]', description: 'Kick user from room. Example: /kick userId1 userId2 [-r reason]',
exe: async (payload) => { exe: async (payload) => {
const { users, reason } = parseUsersAndReason(payload); const { users, reason } = parseUsersAndReason(payload);
users.map((id) => roomActions.kick(room.roomId, id, reason)); users.map((id) => mx.kick(room.roomId, id, reason));
}, },
}, },
[Command.Ban]: { [Command.Ban]: {
@ -151,7 +151,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
description: 'Ban user from room. Example: /ban userId1 userId2 [-r reason]', description: 'Ban user from room. Example: /ban userId1 userId2 [-r reason]',
exe: async (payload) => { exe: async (payload) => {
const { users, reason } = parseUsersAndReason(payload); const { users, reason } = parseUsersAndReason(payload);
users.map((id) => roomActions.ban(room.roomId, id, reason)); users.map((id) => mx.ban(room.roomId, id, reason));
}, },
}, },
[Command.UnBan]: { [Command.UnBan]: {
@ -160,7 +160,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
exe: async (payload) => { exe: async (payload) => {
const rawIds = payload.split(' '); const rawIds = payload.split(' ');
const users = rawIds.filter((id) => isUserId(id)); const users = rawIds.filter((id) => isUserId(id));
users.map((id) => roomActions.unban(room.roomId, id)); users.map((id) => mx.unban(room.roomId, id));
}, },
}, },
[Command.Ignore]: { [Command.Ignore]: {
@ -169,7 +169,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
exe: async (payload) => { exe: async (payload) => {
const rawIds = payload.split(' '); const rawIds = payload.split(' ');
const userIds = rawIds.filter((id) => isUserId(id)); const userIds = rawIds.filter((id) => isUserId(id));
if (userIds.length > 0) roomActions.ignore(userIds); if (userIds.length > 0) roomActions.ignore(mx, userIds);
}, },
}, },
[Command.UnIgnore]: { [Command.UnIgnore]: {
@ -178,7 +178,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
exe: async (payload) => { exe: async (payload) => {
const rawIds = payload.split(' '); const rawIds = payload.split(' ');
const userIds = rawIds.filter((id) => isUserId(id)); const userIds = rawIds.filter((id) => isUserId(id));
if (userIds.length > 0) roomActions.unignore(userIds); if (userIds.length > 0) roomActions.unignore(mx, userIds);
}, },
}, },
[Command.MyRoomNick]: { [Command.MyRoomNick]: {
@ -187,7 +187,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
exe: async (payload) => { exe: async (payload) => {
const nick = payload.trim(); const nick = payload.trim();
if (nick === '') return; if (nick === '') return;
roomActions.setMyRoomNick(room.roomId, nick); roomActions.setMyRoomNick(mx, room.roomId, nick);
}, },
}, },
[Command.MyRoomAvatar]: { [Command.MyRoomAvatar]: {
@ -195,7 +195,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
description: 'Change profile picture in current room. Example /myroomavatar mxc://xyzabc', description: 'Change profile picture in current room. Example /myroomavatar mxc://xyzabc',
exe: async (payload) => { exe: async (payload) => {
if (payload.match(/^mxc:\/\/\S+$/)) { if (payload.match(/^mxc:\/\/\S+$/)) {
roomActions.setMyRoomAvatar(room.roomId, payload); roomActions.setMyRoomAvatar(mx, room.roomId, payload);
} }
}, },
}, },
@ -203,14 +203,14 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
name: Command.ConvertToDm, name: Command.ConvertToDm,
description: 'Convert room to direct message', description: 'Convert room to direct message',
exe: async () => { exe: async () => {
roomActions.convertToDm(room.roomId); roomActions.convertToDm(mx, room.roomId);
}, },
}, },
[Command.ConvertToRoom]: { [Command.ConvertToRoom]: {
name: Command.ConvertToRoom, name: Command.ConvertToRoom,
description: 'Convert direct message to room', description: 'Convert direct message to room',
exe: async () => { exe: async () => {
roomActions.convertToRoom(room.roomId); roomActions.convertToRoom(mx, room.roomId);
}, },
}, },
}), }),

View file

@ -1,12 +1,12 @@
/* eslint-disable import/prefer-default-export */ /* eslint-disable import/prefer-default-export */
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import initMatrix from '../../client/initMatrix';
import { hasCrossSigningAccountData } from '../../util/matrixUtil'; import { hasCrossSigningAccountData } from '../../util/matrixUtil';
import { useMatrixClient } from './useMatrixClient';
export function useCrossSigningStatus() { export function useCrossSigningStatus() {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const [isCSEnabled, setIsCSEnabled] = useState(hasCrossSigningAccountData()); const [isCSEnabled, setIsCSEnabled] = useState(hasCrossSigningAccountData(mx));
useEffect(() => { useEffect(() => {
if (isCSEnabled) return undefined; if (isCSEnabled) return undefined;
@ -20,6 +20,6 @@ export function useCrossSigningStatus() {
return () => { return () => {
mx.removeListener('accountData', handleAccountData); mx.removeListener('accountData', handleAccountData);
}; };
}, [isCSEnabled === false]); }, [mx, isCSEnabled]);
return isCSEnabled; return isCSEnabled;
} }

View file

@ -1,10 +1,9 @@
/* eslint-disable import/prefer-default-export */ /* eslint-disable import/prefer-default-export */
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useMatrixClient } from './useMatrixClient';
import initMatrix from '../../client/initMatrix';
export function useDeviceList() { export function useDeviceList() {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const [deviceList, setDeviceList] = useState(null); const [deviceList, setDeviceList] = useState(null);
useEffect(() => { useEffect(() => {
@ -27,6 +26,6 @@ export function useDeviceList() {
mx.removeListener('crypto.devicesUpdated', handleDevicesUpdate); mx.removeListener('crypto.devicesUpdated', handleDevicesUpdate);
isMounted = false; isMounted = false;
}; };
}, []); }, [mx]);
return deviceList; return deviceList;
} }

View file

@ -2,13 +2,13 @@ import { ClientEvent, ClientEventHandlerMap, MatrixClient } from 'matrix-js-sdk'
import { useEffect } from 'react'; import { useEffect } from 'react';
export const useSyncState = ( export const useSyncState = (
mx: MatrixClient, mx: MatrixClient | undefined,
onChange: ClientEventHandlerMap[ClientEvent.Sync] onChange: ClientEventHandlerMap[ClientEvent.Sync]
): void => { ): void => {
useEffect(() => { useEffect(() => {
mx.on(ClientEvent.Sync, onChange); mx?.on(ClientEvent.Sync, onChange);
return () => { return () => {
mx.removeListener(ClientEvent.Sync, onChange); mx?.removeListener(ClientEvent.Sync, onChange);
}; };
}, [mx, onChange]); }, [mx, onChange]);
}; };

View file

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import initMatrix from '../../../client/initMatrix';
import { openReusableContextMenu } from '../../../client/action/navigation'; import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common'; import { getEventCords } from '../../../util/common';
@ -14,6 +13,7 @@ import NotificationSelector from './NotificationSelector';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import { useAccountData } from '../../hooks/useAccountData'; import { useAccountData } from '../../hooks/useAccountData';
import { useMatrixClient } from '../../hooks/useMatrixClient';
export const notifType = { export const notifType = {
ON: 'on', ON: 'on',
@ -52,7 +52,7 @@ export function getTypeActions(type, highlightValue = false) {
} }
function useGlobalNotif() { function useGlobalNotif() {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const pushRules = useAccountData('m.push_rules')?.getContent(); const pushRules = useAccountData('m.push_rules')?.getContent();
const underride = pushRules?.global?.underride ?? []; const underride = pushRules?.global?.underride ?? [];
const rulesToType = { const rulesToType = {

View file

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import './IgnoreUserList.scss'; import './IgnoreUserList.scss';
import initMatrix from '../../../client/initMatrix';
import * as roomActions from '../../../client/action/room'; import * as roomActions from '../../../client/action/room';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
@ -14,10 +13,12 @@ import SettingTile from '../setting-tile/SettingTile';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import { useAccountData } from '../../hooks/useAccountData'; import { useAccountData } from '../../hooks/useAccountData';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function IgnoreUserList() { function IgnoreUserList() {
useAccountData('m.ignored_user_list'); useAccountData('m.ignored_user_list');
const ignoredUsers = initMatrix.matrixClient.getIgnoredUsers(); const mx = useMatrixClient();
const ignoredUsers = mx.getIgnoredUsers();
const handleSubmit = (evt) => { const handleSubmit = (evt) => {
evt.preventDefault(); evt.preventDefault();
@ -26,7 +27,7 @@ function IgnoreUserList() {
const userIds = value.split(' ').filter((v) => v.match(/^@\S+:\S+$/)); const userIds = value.split(' ').filter((v) => v.match(/^@\S+:\S+$/));
if (userIds.length === 0) return; if (userIds.length === 0) return;
ignoreInput.value = ''; ignoreInput.value = '';
roomActions.ignore(userIds); roomActions.ignore(mx, userIds);
}; };
return ( return (
@ -49,7 +50,7 @@ function IgnoreUserList() {
key={uId} key={uId}
text={uId} text={uId}
iconColor={CrossIC} iconColor={CrossIC}
onClick={() => roomActions.unignore([uId])} onClick={() => roomActions.unignore(mx, [uId])}
/> />
))} ))}
</div> </div>

View file

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import './KeywordNotification.scss'; import './KeywordNotification.scss';
import initMatrix from '../../../client/initMatrix';
import { openReusableContextMenu } from '../../../client/action/navigation'; import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common'; import { getEventCords } from '../../../util/common';
@ -21,6 +20,7 @@ import { useAccountData } from '../../hooks/useAccountData';
import { import {
notifType, typeToLabel, getActionType, getTypeActions, notifType, typeToLabel, getActionType, getTypeActions,
} from './GlobalNotification'; } from './GlobalNotification';
import { useMatrixClient } from '../../hooks/useMatrixClient';
const DISPLAY_NAME = '.m.rule.contains_display_name'; const DISPLAY_NAME = '.m.rule.contains_display_name';
const ROOM_PING = '.m.rule.roomnotif'; const ROOM_PING = '.m.rule.roomnotif';
@ -28,7 +28,7 @@ const USERNAME = '.m.rule.contains_user_name';
const KEYWORD = 'keyword'; const KEYWORD = 'keyword';
function useKeywordNotif() { function useKeywordNotif() {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const pushRules = useAccountData('m.push_rules')?.getContent(); const pushRules = useAccountData('m.push_rules')?.getContent();
const override = pushRules?.global?.override ?? []; const override = pushRules?.global?.override ?? [];
const content = pushRules?.global?.content ?? []; const content = pushRules?.global?.content ?? [];

View file

@ -4,7 +4,6 @@ import React, {
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './ImagePack.scss'; import './ImagePack.scss';
import initMatrix from '../../../client/initMatrix';
import { openReusableDialog } from '../../../client/action/navigation'; import { openReusableDialog } from '../../../client/action/navigation';
import { suffixRename } from '../../../util/common'; import { suffixRename } from '../../../util/common';
@ -19,6 +18,7 @@ import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
import ImagePackProfile from './ImagePackProfile'; import ImagePackProfile from './ImagePackProfile';
import ImagePackItem from './ImagePackItem'; import ImagePackItem from './ImagePackItem';
import ImagePackUpload from './ImagePackUpload'; import ImagePackUpload from './ImagePackUpload';
import { useMatrixClient } from '../../hooks/useMatrixClient';
const renameImagePackItem = (shortcode) => new Promise((resolve) => { const renameImagePackItem = (shortcode) => new Promise((resolve) => {
let isCompleted = false; let isCompleted = false;
@ -63,8 +63,7 @@ function getUsage(usage) {
return 'both'; return 'both';
} }
function isGlobalPack(roomId, stateKey) { function isGlobalPack(mx, roomId, stateKey) {
const mx = initMatrix.matrixClient;
const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent(); const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent();
if (typeof globalContent !== 'object') return false; if (typeof globalContent !== 'object') return false;
@ -75,13 +74,13 @@ function isGlobalPack(roomId, stateKey) {
} }
function useRoomImagePack(roomId, stateKey) { function useRoomImagePack(roomId, stateKey) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const pack = useMemo(() => {
const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey); const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
const pack = useMemo(() => ( return ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent())
ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent()) }, [room, stateKey]);
), [room, stateKey]);
const sendPackContent = (content) => { const sendPackContent = (content) => {
mx.sendStateEvent(roomId, 'im.ponies.room_emotes', content, stateKey); mx.sendStateEvent(roomId, 'im.ponies.room_emotes', content, stateKey);
@ -94,14 +93,14 @@ function useRoomImagePack(roomId, stateKey) {
} }
function useUserImagePack() { function useUserImagePack() {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const pack = useMemo(() => {
const packEvent = mx.getAccountData('im.ponies.user_emotes'); const packEvent = mx.getAccountData('im.ponies.user_emotes');
const pack = useMemo(() => ( return ImagePackBuilder.parsePack(mx.getUserId(), packEvent?.getContent() ?? {
ImagePackBuilder.parsePack(mx.getUserId(), packEvent?.getContent() ?? {
pack: { display_name: 'Personal' }, pack: { display_name: 'Personal' },
images: {}, images: {},
}) })
), []); }, [mx]);
const sendPackContent = (content) => { const sendPackContent = (content) => {
mx.setAccountData('im.ponies.user_emotes', content); mx.setAccountData('im.ponies.user_emotes', content);
@ -223,10 +222,10 @@ function removeGlobalImagePack(mx, roomId, stateKey) {
} }
function ImagePack({ roomId, stateKey, handlePackDelete }) { function ImagePack({ roomId, stateKey, handlePackDelete }) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const [viewMore, setViewMore] = useState(false); const [viewMore, setViewMore] = useState(false);
const [isGlobal, setIsGlobal] = useState(isGlobalPack(roomId, stateKey)); const [isGlobal, setIsGlobal] = useState(isGlobalPack(mx, roomId, stateKey));
const { pack, sendPackContent } = useRoomImagePack(roomId, stateKey); const { pack, sendPackContent } = useRoomImagePack(roomId, stateKey);
@ -331,7 +330,7 @@ ImagePack.propTypes = {
}; };
function ImagePackUser() { function ImagePackUser() {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const [viewMore, setViewMore] = useState(false); const [viewMore, setViewMore] = useState(false);
const { pack, sendPackContent } = useUserImagePack(); const { pack, sendPackContent } = useUserImagePack();
@ -397,7 +396,7 @@ function ImagePackUser() {
function useGlobalImagePack() { function useGlobalImagePack() {
const [, forceUpdate] = useReducer((count) => count + 1, 0); const [, forceUpdate] = useReducer((count) => count + 1, 0);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const roomIdToStateKeys = new Map(); const roomIdToStateKeys = new Map();
const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? { rooms: {} }; const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? { rooms: {} };
@ -419,13 +418,13 @@ function useGlobalImagePack() {
return () => { return () => {
mx.removeListener('accountData', handleEvent); mx.removeListener('accountData', handleEvent);
}; };
}, []); }, [mx]);
return roomIdToStateKeys; return roomIdToStateKeys;
} }
function ImagePackGlobal() { function ImagePackGlobal() {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const roomIdToStateKeys = useGlobalImagePack(); const roomIdToStateKeys = useGlobalImagePack();
const handleChange = (roomId, stateKey) => { const handleChange = (roomId, stateKey) => {

View file

@ -2,7 +2,6 @@ import React, { useState, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './ImagePackUpload.scss'; import './ImagePackUpload.scss';
import initMatrix from '../../../client/initMatrix';
import { scaleDownImage } from '../../../util/common'; import { scaleDownImage } from '../../../util/common';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
@ -10,9 +9,10 @@ import Button from '../../atoms/button/Button';
import Input from '../../atoms/input/Input'; import Input from '../../atoms/input/Input';
import IconButton from '../../atoms/button/IconButton'; import IconButton from '../../atoms/button/IconButton';
import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg'; import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function ImagePackUpload({ onUpload }) { function ImagePackUpload({ onUpload }) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const inputRef = useRef(null); const inputRef = useRef(null);
const shortcodeRef = useRef(null); const shortcodeRef = useRef(null);
const [imgFile, setImgFile] = useState(null); const [imgFile, setImgFile] = useState(null);

View file

@ -2,7 +2,6 @@ import React, { useState, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './ImageUpload.scss'; import './ImageUpload.scss';
import initMatrix from '../../../client/initMatrix';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import Avatar from '../../atoms/avatar/Avatar'; import Avatar from '../../atoms/avatar/Avatar';
@ -10,6 +9,7 @@ import Spinner from '../../atoms/spinner/Spinner';
import RawIcon from '../../atoms/system-icons/RawIcon'; import RawIcon from '../../atoms/system-icons/RawIcon';
import PlusIC from '../../../../public/res/ic/outlined/plus.svg'; import PlusIC from '../../../../public/res/ic/outlined/plus.svg';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function ImageUpload({ function ImageUpload({
text, bgColor, imageSrc, onUpload, onRequestRemove, text, bgColor, imageSrc, onUpload, onRequestRemove,
@ -17,12 +17,13 @@ function ImageUpload({
}) { }) {
const [uploadPromise, setUploadPromise] = useState(null); const [uploadPromise, setUploadPromise] = useState(null);
const uploadImageRef = useRef(null); const uploadImageRef = useRef(null);
const mx = useMatrixClient();
async function uploadImage(e) { async function uploadImage(e) {
const file = e.target.files.item(0); const file = e.target.files.item(0);
if (file === null) return; if (file === null) return;
try { try {
const uPromise = initMatrix.matrixClient.uploadContent(file); const uPromise = mx.uploadContent(file);
setUploadPromise(uPromise); setUploadPromise(uPromise);
const res = await uPromise; const res = await uPromise;
@ -35,7 +36,7 @@ function ImageUpload({
} }
function cancelUpload() { function cancelUpload() {
initMatrix.matrixClient.cancelUpload(uploadPromise); mx.cancelUpload(uploadPromise);
setUploadPromise(null); setUploadPromise(null);
uploadImageRef.current.value = null; uploadImageRef.current.value = null;
} }

View file

@ -3,7 +3,6 @@ import './ExportE2ERoomKeys.scss';
import FileSaver from 'file-saver'; import FileSaver from 'file-saver';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import { encryptMegolmKeyFile } from '../../../util/cryptE2ERoomKeys'; import { encryptMegolmKeyFile } from '../../../util/cryptE2ERoomKeys';
@ -13,8 +12,10 @@ import Input from '../../atoms/input/Input';
import Spinner from '../../atoms/spinner/Spinner'; import Spinner from '../../atoms/spinner/Spinner';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function ExportE2ERoomKeys() { function ExportE2ERoomKeys() {
const mx = useMatrixClient();
const isMountStore = useStore(); const isMountStore = useStore();
const [status, setStatus] = useState({ const [status, setStatus] = useState({
isOngoing: false, isOngoing: false,
@ -40,7 +41,7 @@ function ExportE2ERoomKeys() {
type: cons.status.IN_FLIGHT, type: cons.status.IN_FLIGHT,
}); });
try { try {
const keys = await initMatrix.matrixClient.exportRoomKeys(); const keys = await mx.exportRoomKeys();
if (isMountStore.getItem()) { if (isMountStore.getItem()) {
setStatus({ setStatus({
isOngoing: true, isOngoing: true,

View file

@ -1,7 +1,6 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import './ImportE2ERoomKeys.scss'; import './ImportE2ERoomKeys.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import { decryptMegolmKeyFile } from '../../../util/cryptE2ERoomKeys'; import { decryptMegolmKeyFile } from '../../../util/cryptE2ERoomKeys';
@ -14,8 +13,10 @@ import Spinner from '../../atoms/spinner/Spinner';
import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg'; import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function ImportE2ERoomKeys() { function ImportE2ERoomKeys() {
const mx = useMatrixClient();
const isMountStore = useStore(); const isMountStore = useStore();
const [keyFile, setKeyFile] = useState(null); const [keyFile, setKeyFile] = useState(null);
const [status, setStatus] = useState({ const [status, setStatus] = useState({
@ -45,7 +46,7 @@ function ImportE2ERoomKeys() {
type: cons.status.IN_FLIGHT, type: cons.status.IN_FLIGHT,
}); });
} }
await initMatrix.matrixClient.importRoomKeys(JSON.parse(keys)); await mx.importRoomKeys(JSON.parse(keys));
if (isMountStore.getItem()) { if (isMountStore.getItem()) {
setStatus({ setStatus({
isOngoing: false, isOngoing: false,

View file

@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomAliases.scss'; import './RoomAliases.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import { Debounce } from '../../../util/common'; import { Debounce } from '../../../util/common';
import { isRoomAliasAvailable } from '../../../util/matrixUtil'; import { isRoomAliasAvailable } from '../../../util/matrixUtil';
@ -16,8 +15,10 @@ import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import SettingTile from '../setting-tile/SettingTile'; import SettingTile from '../setting-tile/SettingTile';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function useValidate(hsString) { function useValidate(hsString) {
const mx = useMatrixClient();
const [debounce] = useState(new Debounce()); const [debounce] = useState(new Debounce());
const [validate, setValidate] = useState({ alias: null, status: cons.status.PRE_FLIGHT }); const [validate, setValidate] = useState({ alias: null, status: cons.status.PRE_FLIGHT });
@ -62,7 +63,7 @@ function useValidate(hsString) {
msg: `validating ${alias}...`, msg: `validating ${alias}...`,
}); });
const isValid = await isRoomAliasAvailable(alias); const isValid = await isRoomAliasAvailable(mx, alias);
setValidate(() => { setValidate(() => {
if (e.target.value !== value) { if (e.target.value !== value) {
return { alias: null, status: cons.status.PRE_FLIGHT }; return { alias: null, status: cons.status.PRE_FLIGHT };
@ -79,8 +80,7 @@ function useValidate(hsString) {
return [validate, setValidateToDefault, handleAliasChange]; return [validate, setValidateToDefault, handleAliasChange];
} }
function getAliases(roomId) { function getAliases(mx, roomId) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const main = room.getCanonicalAlias(); const main = room.getCanonicalAlias();
@ -95,7 +95,7 @@ function getAliases(roomId) {
} }
function RoomAliases({ roomId }) { function RoomAliases({ roomId }) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const userId = mx.getUserId(); const userId = mx.getUserId();
const hsString = userId.slice(userId.indexOf(':') + 1); const hsString = userId.slice(userId.indexOf(':') + 1);
@ -103,7 +103,7 @@ function RoomAliases({ roomId }) {
const isMountedStore = useStore(); const isMountedStore = useStore();
const [isPublic, setIsPublic] = useState(false); const [isPublic, setIsPublic] = useState(false);
const [isLocalVisible, setIsLocalVisible] = useState(false); const [isLocalVisible, setIsLocalVisible] = useState(false);
const [aliases, setAliases] = useState(getAliases(roomId)); const [aliases, setAliases] = useState(getAliases(mx, roomId));
const [selectedAlias, setSelectedAlias] = useState(null); const [selectedAlias, setSelectedAlias] = useState(null);
const [deleteAlias, setDeleteAlias] = useState(null); const [deleteAlias, setDeleteAlias] = useState(null);
const [validate, setValidateToDefault, handleAliasChange] = useValidate(hsString); const [validate, setValidateToDefault, handleAliasChange] = useValidate(hsString);
@ -140,7 +140,7 @@ function RoomAliases({ roomId }) {
return () => { return () => {
isUnmounted = true; isUnmounted = true;
}; };
}, [roomId]); }, [mx, roomId]);
const toggleDirectoryVisibility = () => { const toggleDirectoryVisibility = () => {
mx.setRoomDirectoryVisibility(roomId, isPublic ? 'private' : 'public'); mx.setRoomDirectoryVisibility(roomId, isPublic ? 'private' : 'public');

View file

@ -2,7 +2,6 @@ import React, { useReducer, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomEmojis.scss'; import './RoomEmojis.scss';
import initMatrix from '../../../client/initMatrix';
import { suffixRename } from '../../../util/common'; import { suffixRename } from '../../../util/common';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
@ -10,9 +9,10 @@ import Text from '../../atoms/text/Text';
import Input from '../../atoms/input/Input'; import Input from '../../atoms/input/Input';
import Button from '../../atoms/button/Button'; import Button from '../../atoms/button/Button';
import ImagePack from '../image-pack/ImagePack'; import ImagePack from '../image-pack/ImagePack';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function useRoomPacks(room) { function useRoomPacks(room) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const [, forceUpdate] = useReducer((count) => count + 1, 0); const [, forceUpdate] = useReducer((count) => count + 1, 0);
const packEvents = room.currentState.getStateEvents('im.ponies.room_emotes'); const packEvents = room.currentState.getStateEvents('im.ponies.room_emotes');
@ -75,7 +75,7 @@ function useRoomPacks(room) {
} }
function RoomEmojis({ roomId }) { function RoomEmojis({ roomId }) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const { usablePacks, createPack, deletePack } = useRoomPacks(room); const { usablePacks, createPack, deletePack } = useRoomPacks(room);

View file

@ -2,16 +2,16 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomEncryption.scss'; import './RoomEncryption.scss';
import initMatrix from '../../../client/initMatrix';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import Toggle from '../../atoms/button/Toggle'; import Toggle from '../../atoms/button/Toggle';
import SettingTile from '../setting-tile/SettingTile'; import SettingTile from '../setting-tile/SettingTile';
import { confirmDialog } from '../confirm-dialog/ConfirmDialog'; import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function RoomEncryption({ roomId }) { function RoomEncryption({ roomId }) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const encryptionEvents = room.currentState.getStateEvents('m.room.encryption'); const encryptionEvents = room.currentState.getStateEvents('m.room.encryption');
const [isEncrypted, setIsEncrypted] = useState(encryptionEvents.length > 0); const [isEncrypted, setIsEncrypted] = useState(encryptionEvents.length > 0);

View file

@ -2,11 +2,11 @@ import React, { useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomHistoryVisibility.scss'; import './RoomHistoryVisibility.scss';
import initMatrix from '../../../client/initMatrix';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import RadioButton from '../../atoms/button/RadioButton'; import RadioButton from '../../atoms/button/RadioButton';
import { MenuItem } from '../../atoms/context-menu/ContextMenu'; import { MenuItem } from '../../atoms/context-menu/ContextMenu';
import { useMatrixClient } from '../../hooks/useMatrixClient';
const visibility = { const visibility = {
WORLD_READABLE: 'world_readable', WORLD_READABLE: 'world_readable',
@ -33,38 +33,33 @@ const items = [{
type: visibility.JOINED, type: visibility.JOINED,
}]; }];
function setHistoryVisibility(roomId, type) {
const mx = initMatrix.matrixClient;
return mx.sendStateEvent(
roomId, 'm.room.history_visibility',
{
history_visibility: type,
},
);
}
function useVisibility(roomId) { function useVisibility(roomId) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const [activeType, setActiveType] = useState(room.getHistoryVisibility()); const [activeType, setActiveType] = useState(room.getHistoryVisibility());
useEffect(() => { useEffect(() => {
setActiveType(room.getHistoryVisibility()); setActiveType(room.getHistoryVisibility());
}, [roomId]); }, [room]);
const setVisibility = useCallback((item) => { const setVisibility = useCallback((item) => {
if (item.type === activeType.type) return; if (item.type === activeType.type) return;
setActiveType(item.type); setActiveType(item.type);
setHistoryVisibility(roomId, item.type); mx.sendStateEvent(
}, [activeType, roomId]); roomId, 'm.room.history_visibility',
{
history_visibility: item.type,
},
);
}, [mx, activeType, roomId]);
return [activeType, setVisibility]; return [activeType, setVisibility];
} }
function RoomHistoryVisibility({ roomId }) { function RoomHistoryVisibility({ roomId }) {
const [activeType, setVisibility] = useVisibility(roomId); const [activeType, setVisibility] = useVisibility(roomId);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const userId = mx.getUserId(); const userId = mx.getUserId();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const { currentState } = room; const { currentState } = room;

View file

@ -4,7 +4,6 @@ import React, {
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomMembers.scss'; import './RoomMembers.scss';
import initMatrix from '../../../client/initMatrix';
import colorMXID from '../../../util/colorMXID'; import colorMXID from '../../../util/colorMXID';
import { openProfileViewer } from '../../../client/action/navigation'; import { openProfileViewer } from '../../../client/action/navigation';
import { getUsernameOfRoomMember, getPowerLabel } from '../../../util/matrixUtil'; import { getUsernameOfRoomMember, getPowerLabel } from '../../../util/matrixUtil';
@ -17,11 +16,11 @@ import Input from '../../atoms/input/Input';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import SegmentedControls from '../../atoms/segmented-controls/SegmentedControls'; import SegmentedControls from '../../atoms/segmented-controls/SegmentedControls';
import PeopleSelector from '../people-selector/PeopleSelector'; import PeopleSelector from '../people-selector/PeopleSelector';
import { useMatrixClient } from '../../hooks/useMatrixClient';
const PER_PAGE_MEMBER = 50; const PER_PAGE_MEMBER = 50;
function normalizeMembers(members) { function normalizeMembers(mx, members) {
const mx = initMatrix.matrixClient;
return members.map((member) => ({ return members.map((member) => ({
userId: member.userId, userId: member.userId,
name: getUsernameOfRoomMember(member), name: getUsernameOfRoomMember(member),
@ -33,7 +32,7 @@ function normalizeMembers(members) {
} }
function useMemberOfMembership(roomId, membership) { function useMemberOfMembership(roomId, membership) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const [members, setMembers] = useState([]); const [members, setMembers] = useState([]);
@ -45,6 +44,7 @@ function useMemberOfMembership(roomId, membership) {
if (isLoadingMembers) return; if (isLoadingMembers) return;
if (event && event?.getRoomId() !== roomId) return; if (event && event?.getRoomId() !== roomId) return;
const memberOfMembership = normalizeMembers( const memberOfMembership = normalizeMembers(
mx,
room.getMembersWithMembership(membership) room.getMembersWithMembership(membership)
.sort(memberByAtoZ).sort(memberByPowerLevel), .sort(memberByAtoZ).sort(memberByPowerLevel),
); );
@ -66,7 +66,7 @@ function useMemberOfMembership(roomId, membership) {
mx.removeListener('RoomMember.membership', updateMemberList); mx.removeListener('RoomMember.membership', updateMemberList);
mx.removeListener('RoomMember.powerLevel', updateMemberList); mx.removeListener('RoomMember.powerLevel', updateMemberList);
}; };
}, [membership]); }, [mx, membership]);
return [members]; return [members];
} }

View file

@ -2,7 +2,6 @@ import React, { useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomNotification.scss'; import './RoomNotification.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
@ -14,6 +13,7 @@ import BellRingIC from '../../../../public/res/ic/outlined/bell-ring.svg';
import BellPingIC from '../../../../public/res/ic/outlined/bell-ping.svg'; import BellPingIC from '../../../../public/res/ic/outlined/bell-ping.svg';
import BellOffIC from '../../../../public/res/ic/outlined/bell-off.svg'; import BellOffIC from '../../../../public/res/ic/outlined/bell-off.svg';
import { getNotificationType } from '../../utils/room'; import { getNotificationType } from '../../utils/room';
import { useMatrixClient } from '../../hooks/useMatrixClient';
const items = [ const items = [
{ {
@ -38,8 +38,7 @@ const items = [
}, },
]; ];
function setRoomNotifType(roomId, newType) { function setRoomNotifType(mx, roomId, newType) {
const mx = initMatrix.matrixClient;
let roomPushRule; let roomPushRule;
try { try {
roomPushRule = mx.getRoomPushRule('global', roomId); roomPushRule = mx.getRoomPushRule('global', roomId);
@ -108,7 +107,7 @@ function setRoomNotifType(roomId, newType) {
} }
function useNotifications(roomId) { function useNotifications(roomId) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const [activeType, setActiveType] = useState(getNotificationType(mx, roomId)); const [activeType, setActiveType] = useState(getNotificationType(mx, roomId));
useEffect(() => { useEffect(() => {
setActiveType(getNotificationType(mx, roomId)); setActiveType(getNotificationType(mx, roomId));
@ -118,9 +117,9 @@ function useNotifications(roomId) {
(item) => { (item) => {
if (item.type === activeType.type) return; if (item.type === activeType.type) return;
setActiveType(item.type); setActiveType(item.type);
setRoomNotifType(roomId, item.type); setRoomNotifType(mx, roomId, item.type);
}, },
[activeType, roomId] [mx, activeType, roomId]
); );
return [activeType, setNotification]; return [activeType, setNotification];
} }

View file

@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomPermissions.scss'; import './RoomPermissions.scss';
import initMatrix from '../../../client/initMatrix';
import { getPowerLabel } from '../../../util/matrixUtil'; import { getPowerLabel } from '../../../util/matrixUtil';
import { openReusableContextMenu } from '../../../client/action/navigation'; import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common'; import { getEventCords } from '../../../util/common';
@ -16,6 +15,7 @@ import SettingTile from '../setting-tile/SettingTile';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import { useForceUpdate } from '../../hooks/useForceUpdate'; import { useForceUpdate } from '../../hooks/useForceUpdate';
import { useMatrixClient } from '../../hooks/useMatrixClient';
const permissionsInfo = { const permissionsInfo = {
users_default: { users_default: {
@ -157,7 +157,7 @@ const spacePermsGroups = {
function useRoomStateUpdate(roomId) { function useRoomStateUpdate(roomId) {
const [, forceUpdate] = useForceUpdate(); const [, forceUpdate] = useForceUpdate();
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
useEffect(() => { useEffect(() => {
const handleStateEvent = (event) => { const handleStateEvent = (event) => {
@ -169,12 +169,12 @@ function useRoomStateUpdate(roomId) {
return () => { return () => {
mx.removeListener('RoomState.events', handleStateEvent); mx.removeListener('RoomState.events', handleStateEvent);
}; };
}, [roomId]); }, [mx, roomId]);
} }
function RoomPermissions({ roomId }) { function RoomPermissions({ roomId }) {
useRoomStateUpdate(roomId); useRoomStateUpdate(roomId);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const pLEvent = room.currentState.getStateEvents('m.room.power_levels')[0]; const pLEvent = room.currentState.getStateEvents('m.room.power_levels')[0];
const permissions = pLEvent.getContent(); const permissions = pLEvent.getContent();

View file

@ -4,7 +4,6 @@ import { useAtomValue } from 'jotai';
import Linkify from 'linkify-react'; import Linkify from 'linkify-react';
import './RoomProfile.scss'; import './RoomProfile.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import colorMXID from '../../../util/colorMXID'; import colorMXID from '../../../util/colorMXID';
@ -22,6 +21,7 @@ import { useForceUpdate } from '../../hooks/useForceUpdate';
import { confirmDialog } from '../confirm-dialog/ConfirmDialog'; import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
import { mDirectAtom } from '../../state/mDirectList'; import { mDirectAtom } from '../../state/mDirectList';
import { LINKIFY_OPTS } from '../../plugins/react-custom-html-parser'; import { LINKIFY_OPTS } from '../../plugins/react-custom-html-parser';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function RoomProfile({ roomId }) { function RoomProfile({ roomId }) {
const isMountStore = useStore(); const isMountStore = useStore();
@ -32,7 +32,7 @@ function RoomProfile({ roomId }) {
type: cons.status.PRE_FLIGHT, type: cons.status.PRE_FLIGHT,
}); });
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const mDirects = useAtomValue(mDirectAtom); const mDirects = useAtomValue(mDirectAtom);
const isDM = mDirects.has(roomId); const isDM = mDirects.has(roomId);
let avatarSrc = mx.getRoom(roomId).getAvatarUrl(mx.baseUrl, 36, 36, 'crop'); let avatarSrc = mx.getRoom(roomId).getAvatarUrl(mx.baseUrl, 36, 36, 'crop');
@ -67,7 +67,7 @@ function RoomProfile({ roomId }) {
}); });
setIsEditing(false); setIsEditing(false);
}; };
}, [roomId]); }, [mx, roomId]);
const handleOnSubmit = async (e) => { const handleOnSubmit = async (e) => {
e.preventDefault(); e.preventDefault();

View file

@ -2,7 +2,6 @@ import React, { useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomVisibility.scss'; import './RoomVisibility.scss';
import initMatrix from '../../../client/initMatrix';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import RadioButton from '../../atoms/button/RadioButton'; import RadioButton from '../../atoms/button/RadioButton';
@ -14,6 +13,7 @@ import HashGlobeIC from '../../../../public/res/ic/outlined/hash-globe.svg';
import SpaceIC from '../../../../public/res/ic/outlined/space.svg'; import SpaceIC from '../../../../public/res/ic/outlined/space.svg';
import SpaceLockIC from '../../../../public/res/ic/outlined/space-lock.svg'; import SpaceLockIC from '../../../../public/res/ic/outlined/space-lock.svg';
import SpaceGlobeIC from '../../../../public/res/ic/outlined/space-globe.svg'; import SpaceGlobeIC from '../../../../public/res/ic/outlined/space-globe.svg';
import { useMatrixClient } from '../../hooks/useMatrixClient';
const visibility = { const visibility = {
INVITE: 'invite', INVITE: 'invite',
@ -21,8 +21,7 @@ const visibility = {
PUBLIC: 'public', PUBLIC: 'public',
}; };
function setJoinRule(roomId, type) { function setJoinRule(mx, roomId, type) {
const mx = initMatrix.matrixClient;
let allow; let allow;
if (type === visibility.RESTRICTED) { if (type === visibility.RESTRICTED) {
const { currentState } = mx.getRoom(roomId); const { currentState } = mx.getRoom(roomId);
@ -46,26 +45,26 @@ function setJoinRule(roomId, type) {
} }
function useVisibility(roomId) { function useVisibility(roomId) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const [activeType, setActiveType] = useState(room.getJoinRule()); const [activeType, setActiveType] = useState(room.getJoinRule());
useEffect(() => { useEffect(() => {
setActiveType(room.getJoinRule()); setActiveType(room.getJoinRule());
}, [roomId]); }, [room]);
const setNotification = useCallback((item) => { const setNotification = useCallback((item) => {
if (item.type === activeType.type) return; if (item.type === activeType.type) return;
setActiveType(item.type); setActiveType(item.type);
setJoinRule(roomId, item.type); setJoinRule(mx, roomId, item.type);
}, [activeType, roomId]); }, [mx, activeType, roomId]);
return [activeType, setNotification]; return [activeType, setNotification];
} }
function RoomVisibility({ roomId }) { function RoomVisibility({ roomId }) {
const [activeType, setVisibility] = useVisibility(roomId); const [activeType, setVisibility] = useVisibility(roomId);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const isSpace = room.isSpaceRoom(); const isSpace = room.isSpaceRoom();
const { currentState } = room; const { currentState } = room;

View file

@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import './SpaceAddExisting.scss'; import './SpaceAddExisting.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import { joinRuleToIconSrc, getIdServer, genRoomVia } from '../../../util/matrixUtil'; import { joinRuleToIconSrc, getIdServer, genRoomVia } from '../../../util/matrixUtil';
@ -27,6 +26,7 @@ import { roomToParentsAtom } from '../../state/room/roomToParents';
import { useDirects, useRooms, useSpaces } from '../../state/hooks/roomList'; import { useDirects, useRooms, useSpaces } from '../../state/hooks/roomList';
import { allRoomsAtom } from '../../state/room-list/roomList'; import { allRoomsAtom } from '../../state/room-list/roomList';
import { mDirectAtom } from '../../state/mDirectList'; import { mDirectAtom } from '../../state/mDirectList';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function SpaceAddExistingContent({ roomId, spaces: onlySpaces }) { function SpaceAddExistingContent({ roomId, spaces: onlySpaces }) {
const mountStore = useStore(roomId); const mountStore = useStore(roomId);
@ -35,7 +35,7 @@ function SpaceAddExistingContent({ roomId, spaces: onlySpaces }) {
const [allRoomIds, setAllRoomIds] = useState([]); const [allRoomIds, setAllRoomIds] = useState([]);
const [selected, setSelected] = useState([]); const [selected, setSelected] = useState([]);
const [searchIds, setSearchIds] = useState(null); const [searchIds, setSearchIds] = useState(null);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const roomIdToParents = useAtomValue(roomToParentsAtom); const roomIdToParents = useAtomValue(roomToParentsAtom);
const mDirects = useAtomValue(mDirectAtom); const mDirects = useAtomValue(mDirectAtom);
const spaces = useSpaces(mx, allRoomsAtom); const spaces = useSpaces(mx, allRoomsAtom);
@ -48,7 +48,7 @@ function SpaceAddExistingContent({ roomId, spaces: onlySpaces }) {
(rId) => rId !== roomId && !roomIdToParents.get(rId)?.has(roomId) (rId) => rId !== roomId && !roomIdToParents.get(rId)?.has(roomId)
); );
setAllRoomIds(allIds); setAllRoomIds(allIds);
}, [roomId, onlySpaces]); }, [spaces, rooms, directs, roomIdToParents, roomId, onlySpaces]);
const toggleSelection = (rId) => { const toggleSelection = (rId) => {
if (process !== null) return; if (process !== null) return;
@ -215,7 +215,7 @@ function useVisibilityToggle() {
function SpaceAddExisting() { function SpaceAddExisting() {
const [data, requestClose] = useVisibilityToggle(); const [data, requestClose] = useVisibilityToggle();
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(data?.roomId); const room = mx.getRoom(data?.roomId);
return ( return (

View file

@ -2,7 +2,6 @@ import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './CreateRoom.scss'; import './CreateRoom.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import { openReusableContextMenu } from '../../../client/action/navigation'; import { openReusableContextMenu } from '../../../client/action/navigation';
@ -32,6 +31,7 @@ import SpaceGlobeIC from '../../../../public/res/ic/outlined/space-globe.svg';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function CreateRoomContent({ isSpace, parentId, onRequestClose }) { function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
const [joinRule, setJoinRule] = useState(parentId ? 'restricted' : 'invite'); const [joinRule, setJoinRule] = useState(parentId ? 'restricted' : 'invite');
@ -46,7 +46,7 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
const addressRef = useRef(null); const addressRef = useRef(null);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const userHs = getIdServer(mx.getUserId()); const userHs = getIdServer(mx.getUserId());
const handleSubmit = async (evt) => { const handleSubmit = async (evt) => {
@ -69,7 +69,7 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
const powerLevel = roleIndex === 1 ? 101 : undefined; const powerLevel = roleIndex === 1 ? 101 : undefined;
try { try {
const data = await roomActions.createRoom({ const data = await roomActions.createRoom(mx, {
name, name,
topic, topic,
joinRule, joinRule,
@ -113,7 +113,7 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
if (roomAlias === '') return; if (roomAlias === '') return;
const roomAddress = `#${roomAlias}:${userHs}`; const roomAddress = `#${roomAlias}:${userHs}`;
if (await isRoomAliasAvailable(roomAddress)) { if (await isRoomAliasAvailable(mx, roomAddress)) {
setIsValidAddress(true); setIsValidAddress(true);
} else { } else {
setIsValidAddress(false); setIsValidAddress(false);
@ -278,7 +278,7 @@ function useWindowToggle() {
function CreateRoom() { function CreateRoom() {
const [create, onRequestClose] = useWindowToggle(); const [create, onRequestClose] = useWindowToggle();
const { isSpace, parentId } = create ?? {}; const { isSpace, parentId } = create ?? {};
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(parentId); const room = mx.getRoom(parentId);
return ( return (

View file

@ -3,7 +3,6 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './EmojiVerification.scss'; import './EmojiVerification.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import { hasPrivateKey } from '../../../client/state/secretStorageKeys'; import { hasPrivateKey } from '../../../client/state/secretStorageKeys';
@ -18,23 +17,24 @@ import Dialog from '../../molecules/dialog/Dialog';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { accessSecretStorage } from '../settings/SecretStorageAccess'; import { accessSecretStorage } from '../settings/SecretStorageAccess';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function EmojiVerificationContent({ data, requestClose }) { function EmojiVerificationContent({ data, requestClose }) {
const [sas, setSas] = useState(null); const [sas, setSas] = useState(null);
const [process, setProcess] = useState(false); const [process, setProcess] = useState(false);
const { request, targetDevice } = data; const { request, targetDevice } = data;
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const mountStore = useStore(); const mountStore = useStore();
const beginStore = useStore(); const beginStore = useStore();
const beginVerification = async () => { const beginVerification = async () => {
if ( if (
isCrossVerified(mx.deviceId) && isCrossVerified(mx, mx.deviceId) &&
(mx.getCrossSigningId() === null || (mx.getCrossSigningId() === null ||
(await mx.crypto.crossSigningInfo.isStoredInKeyCache('self_signing')) === false) (await mx.crypto.crossSigningInfo.isStoredInKeyCache('self_signing')) === false)
) { ) {
if (!hasPrivateKey(getDefaultSSKey())) { if (!hasPrivateKey(getDefaultSSKey(mx))) {
const keyData = await accessSecretStorage('Emoji verification'); const keyData = await accessSecretStorage(mx, 'Emoji verification');
if (!keyData) { if (!keyData) {
request.cancel(); request.cancel();
return; return;
@ -158,7 +158,7 @@ EmojiVerificationContent.propTypes = {
function useVisibilityToggle() { function useVisibilityToggle() {
const [data, setData] = useState(null); const [data, setData] = useState(null);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
useEffect(() => { useEffect(() => {
const handleOpen = (request, targetDevice) => { const handleOpen = (request, targetDevice) => {
@ -170,7 +170,7 @@ function useVisibilityToggle() {
navigation.removeListener(cons.events.navigation.EMOJI_VERIFICATION_OPENED, handleOpen); navigation.removeListener(cons.events.navigation.EMOJI_VERIFICATION_OPENED, handleOpen);
mx.removeListener('crypto.verification.request', handleOpen); mx.removeListener('crypto.verification.request', handleOpen);
}; };
}, []); }, [mx]);
const requestClose = () => setData(null); const requestClose = () => setData(null);

View file

@ -2,7 +2,6 @@ import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './InviteUser.scss'; import './InviteUser.scss';
import initMatrix from '../../../client/initMatrix';
import * as roomActions from '../../../client/action/room'; import * as roomActions from '../../../client/action/room';
import { hasDevices } from '../../../util/matrixUtil'; import { hasDevices } from '../../../util/matrixUtil';
@ -18,6 +17,7 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import UserIC from '../../../../public/res/ic/outlined/user.svg'; import UserIC from '../../../../public/res/ic/outlined/user.svg';
import { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { getDMRoomFor } from '../../utils/matrix'; import { getDMRoomFor } from '../../utils/matrix';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) { function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
const [isSearching, updateIsSearching] = useState(false); const [isSearching, updateIsSearching] = useState(false);
@ -34,7 +34,7 @@ function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
const usernameRef = useRef(null); const usernameRef = useRef(null);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const { navigateRoom } = useRoomNavigate(); const { navigateRoom } = useRoomNavigate();
function getMapCopy(myMap) { function getMapCopy(myMap) {
@ -118,7 +118,7 @@ function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
procUserError.delete(userId); procUserError.delete(userId);
updateUserProcError(getMapCopy(procUserError)); updateUserProcError(getMapCopy(procUserError));
const result = await roomActions.createDM(userId, await hasDevices(userId)); const result = await roomActions.createDM(mx, userId, await hasDevices(mx, userId));
roomIdToUserId.set(result.room_id, userId); roomIdToUserId.set(result.room_id, userId);
updateRoomIdToUserId(getMapCopy(roomIdToUserId)); updateRoomIdToUserId(getMapCopy(roomIdToUserId));
onDMCreated(result.room_id); onDMCreated(result.room_id);
@ -137,7 +137,7 @@ function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
procUserError.delete(userId); procUserError.delete(userId);
updateUserProcError(getMapCopy(procUserError)); updateUserProcError(getMapCopy(procUserError));
await roomActions.invite(roomId, userId); await mx.invite(roomId, userId);
invitedUserIds.add(userId); invitedUserIds.add(userId);
updateInvitedUserIds(new Set(Array.from(invitedUserIds))); updateInvitedUserIds(new Set(Array.from(invitedUserIds)));

View file

@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './JoinAlias.scss'; import './JoinAlias.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import { join } from '../../../client/action/room'; import { join } from '../../../client/action/room';
@ -18,6 +17,7 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { useMatrixClient } from '../../hooks/useMatrixClient';
const ALIAS_OR_ID_REG = /^[#|!].+:.+\..+$/; const ALIAS_OR_ID_REG = /^[#|!].+:.+\..+$/;
@ -25,7 +25,7 @@ function JoinAliasContent({ term, requestClose }) {
const [process, setProcess] = useState(false); const [process, setProcess] = useState(false);
const [error, setError] = useState(undefined); const [error, setError] = useState(undefined);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const mountStore = useStore(); const mountStore = useStore();
const { navigateRoom } = useRoomNavigate(); const { navigateRoom } = useRoomNavigate();
@ -63,7 +63,7 @@ function JoinAliasContent({ term, requestClose }) {
} }
} }
try { try {
const roomId = await join(alias, false, via); const roomId = await join(mx, alias, false, via);
if (!mountStore.getItem()) return; if (!mountStore.getItem()) return;
openRoom(roomId); openRoom(roomId);
} catch { } catch {

View file

@ -1,7 +1,6 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import initMatrix from '../../../client/initMatrix';
import colorMXID from '../../../util/colorMXID'; import colorMXID from '../../../util/colorMXID';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
@ -14,10 +13,11 @@ import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
import './ProfileEditor.scss'; import './ProfileEditor.scss';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function ProfileEditor({ userId }) { function ProfileEditor({ userId }) {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const user = mx.getUser(mx.getUserId()); const user = mx.getUser(mx.getUserId());
const displayNameRef = useRef(null); const displayNameRef = useRef(null);
@ -37,7 +37,7 @@ function ProfileEditor({ userId }) {
return () => { return () => {
isMounted = false; isMounted = false;
}; };
}, [userId]); }, [mx, userId]);
const handleAvatarUpload = async (url) => { const handleAvatarUpload = async (url) => {
if (url === null) { if (url === null) {

View file

@ -2,7 +2,6 @@ import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './ProfileViewer.scss'; import './ProfileViewer.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import { openReusableContextMenu } from '../../../client/action/navigation'; import { openReusableContextMenu } from '../../../client/action/navigation';
@ -36,9 +35,10 @@ import { useForceUpdate } from '../../hooks/useForceUpdate';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
import { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { getDMRoomFor } from '../../utils/matrix'; import { getDMRoomFor } from '../../utils/matrix';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function ModerationTools({ roomId, userId }) { function ModerationTools({ roomId, userId }) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const roomMember = room.getMember(userId); const roomMember = room.getMember(userId);
@ -56,13 +56,13 @@ function ModerationTools({ roomId, userId }) {
const handleKick = (e) => { const handleKick = (e) => {
e.preventDefault(); e.preventDefault();
const kickReason = e.target.elements['kick-reason']?.value.trim(); const kickReason = e.target.elements['kick-reason']?.value.trim();
roomActions.kick(roomId, userId, kickReason !== '' ? kickReason : undefined); mx.kick(roomId, userId, kickReason !== '' ? kickReason : undefined);
}; };
const handleBan = (e) => { const handleBan = (e) => {
e.preventDefault(); e.preventDefault();
const banReason = e.target.elements['ban-reason']?.value.trim(); const banReason = e.target.elements['ban-reason']?.value.trim();
roomActions.ban(roomId, userId, banReason !== '' ? banReason : undefined); mx.ban(roomId, userId, banReason !== '' ? banReason : undefined);
}; };
return ( return (
@ -90,7 +90,7 @@ ModerationTools.propTypes = {
function SessionInfo({ userId }) { function SessionInfo({ userId }) {
const [devices, setDevices] = useState(null); const [devices, setDevices] = useState(null);
const [isVisible, setIsVisible] = useState(false); const [isVisible, setIsVisible] = useState(false);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
useEffect(() => { useEffect(() => {
let isUnmounted = false; let isUnmounted = false;
@ -111,7 +111,7 @@ function SessionInfo({ userId }) {
return () => { return () => {
isUnmounted = true; isUnmounted = true;
}; };
}, [userId]); }, [mx, userId]);
function renderSessionChips() { function renderSessionChips() {
if (!isVisible) return null; if (!isVisible) return null;
@ -139,7 +139,7 @@ function SessionInfo({ userId }) {
> >
<Text variant="b2">{`View ${ <Text variant="b2">{`View ${
devices?.length > 0 devices?.length > 0
? `${devices.length} ${devices.length == 1 ? 'session' : 'sessions'}` ? `${devices.length} ${devices.length === 1 ? 'session' : 'sessions'}`
: 'sessions' : 'sessions'
}`}</Text> }`}</Text>
</MenuItem> </MenuItem>
@ -155,10 +155,10 @@ SessionInfo.propTypes = {
function ProfileFooter({ roomId, userId, onRequestClose }) { function ProfileFooter({ roomId, userId, onRequestClose }) {
const [isCreatingDM, setIsCreatingDM] = useState(false); const [isCreatingDM, setIsCreatingDM] = useState(false);
const [isIgnoring, setIsIgnoring] = useState(false); const [isIgnoring, setIsIgnoring] = useState(false);
const [isUserIgnored, setIsUserIgnored] = useState(initMatrix.matrixClient.isUserIgnored(userId)); const mx = useMatrixClient();
const [isUserIgnored, setIsUserIgnored] = useState(mx.isUserIgnored(userId));
const isMountedRef = useRef(true); const isMountedRef = useRef(true);
const mx = initMatrix.matrixClient;
const { navigateRoom } = useRoomNavigate(); const { navigateRoom } = useRoomNavigate();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const member = room.getMember(userId); const member = room.getMember(userId);
@ -182,10 +182,10 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
}; };
useEffect(() => { useEffect(() => {
setIsUserIgnored(initMatrix.matrixClient.isUserIgnored(userId)); setIsUserIgnored(mx.isUserIgnored(userId));
setIsIgnoring(false); setIsIgnoring(false);
setIsInviting(false); setIsInviting(false);
}, [userId]); }, [mx, userId]);
const openDM = async () => { const openDM = async () => {
// Check and open if user already have a DM with userId. // Check and open if user already have a DM with userId.
@ -199,7 +199,7 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
// Create new DM // Create new DM
try { try {
setIsCreatingDM(true); setIsCreatingDM(true);
const result = await roomActions.createDM(userId, await hasDevices(userId)); const result = await roomActions.createDM(mx, userId, await hasDevices(mx, userId));
onCreated(result.room_id); onCreated(result.room_id);
} catch { } catch {
if (isMountedRef.current === false) return; if (isMountedRef.current === false) return;
@ -213,9 +213,9 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
try { try {
setIsIgnoring(true); setIsIgnoring(true);
if (isIgnored) { if (isIgnored) {
await roomActions.unignore([userId]); await roomActions.unignore(mx, [userId]);
} else { } else {
await roomActions.ignore([userId]); await roomActions.ignore(mx, [userId]);
} }
if (isMountedRef.current === false) return; if (isMountedRef.current === false) return;
@ -230,9 +230,9 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
try { try {
setIsInviting(true); setIsInviting(true);
let isInviteSent = false; let isInviteSent = false;
if (isInvited) await roomActions.kick(roomId, userId); if (isInvited) await mx.kick(roomId, userId);
else { else {
await roomActions.invite(roomId, userId); await mx.invite(roomId, userId);
isInviteSent = true; isInviteSent = true;
} }
if (isMountedRef.current === false) return; if (isMountedRef.current === false) return;
@ -249,7 +249,7 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
{isCreatingDM ? 'Creating room...' : 'Message'} {isCreatingDM ? 'Creating room...' : 'Message'}
</Button> </Button>
{isBanned && canIKick && ( {isBanned && canIKick && (
<Button variant="positive" onClick={() => roomActions.unban(roomId, userId)}> <Button variant="positive" onClick={() => mx.unban(roomId, userId)}>
Unban Unban
</Button> </Button>
)} )}
@ -306,7 +306,7 @@ function useToggleDialog() {
} }
function useRerenderOnProfileChange(roomId, userId) { function useRerenderOnProfileChange(roomId, userId) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const [, forceUpdate] = useForceUpdate(); const [, forceUpdate] = useForceUpdate();
useEffect(() => { useEffect(() => {
const handleProfileChange = (mEvent, member) => { const handleProfileChange = (mEvent, member) => {
@ -323,19 +323,19 @@ function useRerenderOnProfileChange(roomId, userId) {
mx.removeListener('RoomMember.powerLevel', handleProfileChange); mx.removeListener('RoomMember.powerLevel', handleProfileChange);
mx.removeListener('RoomMember.membership', handleProfileChange); mx.removeListener('RoomMember.membership', handleProfileChange);
}; };
}, [roomId, userId]); }, [mx, roomId, userId]);
} }
function ProfileViewer() { function ProfileViewer() {
const [isOpen, roomId, userId, closeDialog, handleAfterClose] = useToggleDialog(); const [isOpen, roomId, userId, closeDialog, handleAfterClose] = useToggleDialog();
useRerenderOnProfileChange(roomId, userId); useRerenderOnProfileChange(roomId, userId);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const renderProfile = () => { const renderProfile = () => {
const roomMember = room.getMember(userId); const roomMember = room.getMember(userId);
const username = roomMember ? getUsernameOfRoomMember(roomMember) : getUsername(userId); const username = roomMember ? getUsernameOfRoomMember(roomMember) : getUsername(mx, userId);
const avatarMxc = roomMember?.getMxcAvatarUrl?.() || mx.getUser(userId)?.avatarUrl; const avatarMxc = roomMember?.getMxcAvatarUrl?.() || mx.getUser(userId)?.avatarUrl;
const avatarUrl = const avatarUrl =
avatarMxc && avatarMxc !== 'null' ? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop') : null; avatarMxc && avatarMxc !== 'null' ? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop') : null;
@ -364,9 +364,9 @@ function ProfileViewer() {
'caution' 'caution'
); );
if (!isConfirmed) return; if (!isConfirmed) return;
roomActions.setPowerLevel(roomId, userId, newPowerLevel); roomActions.setPowerLevel(mx, roomId, userId, newPowerLevel);
} else { } else {
roomActions.setPowerLevel(roomId, userId, newPowerLevel); roomActions.setPowerLevel(mx, roomId, userId, newPowerLevel);
} }
}; };

View file

@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomSettings.scss'; import './RoomSettings.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
@ -30,6 +29,7 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
import PopupWindow from '../../molecules/popup-window/PopupWindow'; import PopupWindow from '../../molecules/popup-window/PopupWindow';
import IconButton from '../../atoms/button/IconButton'; import IconButton from '../../atoms/button/IconButton';
import { useMatrixClient } from '../../hooks/useMatrixClient';
const tabText = { const tabText = {
GENERAL: 'General', GENERAL: 'General',
@ -68,7 +68,7 @@ const tabItems = [
]; ];
function GeneralSettings({ roomId }) { function GeneralSettings({ roomId }) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
return ( return (
@ -155,7 +155,8 @@ function RoomSettings() {
const [window, requestClose] = useWindowToggle(setSelectedTab); const [window, requestClose] = useWindowToggle(setSelectedTab);
const isOpen = window !== null; const isOpen = window !== null;
const roomId = window?.roomId; const roomId = window?.roomId;
const room = initMatrix.matrixClient.getRoom(roomId); const mx = useMatrixClient();
const room = mx.getRoom(roomId);
const handleTabChange = (tabItem) => { const handleTabChange = (tabItem) => {
setSelectedTab(tabItem); setSelectedTab(tabItem);

View file

@ -2,12 +2,10 @@ import React, { useState, useEffect, useRef, useCallback } from 'react';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import './Search.scss'; import './Search.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import AsyncSearch from '../../../util/AsyncSearch'; import AsyncSearch from '../../../util/AsyncSearch';
import { joinRuleToIconSrc } from '../../../util/matrixUtil'; import { joinRuleToIconSrc } from '../../../util/matrixUtil';
import { roomIdByActivity } from '../../../util/sort';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import RawIcon from '../../atoms/system-icons/RawIcon'; import RawIcon from '../../atoms/system-icons/RawIcon';
@ -27,6 +25,8 @@ import { allRoomsAtom } from '../../state/room-list/roomList';
import { mDirectAtom } from '../../state/mDirectList'; import { mDirectAtom } from '../../state/mDirectList';
import { useKeyDown } from '../../hooks/useKeyDown'; import { useKeyDown } from '../../hooks/useKeyDown';
import { openSearch } from '../../../client/action/navigation'; import { openSearch } from '../../../client/action/navigation';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { factoryRoomIdByActivity } from '../../utils/sort';
function useVisiblityToggle(setResult) { function useVisiblityToggle(setResult) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -77,9 +77,7 @@ function useVisiblityToggle(setResult) {
return [isOpen, requestClose]; return [isOpen, requestClose];
} }
function mapRoomIds(roomIds, directs, roomIdToParents) { function mapRoomIds(mx, roomIds, directs, roomIdToParents) {
const mx = initMatrix.matrixClient;
return roomIds.map((roomId) => { return roomIds.map((roomId) => {
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const parentSet = roomIdToParents.get(roomId); const parentSet = roomIdToParents.get(roomId);
@ -107,7 +105,7 @@ function Search() {
const [asyncSearch] = useState(new AsyncSearch()); const [asyncSearch] = useState(new AsyncSearch());
const [isOpen, requestClose] = useVisiblityToggle(setResult); const [isOpen, requestClose] = useVisiblityToggle(setResult);
const searchRef = useRef(null); const searchRef = useRef(null);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const { navigateRoom, navigateSpace } = useRoomNavigate(); const { navigateRoom, navigateSpace } = useRoomNavigate();
const mDirects = useAtomValue(mDirectAtom); const mDirects = useAtomValue(mDirectAtom);
const spaces = useSpaces(mx, allRoomsAtom); const spaces = useSpaces(mx, allRoomsAtom);
@ -141,8 +139,8 @@ function Search() {
ids = [...rooms].concat([...directs], [...spaces]); ids = [...rooms].concat([...directs], [...spaces]);
} }
ids.sort(roomIdByActivity); ids.sort(factoryRoomIdByActivity(mx));
const mappedIds = mapRoomIds(ids, directs, roomToParents); const mappedIds = mapRoomIds(mx, ids, directs, roomToParents);
asyncSearch.setup(mappedIds, { keys: 'name', isContain: true, limit: 20 }); asyncSearch.setup(mappedIds, { keys: 'name', isContain: true, limit: 20 });
if (prefix) handleSearchResults(mappedIds, prefix); if (prefix) handleSearchResults(mappedIds, prefix);
else asyncSearch.search(term); else asyncSearch.search(term);
@ -150,7 +148,7 @@ function Search() {
const loadRecentRooms = () => { const loadRecentRooms = () => {
const recentRooms = []; const recentRooms = [];
handleSearchResults(mapRoomIds(recentRooms, directs, roomToParents).reverse()); handleSearchResults(mapRoomIds(mx, recentRooms, directs, roomToParents).reverse());
}; };
const handleAfterOpen = () => { const handleAfterOpen = () => {

View file

@ -2,7 +2,6 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './AuthRequest.scss'; import './AuthRequest.scss';
import initMatrix from '../../../client/initMatrix';
import { openReusableDialog } from '../../../client/action/navigation'; import { openReusableDialog } from '../../../client/action/navigation';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
@ -11,6 +10,7 @@ import Input from '../../atoms/input/Input';
import Spinner from '../../atoms/spinner/Spinner'; import Spinner from '../../atoms/spinner/Spinner';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { getSecret } from '../../../client/state/auth';
let lastUsedPassword; let lastUsedPassword;
const getAuthId = (password) => ({ const getAuthId = (password) => ({
@ -18,7 +18,7 @@ const getAuthId = (password) => ({
password, password,
identifier: { identifier: {
type: 'm.id.user', type: 'm.id.user',
user: initMatrix.matrixClient.getUserId(), user: getSecret().userId,
}, },
}); });

View file

@ -4,7 +4,6 @@ import './CrossSigning.scss';
import FileSaver from 'file-saver'; import FileSaver from 'file-saver';
import { Formik } from 'formik'; import { Formik } from 'formik';
import initMatrix from '../../../client/initMatrix';
import { openReusableDialog } from '../../../client/action/navigation'; import { openReusableDialog } from '../../../client/action/navigation';
import { copyToClipboard } from '../../../util/common'; import { copyToClipboard } from '../../../util/common';
import { clearSecretStorageKeys } from '../../../client/state/secretStorageKeys'; import { clearSecretStorageKeys } from '../../../client/state/secretStorageKeys';
@ -17,6 +16,7 @@ import SettingTile from '../../molecules/setting-tile/SettingTile';
import { authRequest } from './AuthRequest'; import { authRequest } from './AuthRequest';
import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus';
import { useMatrixClient } from '../../hooks/useMatrixClient';
const failedDialog = () => { const failedDialog = () => {
const renderFailure = (requestClose) => ( const renderFailure = (requestClose) => (
@ -73,9 +73,9 @@ const securityKeyDialog = (key) => {
function CrossSigningSetup() { function CrossSigningSetup() {
const initialValues = { phrase: '', confirmPhrase: '' }; const initialValues = { phrase: '', confirmPhrase: '' };
const [genWithPhrase, setGenWithPhrase] = useState(undefined); const [genWithPhrase, setGenWithPhrase] = useState(undefined);
const mx = useMatrixClient();
const setup = async (securityPhrase = undefined) => { const setup = async (securityPhrase = undefined) => {
const mx = initMatrix.matrixClient;
setGenWithPhrase(typeof securityPhrase === 'string'); setGenWithPhrase(typeof securityPhrase === 'string');
const recoveryKey = await mx.createRecoveryKeyFromPassphrase(securityPhrase); const recoveryKey = await mx.createRecoveryKeyFromPassphrase(securityPhrase);
clearSecretStorageKeys(); clearSecretStorageKeys();

View file

@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import './DeviceManage.scss'; import './DeviceManage.scss';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import initMatrix from '../../../client/initMatrix';
import { isCrossVerified } from '../../../util/matrixUtil'; import { isCrossVerified } from '../../../util/matrixUtil';
import { openReusableDialog, openEmojiVerification } from '../../../client/action/navigation'; import { openReusableDialog, openEmojiVerification } from '../../../client/action/navigation';
@ -26,6 +25,7 @@ import { useStore } from '../../hooks/useStore';
import { useDeviceList } from '../../hooks/useDeviceList'; import { useDeviceList } from '../../hooks/useDeviceList';
import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus';
import { accessSecretStorage } from './SecretStorageAccess'; import { accessSecretStorage } from './SecretStorageAccess';
import { useMatrixClient } from '../../hooks/useMatrixClient';
const promptDeviceName = async (deviceName) => new Promise((resolve) => { const promptDeviceName = async (deviceName) => new Promise((resolve) => {
let isCompleted = false; let isCompleted = false;
@ -63,14 +63,14 @@ const promptDeviceName = async (deviceName) => new Promise((resolve) => {
function DeviceManage() { function DeviceManage() {
const TRUNCATED_COUNT = 4; const TRUNCATED_COUNT = 4;
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const isCSEnabled = useCrossSigningStatus(); const isCSEnabled = useCrossSigningStatus();
const deviceList = useDeviceList(); const deviceList = useDeviceList();
const [processing, setProcessing] = useState([]); const [processing, setProcessing] = useState([]);
const [truncated, setTruncated] = useState(true); const [truncated, setTruncated] = useState(true);
const mountStore = useStore(); const mountStore = useStore();
mountStore.setItem(true); mountStore.setItem(true);
const isMeVerified = isCrossVerified(mx.deviceId); const isMeVerified = isCrossVerified(mx, mx.deviceId);
useEffect(() => { useEffect(() => {
setProcessing([]); setProcessing([]);
@ -130,7 +130,7 @@ function DeviceManage() {
}; };
const verifyWithKey = async (device) => { const verifyWithKey = async (device) => {
const keyData = await accessSecretStorage('Session verification'); const keyData = await accessSecretStorage(mx, 'Session verification');
if (!keyData) return; if (!keyData) return;
addToProcessing(device); addToProcessing(device);
await mx.checkOwnCrossSigningTrust(); await mx.checkOwnCrossSigningTrust();
@ -191,7 +191,7 @@ function DeviceManage() {
)} )}
{isCurrentDevice && ( {isCurrentDevice && (
<Text style={{ marginTop: 'var(--sp-ultra-tight)' }} variant="b3"> <Text style={{ marginTop: 'var(--sp-ultra-tight)' }} variant="b3">
{`Session Key: ${initMatrix.matrixClient.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`} {`Session Key: ${mx.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`}
</Text> </Text>
)} )}
</> </>
@ -204,7 +204,7 @@ function DeviceManage() {
const verified = []; const verified = [];
const noEncryption = []; const noEncryption = [];
deviceList.sort((a, b) => b.last_seen_ts - a.last_seen_ts).forEach((device) => { deviceList.sort((a, b) => b.last_seen_ts - a.last_seen_ts).forEach((device) => {
const isVerified = isCrossVerified(device.device_id); const isVerified = isCrossVerified(mx, device.device_id);
if (isVerified === true) { if (isVerified === true) {
verified.push(device); verified.push(device);
} else if (isVerified === false) { } else if (isVerified === false) {

View file

@ -3,7 +3,6 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './KeyBackup.scss'; import './KeyBackup.scss';
import initMatrix from '../../../client/initMatrix';
import { openReusableDialog } from '../../../client/action/navigation'; import { openReusableDialog } from '../../../client/action/navigation';
import { deletePrivateKey } from '../../../client/state/secretStorageKeys'; import { deletePrivateKey } from '../../../client/state/secretStorageKeys';
@ -22,10 +21,11 @@ import DownloadIC from '../../../../public/res/ic/outlined/download.svg';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function CreateKeyBackupDialog({ keyData }) { function CreateKeyBackupDialog({ keyData }) {
const [done, setDone] = useState(false); const [done, setDone] = useState(false);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const mountStore = useStore(); const mountStore = useStore();
const doBackup = async () => { const doBackup = async () => {
@ -80,7 +80,7 @@ CreateKeyBackupDialog.propTypes = {
function RestoreKeyBackupDialog({ keyData }) { function RestoreKeyBackupDialog({ keyData }) {
const [status, setStatus] = useState(false); const [status, setStatus] = useState(false);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const mountStore = useStore(); const mountStore = useStore();
const restoreBackup = async () => { const restoreBackup = async () => {
@ -150,7 +150,7 @@ RestoreKeyBackupDialog.propTypes = {
function DeleteKeyBackupDialog({ requestClose }) { function DeleteKeyBackupDialog({ requestClose }) {
const [isDeleting, setIsDeleting] = useState(false); const [isDeleting, setIsDeleting] = useState(false);
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const mountStore = useStore(); const mountStore = useStore();
const deleteBackup = async () => { const deleteBackup = async () => {
@ -187,7 +187,7 @@ DeleteKeyBackupDialog.propTypes = {
}; };
function KeyBackup() { function KeyBackup() {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const isCSEnabled = useCrossSigningStatus(); const isCSEnabled = useCrossSigningStatus();
const [keyBackup, setKeyBackup] = useState(undefined); const [keyBackup, setKeyBackup] = useState(undefined);
const mountStore = useStore(); const mountStore = useStore();
@ -215,7 +215,7 @@ function KeyBackup() {
}, [isCSEnabled]); }, [isCSEnabled]);
const openCreateKeyBackup = async () => { const openCreateKeyBackup = async () => {
const keyData = await accessSecretStorage('Create Key Backup'); const keyData = await accessSecretStorage(mx, 'Create Key Backup');
if (keyData === null) return; if (keyData === null) return;
openReusableDialog( openReusableDialog(
@ -228,7 +228,7 @@ function KeyBackup() {
}; };
const openRestoreKeyBackup = async () => { const openRestoreKeyBackup = async () => {
const keyData = await accessSecretStorage('Restore Key Backup'); const keyData = await accessSecretStorage(mx, 'Restore Key Backup');
if (keyData === null) return; if (keyData === null) return;
openReusableDialog( openReusableDialog(

View file

@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import './SecretStorageAccess.scss'; import './SecretStorageAccess.scss';
import { deriveKey } from 'matrix-js-sdk/lib/crypto/key_passphrase'; import { deriveKey } from 'matrix-js-sdk/lib/crypto/key_passphrase';
import initMatrix from '../../../client/initMatrix';
import { openReusableDialog } from '../../../client/action/navigation'; import { openReusableDialog } from '../../../client/action/navigation';
import { getDefaultSSKey, getSSKeyInfo } from '../../../util/matrixUtil'; import { getDefaultSSKey, getSSKeyInfo } from '../../../util/matrixUtil';
import { storePrivateKey, hasPrivateKey, getPrivateKey } from '../../../client/state/secretStorageKeys'; import { storePrivateKey, hasPrivateKey, getPrivateKey } from '../../../client/state/secretStorageKeys';
@ -14,11 +13,12 @@ import Input from '../../atoms/input/Input';
import Spinner from '../../atoms/spinner/Spinner'; import Spinner from '../../atoms/spinner/Spinner';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function SecretStorageAccess({ onComplete }) { function SecretStorageAccess({ onComplete }) {
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const sSKeyId = getDefaultSSKey(); const sSKeyId = getDefaultSSKey(mx);
const sSKeyInfo = getSSKeyInfo(sSKeyId); const sSKeyInfo = getSSKeyInfo(mx, sSKeyId);
const isPassphrase = !!sSKeyInfo.passphrase; const isPassphrase = !!sSKeyInfo.passphrase;
const [withPhrase, setWithPhrase] = useState(isPassphrase); const [withPhrase, setWithPhrase] = useState(isPassphrase);
const [process, setProcess] = useState(false); const [process, setProcess] = useState(false);
@ -98,12 +98,13 @@ SecretStorageAccess.propTypes = {
}; };
/** /**
* @param {MatrixClient} mx Matrix client
* @param {string} title Title of secret storage access dialog * @param {string} title Title of secret storage access dialog
* @returns {Promise<keyData | null>} resolve to keyData or null * @returns {Promise<keyData | null>} resolve to keyData or null
*/ */
export const accessSecretStorage = (title) => new Promise((resolve) => { export const accessSecretStorage = (mx, title) => new Promise((resolve) => {
let isCompleted = false; let isCompleted = false;
const defaultSSKey = getDefaultSSKey(); const defaultSSKey = getDefaultSSKey(mx);
if (hasPrivateKey(defaultSSKey)) { if (hasPrivateKey(defaultSSKey)) {
resolve({ keyId: defaultSSKey, privateKey: getPrivateKey(defaultSSKey) }); resolve({ keyId: defaultSSKey, privateKey: getPrivateKey(defaultSSKey) });
return; return;

View file

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import './Settings.scss'; import './Settings.scss';
import initMatrix from '../../../client/initMatrix'; import { clearCacheAndReload, logoutClient } from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import settings from '../../../client/state/settings'; import settings from '../../../client/state/settings';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
@ -47,6 +47,7 @@ import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings'; import { settingsAtom } from '../../state/settings';
import { isMacOS } from '../../utils/user-agent'; import { isMacOS } from '../../utils/user-agent';
import { KeySymbol } from '../../utils/key-symbol'; import { KeySymbol } from '../../utils/key-symbol';
import { useMatrixClient } from '../../hooks/useMatrixClient';
function AppearanceSection() { function AppearanceSection() {
const [, updateState] = useState({}); const [, updateState] = useState({});
@ -332,6 +333,8 @@ function SecuritySection() {
} }
function AboutSection() { function AboutSection() {
const mx = useMatrixClient();
return ( return (
<div className="settings-about"> <div className="settings-about">
<div className="settings-about__card"> <div className="settings-about__card">
@ -348,7 +351,7 @@ function AboutSection() {
<div className="settings-about__btns"> <div className="settings-about__btns">
<Button onClick={() => window.open('https://github.com/ajbura/cinny')}>Source code</Button> <Button onClick={() => window.open('https://github.com/ajbura/cinny')}>Source code</Button>
<Button onClick={() => window.open('https://cinny.in/#sponsor')}>Support</Button> <Button onClick={() => window.open('https://cinny.in/#sponsor')}>Support</Button>
<Button onClick={() => initMatrix.clearCacheAndReload()} variant="danger">Clear cache & reload</Button> <Button onClick={() => clearCacheAndReload(mx)} variant="danger">Clear cache & reload</Button>
</div> </div>
</div> </div>
</div> </div>
@ -437,11 +440,12 @@ function useWindowToggle(setSelectedTab) {
function Settings() { function Settings() {
const [selectedTab, setSelectedTab] = useState(tabItems[0]); const [selectedTab, setSelectedTab] = useState(tabItems[0]);
const [isOpen, requestClose] = useWindowToggle(setSelectedTab); const [isOpen, requestClose] = useWindowToggle(setSelectedTab);
const mx = useMatrixClient();
const handleTabChange = (tabItem) => setSelectedTab(tabItem); const handleTabChange = (tabItem) => setSelectedTab(tabItem);
const handleLogout = async () => { const handleLogout = async () => {
if (await confirmDialog('Logout', 'Are you sure that you want to logout your session?', 'Logout', 'danger')) { if (await confirmDialog('Logout', 'Are you sure that you want to logout your session?', 'Logout', 'danger')) {
initMatrix.logout(); logoutClient(mx);
} }
}; };
@ -462,7 +466,7 @@ function Settings() {
> >
{isOpen && ( {isOpen && (
<div className="settings-window__content"> <div className="settings-window__content">
<ProfileEditor userId={initMatrix.matrixClient.getUserId()} /> <ProfileEditor userId={mx.getUserId()} />
<Tabs <Tabs
items={tabItems} items={tabItems}
defaultSelected={tabItems.findIndex((tab) => tab.text === selectedTab.text)} defaultSelected={tabItems.findIndex((tab) => tab.text === selectedTab.text)}

View file

@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './SpaceSettings.scss'; import './SpaceSettings.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
@ -59,8 +58,8 @@ const tabItems = [
]; ];
function GeneralSettings({ roomId }) { function GeneralSettings({ roomId }) {
const roomName = initMatrix.matrixClient.getRoom(roomId)?.name;
const mx = useMatrixClient(); const mx = useMatrixClient();
const roomName = mx.getRoom(roomId)?.name;
return ( return (
<> <>
@ -124,7 +123,7 @@ function SpaceSettings() {
const isOpen = window !== null; const isOpen = window !== null;
const roomId = window?.roomId; const roomId = window?.roomId;
const mx = initMatrix.matrixClient; const mx = useMatrixClient();
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const handleTabChange = (tabItem) => { const handleTabChange = (tabItem) => {

View file

@ -7,7 +7,7 @@ type ClientLayoutProps = {
}; };
export function ClientLayout({ nav, children }: ClientLayoutProps) { export function ClientLayout({ nav, children }: ClientLayoutProps) {
return ( return (
<Box style={{ height: '100%' }}> <Box grow="Yes">
<Box shrink="No">{nav}</Box> <Box shrink="No">{nav}</Box>
<Box grow="Yes">{children}</Box> <Box grow="Yes">{children}</Box>
</Box> </Box>

View file

@ -1,6 +1,27 @@
import { Box, Spinner, Text } from 'folds'; import {
import React, { ReactNode, useEffect, useState } from 'react'; Box,
import initMatrix from '../../../client/initMatrix'; Button,
config,
Dialog,
Icon,
IconButton,
Icons,
Menu,
MenuItem,
PopOut,
RectCords,
Spinner,
Text,
} from 'folds';
import { HttpApiEvent, HttpApiEventHandlerMap, MatrixClient } from 'matrix-js-sdk';
import FocusTrap from 'focus-trap-react';
import React, { MouseEventHandler, ReactNode, useCallback, useEffect, useState } from 'react';
import {
clearCacheAndReload,
initClient,
logoutClient,
startClient,
} from '../../../client/initMatrix';
import { getSecret } from '../../../client/state/auth'; import { getSecret } from '../../../client/state/auth';
import { SplashScreen } from '../../components/splash-screen'; import { SplashScreen } from '../../components/splash-screen';
import { CapabilitiesAndMediaConfigLoader } from '../../components/CapabilitiesAndMediaConfigLoader'; import { CapabilitiesAndMediaConfigLoader } from '../../components/CapabilitiesAndMediaConfigLoader';
@ -13,6 +34,10 @@ import Dialogs from '../../organisms/pw/Dialogs';
import ReusableContextMenu from '../../atoms/context-menu/ReusableContextMenu'; import ReusableContextMenu from '../../atoms/context-menu/ReusableContextMenu';
import { useSetting } from '../../state/hooks/settings'; import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings'; import { settingsAtom } from '../../state/settings';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { useSyncState } from '../../hooks/useSyncState';
import { stopPropagation } from '../../utils/keyboard';
import { SyncStatus } from './SyncStatus';
function SystemEmojiFeature() { function SystemEmojiFeature() {
const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
@ -37,6 +62,89 @@ function ClientRootLoading() {
); );
} }
function ClientRootOptions({ mx }: { mx: MatrixClient }) {
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
const handleToggle: MouseEventHandler<HTMLButtonElement> = (evt) => {
const cords = evt.currentTarget.getBoundingClientRect();
setMenuAnchor((currentState) => {
if (currentState) return undefined;
return cords;
});
};
return (
<IconButton
style={{
position: 'absolute',
top: config.space.S100,
right: config.space.S100,
}}
variant="Background"
fill="None"
onClick={handleToggle}
>
<Icon size="200" src={Icons.VerticalDots} />
<PopOut
anchor={menuAnchor}
position="Bottom"
align="End"
offset={6}
content={
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
escapeDeactivates: stopPropagation,
}}
>
<Menu>
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
<MenuItem onClick={() => clearCacheAndReload(mx)} size="300" radii="300">
<Text as="span" size="T300" truncate>
Clear Cache and Reload
</Text>
</MenuItem>
<MenuItem
onClick={() => logoutClient(mx)}
size="300"
radii="300"
variant="Critical"
fill="None"
>
<Text as="span" size="T300" truncate>
Logout
</Text>
</MenuItem>
</Box>
</Menu>
</FocusTrap>
}
/>
</IconButton>
);
}
const useLogoutListener = (mx?: MatrixClient) => {
useEffect(() => {
const handleLogout: HttpApiEventHandlerMap[HttpApiEvent.SessionLoggedOut] = async () => {
mx?.stopClient();
await mx?.clearStores();
window.localStorage.clear();
window.location.reload();
};
mx?.on(HttpApiEvent.SessionLoggedOut, handleLogout);
return () => {
mx?.removeListener(HttpApiEvent.SessionLoggedOut, handleLogout);
};
}, [mx]);
};
type ClientRootProps = { type ClientRootProps = {
children: ReactNode; children: ReactNode;
}; };
@ -44,30 +152,71 @@ export function ClientRoot({ children }: ClientRootProps) {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const { baseUrl } = getSecret(); const { baseUrl } = getSecret();
const [loadState, loadMatrix] = useAsyncCallback<MatrixClient, Error, []>(
useCallback(() => initClient(getSecret() as any), [])
);
const mx = loadState.status === AsyncStatus.Success ? loadState.data : undefined;
const [startState, startMatrix] = useAsyncCallback<void, Error, [MatrixClient]>(
useCallback((m) => startClient(m), [])
);
useLogoutListener(mx);
useEffect(() => { useEffect(() => {
const handleStart = () => { if (loadState.status === AsyncStatus.Idle) {
loadMatrix();
}
}, [loadState, loadMatrix]);
useEffect(() => {
if (mx && !mx.clientRunning) {
startMatrix(mx);
}
}, [mx, startMatrix]);
useSyncState(
mx,
useCallback((state) => {
if (state === 'PREPARED') {
setLoading(false); setLoading(false);
}; }
initMatrix.once('init_loading_finished', handleStart); }, [])
if (!initMatrix.matrixClient) initMatrix.init(); );
return () => {
initMatrix.removeListener('init_loading_finished', handleStart);
};
}, []);
return ( return (
<SpecVersions baseUrl={baseUrl!}> <SpecVersions baseUrl={baseUrl!}>
{loading ? ( {mx && <SyncStatus mx={mx} />}
{loading && mx && <ClientRootOptions mx={mx} />}
{(loadState.status === AsyncStatus.Error || startState.status === AsyncStatus.Error) && (
<SplashScreen>
<Box direction="Column" grow="Yes" alignItems="Center" justifyContent="Center" gap="400">
<Dialog>
<Box direction="Column" gap="400" style={{ padding: config.space.S400 }}>
{loadState.status === AsyncStatus.Error && (
<Text>{`Failed to load. ${loadState.error.message}`}</Text>
)}
{startState.status === AsyncStatus.Error && (
<Text>{`Failed to load. ${startState.error.message}`}</Text>
)}
<Button variant="Critical" onClick={loadMatrix}>
<Text as="span" size="B400">
Retry
</Text>
</Button>
</Box>
</Dialog>
</Box>
</SplashScreen>
)}
{loading || !mx ? (
<ClientRootLoading /> <ClientRootLoading />
) : ( ) : (
<MatrixClientProvider value={initMatrix.matrixClient!}> <MatrixClientProvider value={mx}>
<CapabilitiesAndMediaConfigLoader> <CapabilitiesAndMediaConfigLoader>
{(capabilities, mediaConfig) => ( {(capabilities, mediaConfig) => (
<CapabilitiesProvider value={capabilities ?? {}}> <CapabilitiesProvider value={capabilities ?? {}}>
<MediaConfigProvider value={mediaConfig ?? {}}> <MediaConfigProvider value={mediaConfig ?? {}}>
{children} {children}
{/* TODO: remove these components after navigation refactor */}
<Windows /> <Windows />
<Dialogs /> <Dialogs />
<ReusableContextMenu /> <ReusableContextMenu />

View file

@ -0,0 +1,87 @@
import { MatrixClient, SyncState } from 'matrix-js-sdk';
import React, { useCallback, useState } from 'react';
import { Box, config, Line, Text } from 'folds';
import { useSyncState } from '../../hooks/useSyncState';
import { ContainerColor } from '../../styles/ContainerColor.css';
type StateData = {
current: SyncState | null;
previous: SyncState | null | undefined;
};
type SyncStatusProps = {
mx: MatrixClient;
};
export function SyncStatus({ mx }: SyncStatusProps) {
const [stateData, setStateData] = useState<StateData>({
current: null,
previous: undefined,
});
useSyncState(
mx,
useCallback((current, previous) => {
setStateData((s) => {
if (s.current === current && s.previous === previous) {
return s;
}
return { current, previous };
});
}, [])
);
if (
(stateData.current === SyncState.Prepared ||
stateData.current === SyncState.Syncing ||
stateData.current === SyncState.Catchup) &&
stateData.previous !== SyncState.Syncing
) {
return (
<Box direction="Column" shrink="No">
<Box
className={ContainerColor({ variant: 'Success' })}
style={{ padding: `${config.space.S100} 0` }}
alignItems="Center"
justifyContent="Center"
>
<Text size="L400">Connecting...</Text>
</Box>
<Line variant="Success" size="300" />
</Box>
);
}
if (stateData.current === SyncState.Reconnecting) {
return (
<Box direction="Column" shrink="No">
<Box
className={ContainerColor({ variant: 'Warning' })}
style={{ padding: `${config.space.S100} 0` }}
alignItems="Center"
justifyContent="Center"
>
<Text size="L400">Connection Lost! Reconnecting...</Text>
</Box>
<Line variant="Warning" size="300" />
</Box>
);
}
if (stateData.current === SyncState.Error) {
return (
<Box direction="Column" shrink="No">
<Box
className={ContainerColor({ variant: 'Critical' })}
style={{ padding: `${config.space.S100} 0` }}
alignItems="Center"
justifyContent="Center"
>
<Text size="L400">Connection Lost!</Text>
</Box>
<Line variant="Critical" size="300" />
</Box>
);
}
return null;
}

View file

@ -18,7 +18,7 @@ export function WelcomePage() {
title="Welcome to Cinny" title="Welcome to Cinny"
subTitle={ subTitle={
<span> <span>
Yet anothor matrix client.{' '} Yet another matrix client.{' '}
<a <a
href="https://github.com/cinnyapp/cinny/releases" href="https://github.com/cinnyapp/cinny/releases"
target="_blank" target="_blank"

View file

@ -50,12 +50,13 @@ type DirectMenuProps = {
requestClose: () => void; requestClose: () => void;
}; };
const DirectMenu = forwardRef<HTMLDivElement, DirectMenuProps>(({ requestClose }, ref) => { const DirectMenu = forwardRef<HTMLDivElement, DirectMenuProps>(({ requestClose }, ref) => {
const mx = useMatrixClient();
const orphanRooms = useDirectRooms(); const orphanRooms = useDirectRooms();
const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
if (!unread) return; if (!unread) return;
orphanRooms.forEach((rId) => markAsRead(rId)); orphanRooms.forEach((rId) => markAsRead(mx, rId));
requestClose(); requestClose();
}; };

View file

@ -55,10 +55,11 @@ type HomeMenuProps = {
const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => { const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => {
const orphanRooms = useHomeRooms(); const orphanRooms = useHomeRooms();
const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
const mx = useMatrixClient();
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
if (!unread) return; if (!unread) return;
orphanRooms.forEach((rId) => markAsRead(rId)); orphanRooms.forEach((rId) => markAsRead(mx, rId));
requestClose(); requestClose();
}; };

View file

@ -356,7 +356,7 @@ function RoomNotificationsGroupComp({
onOpen(room.roomId, eventId); onOpen(room.roomId, eventId);
}; };
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
markAsRead(room.roomId); markAsRead(mx, room.roomId);
}; };
return ( return (

View file

@ -30,10 +30,11 @@ type DirectMenuProps = {
const DirectMenu = forwardRef<HTMLDivElement, DirectMenuProps>(({ requestClose }, ref) => { const DirectMenu = forwardRef<HTMLDivElement, DirectMenuProps>(({ requestClose }, ref) => {
const orphanRooms = useDirectRooms(); const orphanRooms = useDirectRooms();
const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
const mx = useMatrixClient();
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
if (!unread) return; if (!unread) return;
orphanRooms.forEach((rId) => markAsRead(rId)); orphanRooms.forEach((rId) => markAsRead(mx, rId));
requestClose(); requestClose();
}; };

View file

@ -31,10 +31,11 @@ type HomeMenuProps = {
const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => { const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => {
const orphanRooms = useHomeRooms(); const orphanRooms = useHomeRooms();
const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
const mx = useMatrixClient();
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
if (!unread) return; if (!unread) return;
orphanRooms.forEach((rId) => markAsRead(rId)); orphanRooms.forEach((rId) => markAsRead(mx, rId));
requestClose(); requestClose();
}; };

View file

@ -114,7 +114,7 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
const unread = useRoomsUnread(allChild, roomToUnreadAtom); const unread = useRoomsUnread(allChild, roomToUnreadAtom);
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
allChild.forEach((childRoomId) => markAsRead(childRoomId)); allChild.forEach((childRoomId) => markAsRead(mx, childRoomId));
requestClose(); requestClose();
}; };

View file

@ -95,7 +95,7 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClo
const unread = useRoomsUnread(allChild, roomToUnreadAtom); const unread = useRoomsUnread(allChild, roomToUnreadAtom);
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
allChild.forEach((childRoomId) => markAsRead(childRoomId)); allChild.forEach((childRoomId) => markAsRead(mx, childRoomId));
requestClose(); requestClose();
}; };

View file

@ -1,13 +1,11 @@
import initMatrix from '../initMatrix'; import { MatrixClient } from "matrix-js-sdk";
// eslint-disable-next-line import/prefer-default-export export async function markAsRead(mx: MatrixClient, roomId: string) {
export async function markAsRead(roomId) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
if (!room) return; if (!room) return;
const timeline = room.getLiveTimeline().getEvents(); const timeline = room.getLiveTimeline().getEvents();
const readEventId = room.getEventReadUpTo(mx.getUserId()); const readEventId = room.getEventReadUpTo(mx.getUserId()!);
const getLatestValidEvent = () => { const getLatestValidEvent = () => {
for (let i = timeline.length - 1; i >= 0; i -= 1) { for (let i = timeline.length - 1; i >= 0; i -= 1) {

View file

@ -1,16 +1,13 @@
import initMatrix from '../initMatrix';
import appDispatcher from '../dispatcher';
import cons from '../state/cons';
import { getIdServer } from '../../util/matrixUtil'; import { getIdServer } from '../../util/matrixUtil';
/** /**
* https://github.com/matrix-org/matrix-react-sdk/blob/1e6c6e9d800890c732d60429449bc280de01a647/src/Rooms.js#L73 * https://github.com/matrix-org/matrix-react-sdk/blob/1e6c6e9d800890c732d60429449bc280de01a647/src/Rooms.js#L73
* @param {MatrixClient} mx Matrix client
* @param {string} roomId Id of room to add * @param {string} roomId Id of room to add
* @param {string} userId User id to which dm || undefined to remove * @param {string} userId User id to which dm || undefined to remove
* @returns {Promise} A promise * @returns {Promise} A promise
*/ */
function addRoomToMDirect(roomId, userId) { function addRoomToMDirect(mx, roomId, userId) {
const mx = initMatrix.matrixClient;
const mDirectsEvent = mx.getAccountData('m.direct'); const mDirectsEvent = mx.getAccountData('m.direct');
let userIdToRoomIds = {}; let userIdToRoomIds = {};
@ -79,24 +76,22 @@ function guessDMRoomTargetId(room, myUserId) {
return oldestMember.userId; return oldestMember.userId;
} }
function convertToDm(roomId) { function convertToDm(mx, roomId) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
return addRoomToMDirect(roomId, guessDMRoomTargetId(room, mx.getUserId())); return addRoomToMDirect(mx, roomId, guessDMRoomTargetId(room, mx.getUserId()));
} }
function convertToRoom(roomId) { function convertToRoom(mx, roomId) {
return addRoomToMDirect(roomId, undefined); return addRoomToMDirect(mx, roomId, undefined);
} }
/** /**
* * @param {MatrixClient} mx
* @param {string} roomId * @param {string} roomId
* @param {boolean} isDM * @param {boolean} isDM
* @param {string[]} via * @param {string[]} via
*/ */
async function join(roomIdOrAlias, isDM = false, via = undefined) { async function join(mx, roomIdOrAlias, isDM = false, via = undefined) {
const mx = initMatrix.matrixClient;
const roomIdParts = roomIdOrAlias.split(':'); const roomIdParts = roomIdOrAlias.split(':');
const viaServers = via || [roomIdParts[1]]; const viaServers = via || [roomIdParts[1]];
@ -105,7 +100,7 @@ async function join(roomIdOrAlias, isDM = false, via = undefined) {
if (isDM) { if (isDM) {
const targetUserId = guessDMRoomTargetId(mx.getRoom(resultRoom.roomId), mx.getUserId()); const targetUserId = guessDMRoomTargetId(mx.getRoom(resultRoom.roomId), mx.getUserId());
await addRoomToMDirect(resultRoom.roomId, targetUserId); await addRoomToMDirect(mx, resultRoom.roomId, targetUserId);
} }
return resultRoom.roomId; return resultRoom.roomId;
} catch (e) { } catch (e) {
@ -113,12 +108,11 @@ async function join(roomIdOrAlias, isDM = false, via = undefined) {
} }
} }
async function create(options, isDM = false) { async function create(mx, options, isDM = false) {
const mx = initMatrix.matrixClient;
try { try {
const result = await mx.createRoom(options); const result = await mx.createRoom(options);
if (isDM && typeof options.invite?.[0] === 'string') { if (isDM && typeof options.invite?.[0] === 'string') {
await addRoomToMDirect(result.room_id, options.invite[0]); await addRoomToMDirect(mx, result.room_id, options.invite[0]);
} }
return result; return result;
} catch (e) { } catch (e) {
@ -130,7 +124,7 @@ async function create(options, isDM = false) {
} }
} }
async function createDM(userIdOrIds, isEncrypted = true) { async function createDM(mx, userIdOrIds, isEncrypted = true) {
const options = { const options = {
is_direct: true, is_direct: true,
invite: Array.isArray(userIdOrIds) ? userIdOrIds : [userIdOrIds], invite: Array.isArray(userIdOrIds) ? userIdOrIds : [userIdOrIds],
@ -148,11 +142,11 @@ async function createDM(userIdOrIds, isEncrypted = true) {
}); });
} }
const result = await create(options, true); const result = await create(mx, options, true);
return result; return result;
} }
async function createRoom(opts) { async function createRoom(mx, opts) {
// joinRule: 'public' | 'invite' | 'restricted' // joinRule: 'public' | 'invite' | 'restricted'
const { name, topic, joinRule } = opts; const { name, topic, joinRule } = opts;
const alias = opts.alias ?? undefined; const alias = opts.alias ?? undefined;
@ -162,7 +156,6 @@ async function createRoom(opts) {
const powerLevel = opts.powerLevel ?? undefined; const powerLevel = opts.powerLevel ?? undefined;
const blockFederation = opts.blockFederation ?? false; const blockFederation = opts.blockFederation ?? false;
const mx = initMatrix.matrixClient;
const visibility = joinRule === 'public' ? 'public' : 'private'; const visibility = joinRule === 'public' ? 'public' : 'private';
const options = { const options = {
creation_content: undefined, creation_content: undefined,
@ -225,7 +218,7 @@ async function createRoom(opts) {
}); });
} }
const result = await create(options); const result = await create(mx, options);
if (parentId) { if (parentId) {
await mx.sendStateEvent(parentId, 'm.space.child', { await mx.sendStateEvent(parentId, 'm.space.child', {
@ -238,51 +231,19 @@ async function createRoom(opts) {
return result; return result;
} }
async function invite(roomId, userId, reason) { async function ignore(mx, userIds) {
const mx = initMatrix.matrixClient;
const result = await mx.invite(roomId, userId, undefined, reason);
return result;
}
async function kick(roomId, userId, reason) {
const mx = initMatrix.matrixClient;
const result = await mx.kick(roomId, userId, reason);
return result;
}
async function ban(roomId, userId, reason) {
const mx = initMatrix.matrixClient;
const result = await mx.ban(roomId, userId, reason);
return result;
}
async function unban(roomId, userId) {
const mx = initMatrix.matrixClient;
const result = await mx.unban(roomId, userId);
return result;
}
async function ignore(userIds) {
const mx = initMatrix.matrixClient;
let ignoredUsers = mx.getIgnoredUsers().concat(userIds); let ignoredUsers = mx.getIgnoredUsers().concat(userIds);
ignoredUsers = [...new Set(ignoredUsers)]; ignoredUsers = [...new Set(ignoredUsers)];
await mx.setIgnoredUsers(ignoredUsers); await mx.setIgnoredUsers(ignoredUsers);
} }
async function unignore(userIds) { async function unignore(mx, userIds) {
const mx = initMatrix.matrixClient;
const ignoredUsers = mx.getIgnoredUsers(); const ignoredUsers = mx.getIgnoredUsers();
await mx.setIgnoredUsers(ignoredUsers.filter((id) => !userIds.includes(id))); await mx.setIgnoredUsers(ignoredUsers.filter((id) => !userIds.includes(id)));
} }
async function setPowerLevel(roomId, userId, powerLevel) { async function setPowerLevel(mx, roomId, userId, powerLevel) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const powerlevelEvent = room.currentState.getStateEvents('m.room.power_levels')[0]; const powerlevelEvent = room.currentState.getStateEvents('m.room.power_levels')[0];
@ -291,8 +252,7 @@ async function setPowerLevel(roomId, userId, powerLevel) {
return result; return result;
} }
async function setMyRoomNick(roomId, nick) { async function setMyRoomNick(mx, roomId, nick) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const mEvent = room.currentState.getStateEvents('m.room.member', mx.getUserId()); const mEvent = room.currentState.getStateEvents('m.room.member', mx.getUserId());
const content = mEvent?.getContent(); const content = mEvent?.getContent();
@ -303,8 +263,7 @@ async function setMyRoomNick(roomId, nick) {
}, mx.getUserId()); }, mx.getUserId());
} }
async function setMyRoomAvatar(roomId, mxc) { async function setMyRoomAvatar(mx, roomId, mxc) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
const mEvent = room.currentState.getStateEvents('m.room.member', mx.getUserId()); const mEvent = room.currentState.getStateEvents('m.room.member', mx.getUserId());
const content = mEvent?.getContent(); const content = mEvent?.getContent();
@ -320,7 +279,6 @@ export {
convertToRoom, convertToRoom,
join, join,
createDM, createRoom, createDM, createRoom,
invite, kick, ban, unban,
ignore, unignore, ignore, unignore,
setPowerLevel, setPowerLevel,
setMyRoomNick, setMyRoomAvatar, setMyRoomNick, setMyRoomAvatar,

View file

@ -1,128 +0,0 @@
import EventEmitter from 'events';
import * as sdk from 'matrix-js-sdk';
import Olm from '@matrix-org/olm';
import { logger } from 'matrix-js-sdk/lib/logger';
import { getSecret } from './state/auth';
import { cryptoCallbacks } from './state/secretStorageKeys';
global.Olm = Olm;
if (import.meta.env.PROD) {
logger.disableAll();
}
class InitMatrix extends EventEmitter {
async init() {
if (this.matrixClient || this.initializing) {
console.warn('Client is already initialized!')
return;
}
this.initializing = true;
try {
await this.startClient();
this.setupSync();
this.listenEvents();
this.initializing = false;
} catch {
this.initializing = false;
}
}
async startClient() {
const indexedDBStore = new sdk.IndexedDBStore({
indexedDB: global.indexedDB,
localStorage: global.localStorage,
dbName: 'web-sync-store',
});
await indexedDBStore.startup();
const secret = getSecret();
this.matrixClient = sdk.createClient({
baseUrl: secret.baseUrl,
accessToken: secret.accessToken,
userId: secret.userId,
store: indexedDBStore,
cryptoStore: new sdk.IndexedDBCryptoStore(global.indexedDB, 'crypto-store'),
deviceId: secret.deviceId,
timelineSupport: true,
cryptoCallbacks,
verificationMethods: [
'm.sas.v1',
],
});
await this.matrixClient.initCrypto();
await this.matrixClient.startClient({
lazyLoadMembers: true,
});
this.matrixClient.setGlobalErrorOnUnknownDevices(false);
this.matrixClient.setMaxListeners(50);
}
setupSync() {
const sync = {
NULL: () => {
console.log('NULL state');
},
SYNCING: () => {
console.log('SYNCING state');
},
PREPARED: (prevState) => {
console.log('PREPARED state');
console.log('Previous state: ', prevState);
global.initMatrix = this;
if (prevState === null) {
this.emit('init_loading_finished');
}
},
RECONNECTING: () => {
console.log('RECONNECTING state');
},
CATCHUP: () => {
console.log('CATCHUP state');
},
ERROR: () => {
console.log('ERROR state');
},
STOPPED: () => {
console.log('STOPPED state');
},
};
this.matrixClient.on('sync', (state, prevState) => sync[state](prevState));
}
listenEvents() {
this.matrixClient.on('Session.logged_out', async () => {
this.matrixClient.stopClient();
await this.matrixClient.clearStores();
window.localStorage.clear();
window.location.reload();
});
}
async logout() {
this.matrixClient.stopClient();
try {
await this.matrixClient.logout();
} catch {
// ignore if failed to logout
}
await this.matrixClient.clearStores();
window.localStorage.clear();
window.location.reload();
}
clearCacheAndReload() {
this.matrixClient.stopClient();
this.matrixClient.store.deleteAllData().then(() => {
window.location.reload();
});
}
}
const initMatrix = new InitMatrix();
export default initMatrix;

70
src/client/initMatrix.ts Normal file
View file

@ -0,0 +1,70 @@
import { createClient, MatrixClient, IndexedDBStore, IndexedDBCryptoStore } from 'matrix-js-sdk';
import Olm from '@matrix-org/olm';
import { logger } from 'matrix-js-sdk/lib/logger';
import { cryptoCallbacks } from './state/secretStorageKeys';
global.Olm = Olm;
if (import.meta.env.PROD) {
logger.disableAll();
}
type Session = {
baseUrl: string;
accessToken: string;
userId: string;
deviceId: string;
};
export const initClient = async (session: Session): Promise<MatrixClient> => {
const indexedDBStore = new IndexedDBStore({
indexedDB: global.indexedDB,
localStorage: global.localStorage,
dbName: 'web-sync-store',
});
await indexedDBStore.startup();
const mx = createClient({
baseUrl: session.baseUrl,
accessToken: session.accessToken,
userId: session.userId,
store: indexedDBStore,
cryptoStore: new IndexedDBCryptoStore(global.indexedDB, 'crypto-store'),
deviceId: session.deviceId,
timelineSupport: true,
cryptoCallbacks: cryptoCallbacks as any,
verificationMethods: ['m.sas.v1'],
});
await mx.initCrypto();
mx.setGlobalErrorOnUnknownDevices(false);
mx.setMaxListeners(50);
return mx;
};
export const startClient = async (mx: MatrixClient) => {
await mx.startClient({
lazyLoadMembers: true,
});
};
export const clearCacheAndReload = async (mx: MatrixClient) => {
mx.stopClient();
await mx.store.deleteAllData();
window.location.reload();
};
export const logoutClient = async (mx: MatrixClient) => {
mx.stopClient();
try {
await mx.logout();
} catch {
// ignore if failed to logout
}
await mx.clearStores();
window.localStorage.clear();
window.location.reload();
};

View file

@ -1,7 +0,0 @@
import { MatrixClient } from 'matrix-js-sdk';
import initMatrix from './initMatrix';
export const mx = (): MatrixClient => {
if (!initMatrix.matrixClient) console.error('Matrix client is used before initialization!');
return initMatrix.matrixClient!;
};

View file

@ -409,6 +409,8 @@ body {
#root { #root {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex;
flex-direction: column;
} }
*, *,

View file

@ -1,5 +1,3 @@
import initMatrix from '../client/initMatrix';
import HashIC from '../../public/res/ic/outlined/hash.svg'; import HashIC from '../../public/res/ic/outlined/hash.svg';
import HashGlobeIC from '../../public/res/ic/outlined/hash-globe.svg'; import HashGlobeIC from '../../public/res/ic/outlined/hash-globe.svg';
import HashLockIC from '../../public/res/ic/outlined/hash-lock.svg'; import HashLockIC from '../../public/res/ic/outlined/hash-lock.svg';
@ -24,8 +22,7 @@ export async function getBaseUrl(servername) {
} }
} }
export function getUsername(userId) { export function getUsername(mx, userId) {
const mx = initMatrix.matrixClient;
const user = mx.getUser(userId); const user = mx.getUser(userId);
if (user === null) return userId; if (user === null) return userId;
let username = user.displayName; let username = user.displayName;
@ -39,9 +36,9 @@ export function getUsernameOfRoomMember(roomMember) {
return roomMember.name || roomMember.userId; return roomMember.name || roomMember.userId;
} }
export async function isRoomAliasAvailable(alias) { export async function isRoomAliasAvailable(mx, alias) {
try { try {
const result = await initMatrix.matrixClient.getRoomIdForAlias(alias); const result = await mx.getRoomIdForAlias(alias);
if (result.room_id) return false; if (result.room_id) return false;
return false; return false;
} catch (e) { } catch (e) {
@ -159,9 +156,8 @@ export function genRoomVia(room) {
return via.concat(mostPop3.slice(0, 2)); return via.concat(mostPop3.slice(0, 2));
} }
export function isCrossVerified(deviceId) { export function isCrossVerified(mx, deviceId) {
try { try {
const mx = initMatrix.matrixClient;
const crossSignInfo = mx.getStoredCrossSigningForUser(mx.getUserId()); const crossSignInfo = mx.getStoredCrossSigningForUser(mx.getUserId());
const deviceInfo = mx.getStoredDevice(mx.getUserId(), deviceId); const deviceInfo = mx.getStoredDevice(mx.getUserId(), deviceId);
const deviceTrust = crossSignInfo.checkDeviceTrust(crossSignInfo, deviceInfo, false, true); const deviceTrust = crossSignInfo.checkDeviceTrust(crossSignInfo, deviceInfo, false, true);
@ -172,14 +168,12 @@ export function isCrossVerified(deviceId) {
} }
} }
export function hasCrossSigningAccountData() { export function hasCrossSigningAccountData(mx) {
const mx = initMatrix.matrixClient;
const masterKeyData = mx.getAccountData('m.cross_signing.master'); const masterKeyData = mx.getAccountData('m.cross_signing.master');
return !!masterKeyData; return !!masterKeyData;
} }
export function getDefaultSSKey() { export function getDefaultSSKey(mx) {
const mx = initMatrix.matrixClient;
try { try {
return mx.getAccountData('m.secret_storage.default_key').getContent().key; return mx.getAccountData('m.secret_storage.default_key').getContent().key;
} catch { } catch {
@ -187,8 +181,7 @@ export function getDefaultSSKey() {
} }
} }
export function getSSKeyInfo(key) { export function getSSKeyInfo(mx, key) {
const mx = initMatrix.matrixClient;
try { try {
return mx.getAccountData(`m.secret_storage.key.${key}`).getContent(); return mx.getAccountData(`m.secret_storage.key.${key}`).getContent();
} catch { } catch {
@ -196,8 +189,7 @@ export function getSSKeyInfo(key) {
} }
} }
export async function hasDevices(userId) { export async function hasDevices(mx, userId) {
const mx = initMatrix.matrixClient;
try { try {
const usersDeviceMap = await mx.downloadKeys([userId, mx.getUserId()]); const usersDeviceMap = await mx.downloadKeys([userId, mx.getUserId()]);
return Object.values(usersDeviceMap) return Object.values(usersDeviceMap)

View file

@ -1,30 +1,3 @@
import initMatrix from '../client/initMatrix';
export function roomIdByActivity(id1, id2) {
const room1 = initMatrix.matrixClient.getRoom(id1);
const room2 = initMatrix.matrixClient.getRoom(id2);
return room2.getLastActiveTimestamp() - room1.getLastActiveTimestamp();
}
export function roomIdByAtoZ(aId, bId) {
let aName = initMatrix.matrixClient.getRoom(aId).name;
let bName = initMatrix.matrixClient.getRoom(bId).name;
// remove "#" from the room name
// To ignore it in sorting
aName = aName.replace(/#/g, '');
bName = bName.replace(/#/g, '');
if (aName.toLowerCase() < bName.toLowerCase()) {
return -1;
}
if (aName.toLowerCase() > bName.toLowerCase()) {
return 1;
}
return 0;
}
export function memberByAtoZ(m1, m2) { export function memberByAtoZ(m1, m2) {
const aName = m1.name; const aName = m1.name;
const bName = m2.name; const bName = m2.name;