From e2228a18c1533bbf8b7a3f82f8353ae65bc69a80 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Mon, 22 Jul 2024 16:17:19 +0530 Subject: [PATCH] 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 --- src/app/features/room-nav/RoomNavItem.tsx | 2 +- src/app/features/room/Room.tsx | 4 +- src/app/features/room/RoomTimeline.tsx | 10 +- src/app/features/room/RoomTombstone.tsx | 46 ++--- src/app/features/room/RoomViewHeader.tsx | 2 +- src/app/hooks/useAccountData.js | 7 +- src/app/hooks/useCommands.ts | 28 +-- src/app/hooks/useCrossSigningStatus.js | 8 +- src/app/hooks/useDeviceList.js | 7 +- src/app/hooks/useSyncState.ts | 6 +- .../GlobalNotification.jsx | 4 +- .../global-notification/IgnoreUserList.jsx | 9 +- .../KeywordNotification.jsx | 4 +- src/app/molecules/image-pack/ImagePack.jsx | 37 ++-- .../molecules/image-pack/ImagePackUpload.jsx | 4 +- .../molecules/image-upload/ImageUpload.jsx | 7 +- .../ExportE2ERoomKeys.jsx | 5 +- .../ImportE2ERoomKeys.jsx | 5 +- .../molecules/room-aliases/RoomAliases.jsx | 14 +- src/app/molecules/room-emojis/RoomEmojis.jsx | 6 +- .../room-encryption/RoomEncryption.jsx | 4 +- .../RoomHistoryVisibility.jsx | 27 ++- .../molecules/room-members/RoomMembers.jsx | 10 +- .../room-notification/RoomNotification.jsx | 11 +- .../room-permissions/RoomPermissions.jsx | 8 +- .../molecules/room-profile/RoomProfile.jsx | 6 +- .../room-visibility/RoomVisibility.jsx | 15 +- .../space-add-existing/SpaceAddExisting.jsx | 8 +- src/app/organisms/create-room/CreateRoom.jsx | 10 +- .../emoji-verification/EmojiVerification.jsx | 14 +- src/app/organisms/invite-user/InviteUser.jsx | 8 +- src/app/organisms/join-alias/JoinAlias.jsx | 6 +- .../profile-editor/ProfileEditor.jsx | 6 +- .../profile-viewer/ProfileViewer.jsx | 46 ++--- src/app/organisms/room/RoomSettings.jsx | 7 +- src/app/organisms/search/Search.jsx | 16 +- src/app/organisms/settings/AuthRequest.jsx | 4 +- src/app/organisms/settings/CrossSigning.jsx | 4 +- src/app/organisms/settings/DeviceManage.jsx | 12 +- src/app/organisms/settings/KeyBackup.jsx | 14 +- .../settings/SecretStorageAccess.jsx | 13 +- src/app/organisms/settings/Settings.jsx | 12 +- .../space-settings/SpaceSettings.jsx | 5 +- src/app/pages/client/ClientLayout.tsx | 2 +- src/app/pages/client/ClientRoot.tsx | 181 ++++++++++++++++-- src/app/pages/client/SyncStatus.tsx | 87 +++++++++ src/app/pages/client/WelcomePage.tsx | 2 +- src/app/pages/client/direct/Direct.tsx | 3 +- src/app/pages/client/home/Home.tsx | 3 +- src/app/pages/client/inbox/Notifications.tsx | 2 +- src/app/pages/client/sidebar/DirectTab.tsx | 3 +- src/app/pages/client/sidebar/HomeTab.tsx | 3 +- src/app/pages/client/sidebar/SpaceTabs.tsx | 2 +- src/app/pages/client/space/Space.tsx | 2 +- .../{notifications.js => notifications.ts} | 8 +- src/client/action/room.js | 82 ++------ src/client/initMatrix.js | 128 ------------- src/client/initMatrix.ts | 70 +++++++ src/client/mx.ts | 7 - src/index.scss | 2 + src/util/matrixUtil.js | 24 +-- src/util/sort.js | 27 --- 62 files changed, 609 insertions(+), 510 deletions(-) create mode 100644 src/app/pages/client/SyncStatus.tsx rename src/client/action/{notifications.js => notifications.ts} (69%) delete mode 100644 src/client/initMatrix.js create mode 100644 src/client/initMatrix.ts delete mode 100644 src/client/mx.ts diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index 8ecc81a3..281c5b77 100644 --- a/src/app/features/room-nav/RoomNavItem.tsx +++ b/src/app/features/room-nav/RoomNavItem.tsx @@ -53,7 +53,7 @@ const RoomNavItemMenu = forwardRef( const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); const handleMarkAsRead = () => { - markAsRead(room.roomId); + markAsRead(mx, room.roomId); requestClose(); }; diff --git a/src/app/features/room/Room.tsx b/src/app/features/room/Room.tsx index fd578ec6..19ac53e1 100644 --- a/src/app/features/room/Room.tsx +++ b/src/app/features/room/Room.tsx @@ -11,10 +11,12 @@ import { PowerLevelsContextProvider, usePowerLevels } from '../../hooks/usePower import { useRoom } from '../../hooks/useRoom'; import { useKeyDown } from '../../hooks/useKeyDown'; import { markAsRead } from '../../../client/action/notifications'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; export function Room() { const { eventId } = useParams(); const room = useRoom(); + const mx = useMatrixClient(); const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer'); const screenSize = useScreenSizeContext(); @@ -25,7 +27,7 @@ export function Room() { useCallback( (evt) => { if (isKeyHotkey('escape', evt)) { - markAsRead(room.roomId); + markAsRead(mx, room.roomId); } }, [room.roomId] diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 14cdb56e..b9bfb843 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -597,7 +597,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli // so timeline can be updated with evt like: edits, reactions etc if (atBottomRef.current) { if (document.hasFocus() && (!unreadInfo || mEvt.getSender() === mx.getUserId())) { - requestAnimationFrame(() => markAsRead(mEvt.getRoomId())); + requestAnimationFrame(() => markAsRead(mx, mEvt.getRoomId())); } if (document.hasFocus()) { @@ -658,15 +658,15 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli const tryAutoMarkAsRead = useCallback(() => { if (!unreadInfo) { - requestAnimationFrame(() => markAsRead(room.roomId)); + requestAnimationFrame(() => markAsRead(mx, room.roomId)); return; } const evtTimeline = getEventTimeline(room, unreadInfo.readUptoEventId); const latestTimeline = evtTimeline && getFirstLinkedTimeline(evtTimeline, Direction.Forward); if (latestTimeline === room.getLiveTimeline()) { - requestAnimationFrame(() => markAsRead(room.roomId)); + requestAnimationFrame(() => markAsRead(mx, room.roomId)); } - }, [room, unreadInfo]); + }, [mx, room, unreadInfo]); const debounceSetAtBottom = useDebounce( useCallback((entry: IntersectionObserverEntry) => { @@ -832,7 +832,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli }; const handleMarkAsRead = () => { - markAsRead(room.roomId); + markAsRead(mx, room.roomId); }; const handleOpenReply: MouseEventHandler = useCallback( diff --git a/src/app/features/room/RoomTombstone.tsx b/src/app/features/room/RoomTombstone.tsx index bd8afdad..e3f8251f 100644 --- a/src/app/features/room/RoomTombstone.tsx +++ b/src/app/features/room/RoomTombstone.tsx @@ -40,28 +40,30 @@ export function RoomTombstone({ roomId, body, replacementRoomId }: RoomTombstone )} - {replacementRoom?.getMyMembership() === Membership.Join || - joinState.status === AsyncStatus.Success ? ( - - ) : ( - - )} + + {replacementRoom?.getMyMembership() === Membership.Join || + joinState.status === AsyncStatus.Success ? ( + + ) : ( + + )} + ); } diff --git a/src/app/features/room/RoomViewHeader.tsx b/src/app/features/room/RoomViewHeader.tsx index aa267c53..6750f923 100644 --- a/src/app/features/room/RoomViewHeader.tsx +++ b/src/app/features/room/RoomViewHeader.tsx @@ -74,7 +74,7 @@ const RoomMenu = forwardRef( const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); const handleMarkAsRead = () => { - markAsRead(room.roomId); + markAsRead(mx, room.roomId); requestClose(); }; diff --git a/src/app/hooks/useAccountData.js b/src/app/hooks/useAccountData.js index ed654d97..43cc11ed 100644 --- a/src/app/hooks/useAccountData.js +++ b/src/app/hooks/useAccountData.js @@ -1,10 +1,9 @@ /* eslint-disable import/prefer-default-export */ import { useState, useEffect } from 'react'; - -import initMatrix from '../../client/initMatrix'; +import { useMatrixClient } from './useMatrixClient'; export function useAccountData(eventType) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const [event, setEvent] = useState(mx.getAccountData(eventType)); useEffect(() => { @@ -16,7 +15,7 @@ export function useAccountData(eventType) { return () => { mx.removeListener('accountData', handleChange); }; - }, [eventType]); + }, [mx, eventType]); return event; } diff --git a/src/app/hooks/useCommands.ts b/src/app/hooks/useCommands.ts index 3c829514..182d1307 100644 --- a/src/app/hooks/useCommands.ts +++ b/src/app/hooks/useCommands.ts @@ -92,9 +92,9 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { 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 result = await roomActions.createDM(userIds, isEncrypt); + const result = await roomActions.createDM(mx, userIds, isEncrypt); navigateRoom(result.room_id); }, }, @@ -106,7 +106,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { const roomIds = rawIds.filter( (idOrAlias) => isRoomId(idOrAlias) || isRoomAlias(idOrAlias) ); - roomIds.map((id) => roomActions.join(id)); + roomIds.map((id) => roomActions.join(mx, id)); }, }, [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]', exe: async (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]: { @@ -135,7 +135,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { description: 'Disinvite user to room. Example: /disinvite userId1 userId2 [-r reason]', exe: async (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]: { @@ -143,7 +143,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { description: 'Kick user from room. Example: /kick userId1 userId2 [-r reason]', exe: async (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]: { @@ -151,7 +151,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { description: 'Ban user from room. Example: /ban userId1 userId2 [-r reason]', exe: async (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]: { @@ -160,7 +160,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { exe: async (payload) => { const rawIds = payload.split(' '); 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]: { @@ -169,7 +169,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { exe: async (payload) => { const rawIds = payload.split(' '); const userIds = rawIds.filter((id) => isUserId(id)); - if (userIds.length > 0) roomActions.ignore(userIds); + if (userIds.length > 0) roomActions.ignore(mx, userIds); }, }, [Command.UnIgnore]: { @@ -178,7 +178,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { exe: async (payload) => { const rawIds = payload.split(' '); const userIds = rawIds.filter((id) => isUserId(id)); - if (userIds.length > 0) roomActions.unignore(userIds); + if (userIds.length > 0) roomActions.unignore(mx, userIds); }, }, [Command.MyRoomNick]: { @@ -187,7 +187,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { exe: async (payload) => { const nick = payload.trim(); if (nick === '') return; - roomActions.setMyRoomNick(room.roomId, nick); + roomActions.setMyRoomNick(mx, room.roomId, nick); }, }, [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', exe: async (payload) => { 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, description: 'Convert room to direct message', exe: async () => { - roomActions.convertToDm(room.roomId); + roomActions.convertToDm(mx, room.roomId); }, }, [Command.ConvertToRoom]: { name: Command.ConvertToRoom, description: 'Convert direct message to room', exe: async () => { - roomActions.convertToRoom(room.roomId); + roomActions.convertToRoom(mx, room.roomId); }, }, }), diff --git a/src/app/hooks/useCrossSigningStatus.js b/src/app/hooks/useCrossSigningStatus.js index 845c5462..990076e6 100644 --- a/src/app/hooks/useCrossSigningStatus.js +++ b/src/app/hooks/useCrossSigningStatus.js @@ -1,12 +1,12 @@ /* eslint-disable import/prefer-default-export */ import { useState, useEffect } from 'react'; -import initMatrix from '../../client/initMatrix'; import { hasCrossSigningAccountData } from '../../util/matrixUtil'; +import { useMatrixClient } from './useMatrixClient'; export function useCrossSigningStatus() { - const mx = initMatrix.matrixClient; - const [isCSEnabled, setIsCSEnabled] = useState(hasCrossSigningAccountData()); + const mx = useMatrixClient(); + const [isCSEnabled, setIsCSEnabled] = useState(hasCrossSigningAccountData(mx)); useEffect(() => { if (isCSEnabled) return undefined; @@ -20,6 +20,6 @@ export function useCrossSigningStatus() { return () => { mx.removeListener('accountData', handleAccountData); }; - }, [isCSEnabled === false]); + }, [mx, isCSEnabled]); return isCSEnabled; } diff --git a/src/app/hooks/useDeviceList.js b/src/app/hooks/useDeviceList.js index 2cce0fe5..7daaad1f 100644 --- a/src/app/hooks/useDeviceList.js +++ b/src/app/hooks/useDeviceList.js @@ -1,10 +1,9 @@ /* eslint-disable import/prefer-default-export */ import { useState, useEffect } from 'react'; - -import initMatrix from '../../client/initMatrix'; +import { useMatrixClient } from './useMatrixClient'; export function useDeviceList() { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const [deviceList, setDeviceList] = useState(null); useEffect(() => { @@ -27,6 +26,6 @@ export function useDeviceList() { mx.removeListener('crypto.devicesUpdated', handleDevicesUpdate); isMounted = false; }; - }, []); + }, [mx]); return deviceList; } diff --git a/src/app/hooks/useSyncState.ts b/src/app/hooks/useSyncState.ts index 4b2f4163..be8a6ef2 100644 --- a/src/app/hooks/useSyncState.ts +++ b/src/app/hooks/useSyncState.ts @@ -2,13 +2,13 @@ import { ClientEvent, ClientEventHandlerMap, MatrixClient } from 'matrix-js-sdk' import { useEffect } from 'react'; export const useSyncState = ( - mx: MatrixClient, + mx: MatrixClient | undefined, onChange: ClientEventHandlerMap[ClientEvent.Sync] ): void => { useEffect(() => { - mx.on(ClientEvent.Sync, onChange); + mx?.on(ClientEvent.Sync, onChange); return () => { - mx.removeListener(ClientEvent.Sync, onChange); + mx?.removeListener(ClientEvent.Sync, onChange); }; }, [mx, onChange]); }; diff --git a/src/app/molecules/global-notification/GlobalNotification.jsx b/src/app/molecules/global-notification/GlobalNotification.jsx index 865582ce..b115c9dc 100644 --- a/src/app/molecules/global-notification/GlobalNotification.jsx +++ b/src/app/molecules/global-notification/GlobalNotification.jsx @@ -1,6 +1,5 @@ import React from 'react'; -import initMatrix from '../../../client/initMatrix'; import { openReusableContextMenu } from '../../../client/action/navigation'; import { getEventCords } from '../../../util/common'; @@ -14,6 +13,7 @@ import NotificationSelector from './NotificationSelector'; import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; import { useAccountData } from '../../hooks/useAccountData'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; export const notifType = { ON: 'on', @@ -52,7 +52,7 @@ export function getTypeActions(type, highlightValue = false) { } function useGlobalNotif() { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const pushRules = useAccountData('m.push_rules')?.getContent(); const underride = pushRules?.global?.underride ?? []; const rulesToType = { diff --git a/src/app/molecules/global-notification/IgnoreUserList.jsx b/src/app/molecules/global-notification/IgnoreUserList.jsx index 87ee6272..1b44a041 100644 --- a/src/app/molecules/global-notification/IgnoreUserList.jsx +++ b/src/app/molecules/global-notification/IgnoreUserList.jsx @@ -1,7 +1,6 @@ import React from 'react'; import './IgnoreUserList.scss'; -import initMatrix from '../../../client/initMatrix'; import * as roomActions from '../../../client/action/room'; 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 { useAccountData } from '../../hooks/useAccountData'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function IgnoreUserList() { useAccountData('m.ignored_user_list'); - const ignoredUsers = initMatrix.matrixClient.getIgnoredUsers(); + const mx = useMatrixClient(); + const ignoredUsers = mx.getIgnoredUsers(); const handleSubmit = (evt) => { evt.preventDefault(); @@ -26,7 +27,7 @@ function IgnoreUserList() { const userIds = value.split(' ').filter((v) => v.match(/^@\S+:\S+$/)); if (userIds.length === 0) return; ignoreInput.value = ''; - roomActions.ignore(userIds); + roomActions.ignore(mx, userIds); }; return ( @@ -49,7 +50,7 @@ function IgnoreUserList() { key={uId} text={uId} iconColor={CrossIC} - onClick={() => roomActions.unignore([uId])} + onClick={() => roomActions.unignore(mx, [uId])} /> ))} diff --git a/src/app/molecules/global-notification/KeywordNotification.jsx b/src/app/molecules/global-notification/KeywordNotification.jsx index 8484d41d..7f7e4dea 100644 --- a/src/app/molecules/global-notification/KeywordNotification.jsx +++ b/src/app/molecules/global-notification/KeywordNotification.jsx @@ -1,7 +1,6 @@ import React from 'react'; import './KeywordNotification.scss'; -import initMatrix from '../../../client/initMatrix'; import { openReusableContextMenu } from '../../../client/action/navigation'; import { getEventCords } from '../../../util/common'; @@ -21,6 +20,7 @@ import { useAccountData } from '../../hooks/useAccountData'; import { notifType, typeToLabel, getActionType, getTypeActions, } from './GlobalNotification'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; const DISPLAY_NAME = '.m.rule.contains_display_name'; const ROOM_PING = '.m.rule.roomnotif'; @@ -28,7 +28,7 @@ const USERNAME = '.m.rule.contains_user_name'; const KEYWORD = 'keyword'; function useKeywordNotif() { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const pushRules = useAccountData('m.push_rules')?.getContent(); const override = pushRules?.global?.override ?? []; const content = pushRules?.global?.content ?? []; diff --git a/src/app/molecules/image-pack/ImagePack.jsx b/src/app/molecules/image-pack/ImagePack.jsx index f88886c1..51ffd0d3 100644 --- a/src/app/molecules/image-pack/ImagePack.jsx +++ b/src/app/molecules/image-pack/ImagePack.jsx @@ -4,7 +4,6 @@ import React, { import PropTypes from 'prop-types'; import './ImagePack.scss'; -import initMatrix from '../../../client/initMatrix'; import { openReusableDialog } from '../../../client/action/navigation'; import { suffixRename } from '../../../util/common'; @@ -19,6 +18,7 @@ import { confirmDialog } from '../confirm-dialog/ConfirmDialog'; import ImagePackProfile from './ImagePackProfile'; import ImagePackItem from './ImagePackItem'; import ImagePackUpload from './ImagePackUpload'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; const renameImagePackItem = (shortcode) => new Promise((resolve) => { let isCompleted = false; @@ -63,8 +63,7 @@ function getUsage(usage) { return 'both'; } -function isGlobalPack(roomId, stateKey) { - const mx = initMatrix.matrixClient; +function isGlobalPack(mx, roomId, stateKey) { const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent(); if (typeof globalContent !== 'object') return false; @@ -75,13 +74,13 @@ function isGlobalPack(roomId, stateKey) { } function useRoomImagePack(roomId, stateKey) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); - const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey); - const pack = useMemo(() => ( - ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent()) - ), [room, stateKey]); + const pack = useMemo(() => { + const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey); + return ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent()) + }, [room, stateKey]); const sendPackContent = (content) => { mx.sendStateEvent(roomId, 'im.ponies.room_emotes', content, stateKey); @@ -94,14 +93,14 @@ function useRoomImagePack(roomId, stateKey) { } function useUserImagePack() { - const mx = initMatrix.matrixClient; - const packEvent = mx.getAccountData('im.ponies.user_emotes'); - const pack = useMemo(() => ( - ImagePackBuilder.parsePack(mx.getUserId(), packEvent?.getContent() ?? { + const mx = useMatrixClient(); + const pack = useMemo(() => { + const packEvent = mx.getAccountData('im.ponies.user_emotes'); + return ImagePackBuilder.parsePack(mx.getUserId(), packEvent?.getContent() ?? { pack: { display_name: 'Personal' }, images: {}, }) - ), []); + }, [mx]); const sendPackContent = (content) => { mx.setAccountData('im.ponies.user_emotes', content); @@ -223,10 +222,10 @@ function removeGlobalImagePack(mx, roomId, stateKey) { } function ImagePack({ roomId, stateKey, handlePackDelete }) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); 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); @@ -331,7 +330,7 @@ ImagePack.propTypes = { }; function ImagePackUser() { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const [viewMore, setViewMore] = useState(false); const { pack, sendPackContent } = useUserImagePack(); @@ -397,7 +396,7 @@ function ImagePackUser() { function useGlobalImagePack() { const [, forceUpdate] = useReducer((count) => count + 1, 0); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const roomIdToStateKeys = new Map(); const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? { rooms: {} }; @@ -419,13 +418,13 @@ function useGlobalImagePack() { return () => { mx.removeListener('accountData', handleEvent); }; - }, []); + }, [mx]); return roomIdToStateKeys; } function ImagePackGlobal() { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const roomIdToStateKeys = useGlobalImagePack(); const handleChange = (roomId, stateKey) => { diff --git a/src/app/molecules/image-pack/ImagePackUpload.jsx b/src/app/molecules/image-pack/ImagePackUpload.jsx index 6295de1c..cdf2e13b 100644 --- a/src/app/molecules/image-pack/ImagePackUpload.jsx +++ b/src/app/molecules/image-pack/ImagePackUpload.jsx @@ -2,7 +2,6 @@ import React, { useState, useRef } from 'react'; import PropTypes from 'prop-types'; import './ImagePackUpload.scss'; -import initMatrix from '../../../client/initMatrix'; import { scaleDownImage } from '../../../util/common'; import Text from '../../atoms/text/Text'; @@ -10,9 +9,10 @@ import Button from '../../atoms/button/Button'; import Input from '../../atoms/input/Input'; import IconButton from '../../atoms/button/IconButton'; import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function ImagePackUpload({ onUpload }) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const inputRef = useRef(null); const shortcodeRef = useRef(null); const [imgFile, setImgFile] = useState(null); diff --git a/src/app/molecules/image-upload/ImageUpload.jsx b/src/app/molecules/image-upload/ImageUpload.jsx index 53fc7e16..5213381f 100644 --- a/src/app/molecules/image-upload/ImageUpload.jsx +++ b/src/app/molecules/image-upload/ImageUpload.jsx @@ -2,7 +2,6 @@ import React, { useState, useRef } from 'react'; import PropTypes from 'prop-types'; import './ImageUpload.scss'; -import initMatrix from '../../../client/initMatrix'; import Text from '../../atoms/text/Text'; import Avatar from '../../atoms/avatar/Avatar'; @@ -10,6 +9,7 @@ import Spinner from '../../atoms/spinner/Spinner'; import RawIcon from '../../atoms/system-icons/RawIcon'; import PlusIC from '../../../../public/res/ic/outlined/plus.svg'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function ImageUpload({ text, bgColor, imageSrc, onUpload, onRequestRemove, @@ -17,12 +17,13 @@ function ImageUpload({ }) { const [uploadPromise, setUploadPromise] = useState(null); const uploadImageRef = useRef(null); + const mx = useMatrixClient(); async function uploadImage(e) { const file = e.target.files.item(0); if (file === null) return; try { - const uPromise = initMatrix.matrixClient.uploadContent(file); + const uPromise = mx.uploadContent(file); setUploadPromise(uPromise); const res = await uPromise; @@ -35,7 +36,7 @@ function ImageUpload({ } function cancelUpload() { - initMatrix.matrixClient.cancelUpload(uploadPromise); + mx.cancelUpload(uploadPromise); setUploadPromise(null); uploadImageRef.current.value = null; } diff --git a/src/app/molecules/import-export-e2e-room-keys/ExportE2ERoomKeys.jsx b/src/app/molecules/import-export-e2e-room-keys/ExportE2ERoomKeys.jsx index b7738a6a..1ce7e78a 100644 --- a/src/app/molecules/import-export-e2e-room-keys/ExportE2ERoomKeys.jsx +++ b/src/app/molecules/import-export-e2e-room-keys/ExportE2ERoomKeys.jsx @@ -3,7 +3,6 @@ import './ExportE2ERoomKeys.scss'; import FileSaver from 'file-saver'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import { encryptMegolmKeyFile } from '../../../util/cryptE2ERoomKeys'; @@ -13,8 +12,10 @@ import Input from '../../atoms/input/Input'; import Spinner from '../../atoms/spinner/Spinner'; import { useStore } from '../../hooks/useStore'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function ExportE2ERoomKeys() { + const mx = useMatrixClient(); const isMountStore = useStore(); const [status, setStatus] = useState({ isOngoing: false, @@ -40,7 +41,7 @@ function ExportE2ERoomKeys() { type: cons.status.IN_FLIGHT, }); try { - const keys = await initMatrix.matrixClient.exportRoomKeys(); + const keys = await mx.exportRoomKeys(); if (isMountStore.getItem()) { setStatus({ isOngoing: true, diff --git a/src/app/molecules/import-export-e2e-room-keys/ImportE2ERoomKeys.jsx b/src/app/molecules/import-export-e2e-room-keys/ImportE2ERoomKeys.jsx index b5a44b0e..9f0ab793 100644 --- a/src/app/molecules/import-export-e2e-room-keys/ImportE2ERoomKeys.jsx +++ b/src/app/molecules/import-export-e2e-room-keys/ImportE2ERoomKeys.jsx @@ -1,7 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import './ImportE2ERoomKeys.scss'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; 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 { useStore } from '../../hooks/useStore'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function ImportE2ERoomKeys() { + const mx = useMatrixClient(); const isMountStore = useStore(); const [keyFile, setKeyFile] = useState(null); const [status, setStatus] = useState({ @@ -45,7 +46,7 @@ function ImportE2ERoomKeys() { type: cons.status.IN_FLIGHT, }); } - await initMatrix.matrixClient.importRoomKeys(JSON.parse(keys)); + await mx.importRoomKeys(JSON.parse(keys)); if (isMountStore.getItem()) { setStatus({ isOngoing: false, diff --git a/src/app/molecules/room-aliases/RoomAliases.jsx b/src/app/molecules/room-aliases/RoomAliases.jsx index d573f7d6..876d063f 100644 --- a/src/app/molecules/room-aliases/RoomAliases.jsx +++ b/src/app/molecules/room-aliases/RoomAliases.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import './RoomAliases.scss'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import { Debounce } from '../../../util/common'; import { isRoomAliasAvailable } from '../../../util/matrixUtil'; @@ -16,8 +15,10 @@ import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; import SettingTile from '../setting-tile/SettingTile'; import { useStore } from '../../hooks/useStore'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function useValidate(hsString) { + const mx = useMatrixClient(); const [debounce] = useState(new Debounce()); const [validate, setValidate] = useState({ alias: null, status: cons.status.PRE_FLIGHT }); @@ -62,7 +63,7 @@ function useValidate(hsString) { msg: `validating ${alias}...`, }); - const isValid = await isRoomAliasAvailable(alias); + const isValid = await isRoomAliasAvailable(mx, alias); setValidate(() => { if (e.target.value !== value) { return { alias: null, status: cons.status.PRE_FLIGHT }; @@ -79,8 +80,7 @@ function useValidate(hsString) { return [validate, setValidateToDefault, handleAliasChange]; } -function getAliases(roomId) { - const mx = initMatrix.matrixClient; +function getAliases(mx, roomId) { const room = mx.getRoom(roomId); const main = room.getCanonicalAlias(); @@ -95,7 +95,7 @@ function getAliases(roomId) { } function RoomAliases({ roomId }) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); const userId = mx.getUserId(); const hsString = userId.slice(userId.indexOf(':') + 1); @@ -103,7 +103,7 @@ function RoomAliases({ roomId }) { const isMountedStore = useStore(); const [isPublic, setIsPublic] = 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 [deleteAlias, setDeleteAlias] = useState(null); const [validate, setValidateToDefault, handleAliasChange] = useValidate(hsString); @@ -140,7 +140,7 @@ function RoomAliases({ roomId }) { return () => { isUnmounted = true; }; - }, [roomId]); + }, [mx, roomId]); const toggleDirectoryVisibility = () => { mx.setRoomDirectoryVisibility(roomId, isPublic ? 'private' : 'public'); diff --git a/src/app/molecules/room-emojis/RoomEmojis.jsx b/src/app/molecules/room-emojis/RoomEmojis.jsx index 94ae6107..da09ea86 100644 --- a/src/app/molecules/room-emojis/RoomEmojis.jsx +++ b/src/app/molecules/room-emojis/RoomEmojis.jsx @@ -2,7 +2,6 @@ import React, { useReducer, useEffect } from 'react'; import PropTypes from 'prop-types'; import './RoomEmojis.scss'; -import initMatrix from '../../../client/initMatrix'; import { suffixRename } from '../../../util/common'; import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; @@ -10,9 +9,10 @@ import Text from '../../atoms/text/Text'; import Input from '../../atoms/input/Input'; import Button from '../../atoms/button/Button'; import ImagePack from '../image-pack/ImagePack'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function useRoomPacks(room) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const [, forceUpdate] = useReducer((count) => count + 1, 0); const packEvents = room.currentState.getStateEvents('im.ponies.room_emotes'); @@ -75,7 +75,7 @@ function useRoomPacks(room) { } function RoomEmojis({ roomId }) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); const { usablePacks, createPack, deletePack } = useRoomPacks(room); diff --git a/src/app/molecules/room-encryption/RoomEncryption.jsx b/src/app/molecules/room-encryption/RoomEncryption.jsx index 1657f363..47250f47 100644 --- a/src/app/molecules/room-encryption/RoomEncryption.jsx +++ b/src/app/molecules/room-encryption/RoomEncryption.jsx @@ -2,16 +2,16 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; import './RoomEncryption.scss'; -import initMatrix from '../../../client/initMatrix'; import Text from '../../atoms/text/Text'; import Toggle from '../../atoms/button/Toggle'; import SettingTile from '../setting-tile/SettingTile'; import { confirmDialog } from '../confirm-dialog/ConfirmDialog'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function RoomEncryption({ roomId }) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); const encryptionEvents = room.currentState.getStateEvents('m.room.encryption'); const [isEncrypted, setIsEncrypted] = useState(encryptionEvents.length > 0); diff --git a/src/app/molecules/room-history-visibility/RoomHistoryVisibility.jsx b/src/app/molecules/room-history-visibility/RoomHistoryVisibility.jsx index d9dd9540..c897cb35 100644 --- a/src/app/molecules/room-history-visibility/RoomHistoryVisibility.jsx +++ b/src/app/molecules/room-history-visibility/RoomHistoryVisibility.jsx @@ -2,11 +2,11 @@ import React, { useState, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; import './RoomHistoryVisibility.scss'; -import initMatrix from '../../../client/initMatrix'; import Text from '../../atoms/text/Text'; import RadioButton from '../../atoms/button/RadioButton'; import { MenuItem } from '../../atoms/context-menu/ContextMenu'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; const visibility = { WORLD_READABLE: 'world_readable', @@ -33,38 +33,33 @@ const items = [{ 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) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); const [activeType, setActiveType] = useState(room.getHistoryVisibility()); useEffect(() => { setActiveType(room.getHistoryVisibility()); - }, [roomId]); + }, [room]); const setVisibility = useCallback((item) => { if (item.type === activeType.type) return; setActiveType(item.type); - setHistoryVisibility(roomId, item.type); - }, [activeType, roomId]); + mx.sendStateEvent( + roomId, 'm.room.history_visibility', + { + history_visibility: item.type, + }, + ); + }, [mx, activeType, roomId]); return [activeType, setVisibility]; } function RoomHistoryVisibility({ roomId }) { const [activeType, setVisibility] = useVisibility(roomId); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const userId = mx.getUserId(); const room = mx.getRoom(roomId); const { currentState } = room; diff --git a/src/app/molecules/room-members/RoomMembers.jsx b/src/app/molecules/room-members/RoomMembers.jsx index f931f9dd..a3928fb7 100644 --- a/src/app/molecules/room-members/RoomMembers.jsx +++ b/src/app/molecules/room-members/RoomMembers.jsx @@ -4,7 +4,6 @@ import React, { import PropTypes from 'prop-types'; import './RoomMembers.scss'; -import initMatrix from '../../../client/initMatrix'; import colorMXID from '../../../util/colorMXID'; import { openProfileViewer } from '../../../client/action/navigation'; import { getUsernameOfRoomMember, getPowerLabel } from '../../../util/matrixUtil'; @@ -17,11 +16,11 @@ import Input from '../../atoms/input/Input'; import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; import SegmentedControls from '../../atoms/segmented-controls/SegmentedControls'; import PeopleSelector from '../people-selector/PeopleSelector'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; const PER_PAGE_MEMBER = 50; -function normalizeMembers(members) { - const mx = initMatrix.matrixClient; +function normalizeMembers(mx, members) { return members.map((member) => ({ userId: member.userId, name: getUsernameOfRoomMember(member), @@ -33,7 +32,7 @@ function normalizeMembers(members) { } function useMemberOfMembership(roomId, membership) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); const [members, setMembers] = useState([]); @@ -45,6 +44,7 @@ function useMemberOfMembership(roomId, membership) { if (isLoadingMembers) return; if (event && event?.getRoomId() !== roomId) return; const memberOfMembership = normalizeMembers( + mx, room.getMembersWithMembership(membership) .sort(memberByAtoZ).sort(memberByPowerLevel), ); @@ -66,7 +66,7 @@ function useMemberOfMembership(roomId, membership) { mx.removeListener('RoomMember.membership', updateMemberList); mx.removeListener('RoomMember.powerLevel', updateMemberList); }; - }, [membership]); + }, [mx, membership]); return [members]; } diff --git a/src/app/molecules/room-notification/RoomNotification.jsx b/src/app/molecules/room-notification/RoomNotification.jsx index 821ea508..db970708 100644 --- a/src/app/molecules/room-notification/RoomNotification.jsx +++ b/src/app/molecules/room-notification/RoomNotification.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; import './RoomNotification.scss'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; 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 BellOffIC from '../../../../public/res/ic/outlined/bell-off.svg'; import { getNotificationType } from '../../utils/room'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; const items = [ { @@ -38,8 +38,7 @@ const items = [ }, ]; -function setRoomNotifType(roomId, newType) { - const mx = initMatrix.matrixClient; +function setRoomNotifType(mx, roomId, newType) { let roomPushRule; try { roomPushRule = mx.getRoomPushRule('global', roomId); @@ -108,7 +107,7 @@ function setRoomNotifType(roomId, newType) { } function useNotifications(roomId) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const [activeType, setActiveType] = useState(getNotificationType(mx, roomId)); useEffect(() => { setActiveType(getNotificationType(mx, roomId)); @@ -118,9 +117,9 @@ function useNotifications(roomId) { (item) => { if (item.type === activeType.type) return; setActiveType(item.type); - setRoomNotifType(roomId, item.type); + setRoomNotifType(mx, roomId, item.type); }, - [activeType, roomId] + [mx, activeType, roomId] ); return [activeType, setNotification]; } diff --git a/src/app/molecules/room-permissions/RoomPermissions.jsx b/src/app/molecules/room-permissions/RoomPermissions.jsx index da8720cd..f8cd048f 100644 --- a/src/app/molecules/room-permissions/RoomPermissions.jsx +++ b/src/app/molecules/room-permissions/RoomPermissions.jsx @@ -2,7 +2,6 @@ import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import './RoomPermissions.scss'; -import initMatrix from '../../../client/initMatrix'; import { getPowerLabel } from '../../../util/matrixUtil'; import { openReusableContextMenu } from '../../../client/action/navigation'; 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 { useForceUpdate } from '../../hooks/useForceUpdate'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; const permissionsInfo = { users_default: { @@ -157,7 +157,7 @@ const spacePermsGroups = { function useRoomStateUpdate(roomId) { const [, forceUpdate] = useForceUpdate(); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); useEffect(() => { const handleStateEvent = (event) => { @@ -169,12 +169,12 @@ function useRoomStateUpdate(roomId) { return () => { mx.removeListener('RoomState.events', handleStateEvent); }; - }, [roomId]); + }, [mx, roomId]); } function RoomPermissions({ roomId }) { useRoomStateUpdate(roomId); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); const pLEvent = room.currentState.getStateEvents('m.room.power_levels')[0]; const permissions = pLEvent.getContent(); diff --git a/src/app/molecules/room-profile/RoomProfile.jsx b/src/app/molecules/room-profile/RoomProfile.jsx index 15273ebf..da8ce755 100644 --- a/src/app/molecules/room-profile/RoomProfile.jsx +++ b/src/app/molecules/room-profile/RoomProfile.jsx @@ -4,7 +4,6 @@ import { useAtomValue } from 'jotai'; import Linkify from 'linkify-react'; import './RoomProfile.scss'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import colorMXID from '../../../util/colorMXID'; @@ -22,6 +21,7 @@ import { useForceUpdate } from '../../hooks/useForceUpdate'; import { confirmDialog } from '../confirm-dialog/ConfirmDialog'; import { mDirectAtom } from '../../state/mDirectList'; import { LINKIFY_OPTS } from '../../plugins/react-custom-html-parser'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function RoomProfile({ roomId }) { const isMountStore = useStore(); @@ -32,7 +32,7 @@ function RoomProfile({ roomId }) { type: cons.status.PRE_FLIGHT, }); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const mDirects = useAtomValue(mDirectAtom); const isDM = mDirects.has(roomId); let avatarSrc = mx.getRoom(roomId).getAvatarUrl(mx.baseUrl, 36, 36, 'crop'); @@ -67,7 +67,7 @@ function RoomProfile({ roomId }) { }); setIsEditing(false); }; - }, [roomId]); + }, [mx, roomId]); const handleOnSubmit = async (e) => { e.preventDefault(); diff --git a/src/app/molecules/room-visibility/RoomVisibility.jsx b/src/app/molecules/room-visibility/RoomVisibility.jsx index a5e8e2d0..6579513a 100644 --- a/src/app/molecules/room-visibility/RoomVisibility.jsx +++ b/src/app/molecules/room-visibility/RoomVisibility.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; import './RoomVisibility.scss'; -import initMatrix from '../../../client/initMatrix'; import Text from '../../atoms/text/Text'; 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 SpaceLockIC from '../../../../public/res/ic/outlined/space-lock.svg'; import SpaceGlobeIC from '../../../../public/res/ic/outlined/space-globe.svg'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; const visibility = { INVITE: 'invite', @@ -21,8 +21,7 @@ const visibility = { PUBLIC: 'public', }; -function setJoinRule(roomId, type) { - const mx = initMatrix.matrixClient; +function setJoinRule(mx, roomId, type) { let allow; if (type === visibility.RESTRICTED) { const { currentState } = mx.getRoom(roomId); @@ -46,26 +45,26 @@ function setJoinRule(roomId, type) { } function useVisibility(roomId) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); const [activeType, setActiveType] = useState(room.getJoinRule()); useEffect(() => { setActiveType(room.getJoinRule()); - }, [roomId]); + }, [room]); const setNotification = useCallback((item) => { if (item.type === activeType.type) return; setActiveType(item.type); - setJoinRule(roomId, item.type); - }, [activeType, roomId]); + setJoinRule(mx, roomId, item.type); + }, [mx, activeType, roomId]); return [activeType, setNotification]; } function RoomVisibility({ roomId }) { const [activeType, setVisibility] = useVisibility(roomId); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); const isSpace = room.isSpaceRoom(); const { currentState } = room; diff --git a/src/app/molecules/space-add-existing/SpaceAddExisting.jsx b/src/app/molecules/space-add-existing/SpaceAddExisting.jsx index 2eb94a5c..ff338f3f 100644 --- a/src/app/molecules/space-add-existing/SpaceAddExisting.jsx +++ b/src/app/molecules/space-add-existing/SpaceAddExisting.jsx @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import { useAtomValue } from 'jotai'; import './SpaceAddExisting.scss'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; 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 { allRoomsAtom } from '../../state/room-list/roomList'; import { mDirectAtom } from '../../state/mDirectList'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function SpaceAddExistingContent({ roomId, spaces: onlySpaces }) { const mountStore = useStore(roomId); @@ -35,7 +35,7 @@ function SpaceAddExistingContent({ roomId, spaces: onlySpaces }) { const [allRoomIds, setAllRoomIds] = useState([]); const [selected, setSelected] = useState([]); const [searchIds, setSearchIds] = useState(null); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const roomIdToParents = useAtomValue(roomToParentsAtom); const mDirects = useAtomValue(mDirectAtom); const spaces = useSpaces(mx, allRoomsAtom); @@ -48,7 +48,7 @@ function SpaceAddExistingContent({ roomId, spaces: onlySpaces }) { (rId) => rId !== roomId && !roomIdToParents.get(rId)?.has(roomId) ); setAllRoomIds(allIds); - }, [roomId, onlySpaces]); + }, [spaces, rooms, directs, roomIdToParents, roomId, onlySpaces]); const toggleSelection = (rId) => { if (process !== null) return; @@ -215,7 +215,7 @@ function useVisibilityToggle() { function SpaceAddExisting() { const [data, requestClose] = useVisibilityToggle(); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(data?.roomId); return ( diff --git a/src/app/organisms/create-room/CreateRoom.jsx b/src/app/organisms/create-room/CreateRoom.jsx index ff00cca1..04b2faeb 100644 --- a/src/app/organisms/create-room/CreateRoom.jsx +++ b/src/app/organisms/create-room/CreateRoom.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import './CreateRoom.scss'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/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 CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import { useRoomNavigate } from '../../hooks/useRoomNavigate'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function CreateRoomContent({ isSpace, parentId, onRequestClose }) { const [joinRule, setJoinRule] = useState(parentId ? 'restricted' : 'invite'); @@ -46,7 +46,7 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) { const addressRef = useRef(null); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const userHs = getIdServer(mx.getUserId()); const handleSubmit = async (evt) => { @@ -69,7 +69,7 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) { const powerLevel = roleIndex === 1 ? 101 : undefined; try { - const data = await roomActions.createRoom({ + const data = await roomActions.createRoom(mx, { name, topic, joinRule, @@ -113,7 +113,7 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) { if (roomAlias === '') return; const roomAddress = `#${roomAlias}:${userHs}`; - if (await isRoomAliasAvailable(roomAddress)) { + if (await isRoomAliasAvailable(mx, roomAddress)) { setIsValidAddress(true); } else { setIsValidAddress(false); @@ -278,7 +278,7 @@ function useWindowToggle() { function CreateRoom() { const [create, onRequestClose] = useWindowToggle(); const { isSpace, parentId } = create ?? {}; - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(parentId); return ( diff --git a/src/app/organisms/emoji-verification/EmojiVerification.jsx b/src/app/organisms/emoji-verification/EmojiVerification.jsx index 1b543c05..21be477f 100644 --- a/src/app/organisms/emoji-verification/EmojiVerification.jsx +++ b/src/app/organisms/emoji-verification/EmojiVerification.jsx @@ -3,7 +3,6 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import './EmojiVerification.scss'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; 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 { useStore } from '../../hooks/useStore'; import { accessSecretStorage } from '../settings/SecretStorageAccess'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function EmojiVerificationContent({ data, requestClose }) { const [sas, setSas] = useState(null); const [process, setProcess] = useState(false); const { request, targetDevice } = data; - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const mountStore = useStore(); const beginStore = useStore(); const beginVerification = async () => { if ( - isCrossVerified(mx.deviceId) && + isCrossVerified(mx, mx.deviceId) && (mx.getCrossSigningId() === null || (await mx.crypto.crossSigningInfo.isStoredInKeyCache('self_signing')) === false) ) { - if (!hasPrivateKey(getDefaultSSKey())) { - const keyData = await accessSecretStorage('Emoji verification'); + if (!hasPrivateKey(getDefaultSSKey(mx))) { + const keyData = await accessSecretStorage(mx, 'Emoji verification'); if (!keyData) { request.cancel(); return; @@ -158,7 +158,7 @@ EmojiVerificationContent.propTypes = { function useVisibilityToggle() { const [data, setData] = useState(null); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); useEffect(() => { const handleOpen = (request, targetDevice) => { @@ -170,7 +170,7 @@ function useVisibilityToggle() { navigation.removeListener(cons.events.navigation.EMOJI_VERIFICATION_OPENED, handleOpen); mx.removeListener('crypto.verification.request', handleOpen); }; - }, []); + }, [mx]); const requestClose = () => setData(null); diff --git a/src/app/organisms/invite-user/InviteUser.jsx b/src/app/organisms/invite-user/InviteUser.jsx index 10f55f9f..284be72e 100644 --- a/src/app/organisms/invite-user/InviteUser.jsx +++ b/src/app/organisms/invite-user/InviteUser.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import './InviteUser.scss'; -import initMatrix from '../../../client/initMatrix'; import * as roomActions from '../../../client/action/room'; 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 { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { getDMRoomFor } from '../../utils/matrix'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) { const [isSearching, updateIsSearching] = useState(false); @@ -34,7 +34,7 @@ function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) { const usernameRef = useRef(null); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const { navigateRoom } = useRoomNavigate(); function getMapCopy(myMap) { @@ -118,7 +118,7 @@ function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) { procUserError.delete(userId); 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); updateRoomIdToUserId(getMapCopy(roomIdToUserId)); onDMCreated(result.room_id); @@ -137,7 +137,7 @@ function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) { procUserError.delete(userId); updateUserProcError(getMapCopy(procUserError)); - await roomActions.invite(roomId, userId); + await mx.invite(roomId, userId); invitedUserIds.add(userId); updateInvitedUserIds(new Set(Array.from(invitedUserIds))); diff --git a/src/app/organisms/join-alias/JoinAlias.jsx b/src/app/organisms/join-alias/JoinAlias.jsx index 9fa5542d..99cf6e6e 100644 --- a/src/app/organisms/join-alias/JoinAlias.jsx +++ b/src/app/organisms/join-alias/JoinAlias.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import './JoinAlias.scss'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; 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 { useRoomNavigate } from '../../hooks/useRoomNavigate'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; const ALIAS_OR_ID_REG = /^[#|!].+:.+\..+$/; @@ -25,7 +25,7 @@ function JoinAliasContent({ term, requestClose }) { const [process, setProcess] = useState(false); const [error, setError] = useState(undefined); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const mountStore = useStore(); const { navigateRoom } = useRoomNavigate(); @@ -63,7 +63,7 @@ function JoinAliasContent({ term, requestClose }) { } } try { - const roomId = await join(alias, false, via); + const roomId = await join(mx, alias, false, via); if (!mountStore.getItem()) return; openRoom(roomId); } catch { diff --git a/src/app/organisms/profile-editor/ProfileEditor.jsx b/src/app/organisms/profile-editor/ProfileEditor.jsx index c21c82fa..95709355 100644 --- a/src/app/organisms/profile-editor/ProfileEditor.jsx +++ b/src/app/organisms/profile-editor/ProfileEditor.jsx @@ -1,7 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; -import initMatrix from '../../../client/initMatrix'; import colorMXID from '../../../util/colorMXID'; 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 './ProfileEditor.scss'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function ProfileEditor({ userId }) { const [isEditing, setIsEditing] = useState(false); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const user = mx.getUser(mx.getUserId()); const displayNameRef = useRef(null); @@ -37,7 +37,7 @@ function ProfileEditor({ userId }) { return () => { isMounted = false; }; - }, [userId]); + }, [mx, userId]); const handleAvatarUpload = async (url) => { if (url === null) { diff --git a/src/app/organisms/profile-viewer/ProfileViewer.jsx b/src/app/organisms/profile-viewer/ProfileViewer.jsx index b19c9c86..b4ab7473 100644 --- a/src/app/organisms/profile-viewer/ProfileViewer.jsx +++ b/src/app/organisms/profile-viewer/ProfileViewer.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import './ProfileViewer.scss'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; import { openReusableContextMenu } from '../../../client/action/navigation'; @@ -36,9 +35,10 @@ import { useForceUpdate } from '../../hooks/useForceUpdate'; import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; import { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { getDMRoomFor } from '../../utils/matrix'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function ModerationTools({ roomId, userId }) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); const roomMember = room.getMember(userId); @@ -56,13 +56,13 @@ function ModerationTools({ roomId, userId }) { const handleKick = (e) => { e.preventDefault(); 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) => { e.preventDefault(); const banReason = e.target.elements['ban-reason']?.value.trim(); - roomActions.ban(roomId, userId, banReason !== '' ? banReason : undefined); + mx.ban(roomId, userId, banReason !== '' ? banReason : undefined); }; return ( @@ -90,7 +90,7 @@ ModerationTools.propTypes = { function SessionInfo({ userId }) { const [devices, setDevices] = useState(null); const [isVisible, setIsVisible] = useState(false); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); useEffect(() => { let isUnmounted = false; @@ -111,7 +111,7 @@ function SessionInfo({ userId }) { return () => { isUnmounted = true; }; - }, [userId]); + }, [mx, userId]); function renderSessionChips() { if (!isVisible) return null; @@ -139,7 +139,7 @@ function SessionInfo({ userId }) { > {`View ${ devices?.length > 0 - ? `${devices.length} ${devices.length == 1 ? 'session' : 'sessions'}` + ? `${devices.length} ${devices.length === 1 ? 'session' : 'sessions'}` : 'sessions' }`} @@ -155,10 +155,10 @@ SessionInfo.propTypes = { function ProfileFooter({ roomId, userId, onRequestClose }) { const [isCreatingDM, setIsCreatingDM] = 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 mx = initMatrix.matrixClient; const { navigateRoom } = useRoomNavigate(); const room = mx.getRoom(roomId); const member = room.getMember(userId); @@ -182,10 +182,10 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { }; useEffect(() => { - setIsUserIgnored(initMatrix.matrixClient.isUserIgnored(userId)); + setIsUserIgnored(mx.isUserIgnored(userId)); setIsIgnoring(false); setIsInviting(false); - }, [userId]); + }, [mx, userId]); const openDM = async () => { // Check and open if user already have a DM with userId. @@ -199,7 +199,7 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { // Create new DM try { 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); } catch { if (isMountedRef.current === false) return; @@ -213,9 +213,9 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { try { setIsIgnoring(true); if (isIgnored) { - await roomActions.unignore([userId]); + await roomActions.unignore(mx, [userId]); } else { - await roomActions.ignore([userId]); + await roomActions.ignore(mx, [userId]); } if (isMountedRef.current === false) return; @@ -230,9 +230,9 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { try { setIsInviting(true); let isInviteSent = false; - if (isInvited) await roomActions.kick(roomId, userId); + if (isInvited) await mx.kick(roomId, userId); else { - await roomActions.invite(roomId, userId); + await mx.invite(roomId, userId); isInviteSent = true; } if (isMountedRef.current === false) return; @@ -249,7 +249,7 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { {isCreatingDM ? 'Creating room...' : 'Message'} {isBanned && canIKick && ( - )} @@ -306,7 +306,7 @@ function useToggleDialog() { } function useRerenderOnProfileChange(roomId, userId) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const [, forceUpdate] = useForceUpdate(); useEffect(() => { const handleProfileChange = (mEvent, member) => { @@ -323,19 +323,19 @@ function useRerenderOnProfileChange(roomId, userId) { mx.removeListener('RoomMember.powerLevel', handleProfileChange); mx.removeListener('RoomMember.membership', handleProfileChange); }; - }, [roomId, userId]); + }, [mx, roomId, userId]); } function ProfileViewer() { const [isOpen, roomId, userId, closeDialog, handleAfterClose] = useToggleDialog(); useRerenderOnProfileChange(roomId, userId); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); const renderProfile = () => { 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 avatarUrl = avatarMxc && avatarMxc !== 'null' ? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop') : null; @@ -364,9 +364,9 @@ function ProfileViewer() { 'caution' ); if (!isConfirmed) return; - roomActions.setPowerLevel(roomId, userId, newPowerLevel); + roomActions.setPowerLevel(mx, roomId, userId, newPowerLevel); } else { - roomActions.setPowerLevel(roomId, userId, newPowerLevel); + roomActions.setPowerLevel(mx, roomId, userId, newPowerLevel); } }; diff --git a/src/app/organisms/room/RoomSettings.jsx b/src/app/organisms/room/RoomSettings.jsx index 2b8f28e6..69f977b2 100644 --- a/src/app/organisms/room/RoomSettings.jsx +++ b/src/app/organisms/room/RoomSettings.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import './RoomSettings.scss'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; 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 PopupWindow from '../../molecules/popup-window/PopupWindow'; import IconButton from '../../atoms/button/IconButton'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; const tabText = { GENERAL: 'General', @@ -68,7 +68,7 @@ const tabItems = [ ]; function GeneralSettings({ roomId }) { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); return ( @@ -155,7 +155,8 @@ function RoomSettings() { const [window, requestClose] = useWindowToggle(setSelectedTab); const isOpen = window !== null; const roomId = window?.roomId; - const room = initMatrix.matrixClient.getRoom(roomId); + const mx = useMatrixClient(); + const room = mx.getRoom(roomId); const handleTabChange = (tabItem) => { setSelectedTab(tabItem); diff --git a/src/app/organisms/search/Search.jsx b/src/app/organisms/search/Search.jsx index 0990a03f..ebdac396 100644 --- a/src/app/organisms/search/Search.jsx +++ b/src/app/organisms/search/Search.jsx @@ -2,12 +2,10 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; import { useAtomValue } from 'jotai'; import './Search.scss'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; import AsyncSearch from '../../../util/AsyncSearch'; import { joinRuleToIconSrc } from '../../../util/matrixUtil'; -import { roomIdByActivity } from '../../../util/sort'; import Text from '../../atoms/text/Text'; import RawIcon from '../../atoms/system-icons/RawIcon'; @@ -27,6 +25,8 @@ import { allRoomsAtom } from '../../state/room-list/roomList'; import { mDirectAtom } from '../../state/mDirectList'; import { useKeyDown } from '../../hooks/useKeyDown'; import { openSearch } from '../../../client/action/navigation'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; +import { factoryRoomIdByActivity } from '../../utils/sort'; function useVisiblityToggle(setResult) { const [isOpen, setIsOpen] = useState(false); @@ -77,9 +77,7 @@ function useVisiblityToggle(setResult) { return [isOpen, requestClose]; } -function mapRoomIds(roomIds, directs, roomIdToParents) { - const mx = initMatrix.matrixClient; - +function mapRoomIds(mx, roomIds, directs, roomIdToParents) { return roomIds.map((roomId) => { const room = mx.getRoom(roomId); const parentSet = roomIdToParents.get(roomId); @@ -107,7 +105,7 @@ function Search() { const [asyncSearch] = useState(new AsyncSearch()); const [isOpen, requestClose] = useVisiblityToggle(setResult); const searchRef = useRef(null); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const { navigateRoom, navigateSpace } = useRoomNavigate(); const mDirects = useAtomValue(mDirectAtom); const spaces = useSpaces(mx, allRoomsAtom); @@ -141,8 +139,8 @@ function Search() { ids = [...rooms].concat([...directs], [...spaces]); } - ids.sort(roomIdByActivity); - const mappedIds = mapRoomIds(ids, directs, roomToParents); + ids.sort(factoryRoomIdByActivity(mx)); + const mappedIds = mapRoomIds(mx, ids, directs, roomToParents); asyncSearch.setup(mappedIds, { keys: 'name', isContain: true, limit: 20 }); if (prefix) handleSearchResults(mappedIds, prefix); else asyncSearch.search(term); @@ -150,7 +148,7 @@ function Search() { const loadRecentRooms = () => { const recentRooms = []; - handleSearchResults(mapRoomIds(recentRooms, directs, roomToParents).reverse()); + handleSearchResults(mapRoomIds(mx, recentRooms, directs, roomToParents).reverse()); }; const handleAfterOpen = () => { diff --git a/src/app/organisms/settings/AuthRequest.jsx b/src/app/organisms/settings/AuthRequest.jsx index ca07c2a2..f897f83c 100644 --- a/src/app/organisms/settings/AuthRequest.jsx +++ b/src/app/organisms/settings/AuthRequest.jsx @@ -2,7 +2,6 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; import './AuthRequest.scss'; -import initMatrix from '../../../client/initMatrix'; import { openReusableDialog } from '../../../client/action/navigation'; import Text from '../../atoms/text/Text'; @@ -11,6 +10,7 @@ import Input from '../../atoms/input/Input'; import Spinner from '../../atoms/spinner/Spinner'; import { useStore } from '../../hooks/useStore'; +import { getSecret } from '../../../client/state/auth'; let lastUsedPassword; const getAuthId = (password) => ({ @@ -18,7 +18,7 @@ const getAuthId = (password) => ({ password, identifier: { type: 'm.id.user', - user: initMatrix.matrixClient.getUserId(), + user: getSecret().userId, }, }); diff --git a/src/app/organisms/settings/CrossSigning.jsx b/src/app/organisms/settings/CrossSigning.jsx index 9d848d5a..b993e32f 100644 --- a/src/app/organisms/settings/CrossSigning.jsx +++ b/src/app/organisms/settings/CrossSigning.jsx @@ -4,7 +4,6 @@ import './CrossSigning.scss'; import FileSaver from 'file-saver'; import { Formik } from 'formik'; -import initMatrix from '../../../client/initMatrix'; import { openReusableDialog } from '../../../client/action/navigation'; import { copyToClipboard } from '../../../util/common'; import { clearSecretStorageKeys } from '../../../client/state/secretStorageKeys'; @@ -17,6 +16,7 @@ import SettingTile from '../../molecules/setting-tile/SettingTile'; import { authRequest } from './AuthRequest'; import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; const failedDialog = () => { const renderFailure = (requestClose) => ( @@ -73,9 +73,9 @@ const securityKeyDialog = (key) => { function CrossSigningSetup() { const initialValues = { phrase: '', confirmPhrase: '' }; const [genWithPhrase, setGenWithPhrase] = useState(undefined); + const mx = useMatrixClient(); const setup = async (securityPhrase = undefined) => { - const mx = initMatrix.matrixClient; setGenWithPhrase(typeof securityPhrase === 'string'); const recoveryKey = await mx.createRecoveryKeyFromPassphrase(securityPhrase); clearSecretStorageKeys(); diff --git a/src/app/organisms/settings/DeviceManage.jsx b/src/app/organisms/settings/DeviceManage.jsx index 4a4f141c..9fa8273e 100644 --- a/src/app/organisms/settings/DeviceManage.jsx +++ b/src/app/organisms/settings/DeviceManage.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react'; import './DeviceManage.scss'; import dateFormat from 'dateformat'; -import initMatrix from '../../../client/initMatrix'; import { isCrossVerified } from '../../../util/matrixUtil'; import { openReusableDialog, openEmojiVerification } from '../../../client/action/navigation'; @@ -26,6 +25,7 @@ import { useStore } from '../../hooks/useStore'; import { useDeviceList } from '../../hooks/useDeviceList'; import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; import { accessSecretStorage } from './SecretStorageAccess'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; const promptDeviceName = async (deviceName) => new Promise((resolve) => { let isCompleted = false; @@ -63,14 +63,14 @@ const promptDeviceName = async (deviceName) => new Promise((resolve) => { function DeviceManage() { const TRUNCATED_COUNT = 4; - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const isCSEnabled = useCrossSigningStatus(); const deviceList = useDeviceList(); const [processing, setProcessing] = useState([]); const [truncated, setTruncated] = useState(true); const mountStore = useStore(); mountStore.setItem(true); - const isMeVerified = isCrossVerified(mx.deviceId); + const isMeVerified = isCrossVerified(mx, mx.deviceId); useEffect(() => { setProcessing([]); @@ -130,7 +130,7 @@ function DeviceManage() { }; const verifyWithKey = async (device) => { - const keyData = await accessSecretStorage('Session verification'); + const keyData = await accessSecretStorage(mx, 'Session verification'); if (!keyData) return; addToProcessing(device); await mx.checkOwnCrossSigningTrust(); @@ -191,7 +191,7 @@ function DeviceManage() { )} {isCurrentDevice && ( - {`Session Key: ${initMatrix.matrixClient.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`} + {`Session Key: ${mx.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`} )} @@ -204,7 +204,7 @@ function DeviceManage() { const verified = []; const noEncryption = []; 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) { verified.push(device); } else if (isVerified === false) { diff --git a/src/app/organisms/settings/KeyBackup.jsx b/src/app/organisms/settings/KeyBackup.jsx index b4f2125e..47be511a 100644 --- a/src/app/organisms/settings/KeyBackup.jsx +++ b/src/app/organisms/settings/KeyBackup.jsx @@ -3,7 +3,6 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import './KeyBackup.scss'; -import initMatrix from '../../../client/initMatrix'; import { openReusableDialog } from '../../../client/action/navigation'; 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 { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function CreateKeyBackupDialog({ keyData }) { const [done, setDone] = useState(false); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const mountStore = useStore(); const doBackup = async () => { @@ -80,7 +80,7 @@ CreateKeyBackupDialog.propTypes = { function RestoreKeyBackupDialog({ keyData }) { const [status, setStatus] = useState(false); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const mountStore = useStore(); const restoreBackup = async () => { @@ -150,7 +150,7 @@ RestoreKeyBackupDialog.propTypes = { function DeleteKeyBackupDialog({ requestClose }) { const [isDeleting, setIsDeleting] = useState(false); - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const mountStore = useStore(); const deleteBackup = async () => { @@ -187,7 +187,7 @@ DeleteKeyBackupDialog.propTypes = { }; function KeyBackup() { - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const isCSEnabled = useCrossSigningStatus(); const [keyBackup, setKeyBackup] = useState(undefined); const mountStore = useStore(); @@ -215,7 +215,7 @@ function KeyBackup() { }, [isCSEnabled]); const openCreateKeyBackup = async () => { - const keyData = await accessSecretStorage('Create Key Backup'); + const keyData = await accessSecretStorage(mx, 'Create Key Backup'); if (keyData === null) return; openReusableDialog( @@ -228,7 +228,7 @@ function KeyBackup() { }; const openRestoreKeyBackup = async () => { - const keyData = await accessSecretStorage('Restore Key Backup'); + const keyData = await accessSecretStorage(mx, 'Restore Key Backup'); if (keyData === null) return; openReusableDialog( diff --git a/src/app/organisms/settings/SecretStorageAccess.jsx b/src/app/organisms/settings/SecretStorageAccess.jsx index e4fceb07..02882a41 100644 --- a/src/app/organisms/settings/SecretStorageAccess.jsx +++ b/src/app/organisms/settings/SecretStorageAccess.jsx @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import './SecretStorageAccess.scss'; import { deriveKey } from 'matrix-js-sdk/lib/crypto/key_passphrase'; -import initMatrix from '../../../client/initMatrix'; import { openReusableDialog } from '../../../client/action/navigation'; import { getDefaultSSKey, getSSKeyInfo } from '../../../util/matrixUtil'; 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 { useStore } from '../../hooks/useStore'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function SecretStorageAccess({ onComplete }) { - const mx = initMatrix.matrixClient; - const sSKeyId = getDefaultSSKey(); - const sSKeyInfo = getSSKeyInfo(sSKeyId); + const mx = useMatrixClient(); + const sSKeyId = getDefaultSSKey(mx); + const sSKeyInfo = getSSKeyInfo(mx, sSKeyId); const isPassphrase = !!sSKeyInfo.passphrase; const [withPhrase, setWithPhrase] = useState(isPassphrase); 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 * @returns {Promise} resolve to keyData or null */ -export const accessSecretStorage = (title) => new Promise((resolve) => { +export const accessSecretStorage = (mx, title) => new Promise((resolve) => { let isCompleted = false; - const defaultSSKey = getDefaultSSKey(); + const defaultSSKey = getDefaultSSKey(mx); if (hasPrivateKey(defaultSSKey)) { resolve({ keyId: defaultSSKey, privateKey: getPrivateKey(defaultSSKey) }); return; diff --git a/src/app/organisms/settings/Settings.jsx b/src/app/organisms/settings/Settings.jsx index 47abb45c..f2951b81 100644 --- a/src/app/organisms/settings/Settings.jsx +++ b/src/app/organisms/settings/Settings.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import './Settings.scss'; -import initMatrix from '../../../client/initMatrix'; +import { clearCacheAndReload, logoutClient } from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import settings from '../../../client/state/settings'; import navigation from '../../../client/state/navigation'; @@ -47,6 +47,7 @@ import { useSetting } from '../../state/hooks/settings'; import { settingsAtom } from '../../state/settings'; import { isMacOS } from '../../utils/user-agent'; import { KeySymbol } from '../../utils/key-symbol'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; function AppearanceSection() { const [, updateState] = useState({}); @@ -332,6 +333,8 @@ function SecuritySection() { } function AboutSection() { + const mx = useMatrixClient(); + return (
@@ -348,7 +351,7 @@ function AboutSection() {
- +
@@ -437,11 +440,12 @@ function useWindowToggle(setSelectedTab) { function Settings() { const [selectedTab, setSelectedTab] = useState(tabItems[0]); const [isOpen, requestClose] = useWindowToggle(setSelectedTab); + const mx = useMatrixClient(); const handleTabChange = (tabItem) => setSelectedTab(tabItem); const handleLogout = async () => { 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 && (
- + tab.text === selectedTab.text)} diff --git a/src/app/organisms/space-settings/SpaceSettings.jsx b/src/app/organisms/space-settings/SpaceSettings.jsx index ff6c1863..9f2069d4 100644 --- a/src/app/organisms/space-settings/SpaceSettings.jsx +++ b/src/app/organisms/space-settings/SpaceSettings.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import './SpaceSettings.scss'; -import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; @@ -59,8 +58,8 @@ const tabItems = [ ]; function GeneralSettings({ roomId }) { - const roomName = initMatrix.matrixClient.getRoom(roomId)?.name; const mx = useMatrixClient(); + const roomName = mx.getRoom(roomId)?.name; return ( <> @@ -124,7 +123,7 @@ function SpaceSettings() { const isOpen = window !== null; const roomId = window?.roomId; - const mx = initMatrix.matrixClient; + const mx = useMatrixClient(); const room = mx.getRoom(roomId); const handleTabChange = (tabItem) => { diff --git a/src/app/pages/client/ClientLayout.tsx b/src/app/pages/client/ClientLayout.tsx index 208d12e4..4a077ba6 100644 --- a/src/app/pages/client/ClientLayout.tsx +++ b/src/app/pages/client/ClientLayout.tsx @@ -7,7 +7,7 @@ type ClientLayoutProps = { }; export function ClientLayout({ nav, children }: ClientLayoutProps) { return ( - + {nav} {children} diff --git a/src/app/pages/client/ClientRoot.tsx b/src/app/pages/client/ClientRoot.tsx index c36adfc6..a590e0bc 100644 --- a/src/app/pages/client/ClientRoot.tsx +++ b/src/app/pages/client/ClientRoot.tsx @@ -1,6 +1,27 @@ -import { Box, Spinner, Text } from 'folds'; -import React, { ReactNode, useEffect, useState } from 'react'; -import initMatrix from '../../../client/initMatrix'; +import { + Box, + 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 { SplashScreen } from '../../components/splash-screen'; import { CapabilitiesAndMediaConfigLoader } from '../../components/CapabilitiesAndMediaConfigLoader'; @@ -13,6 +34,10 @@ import Dialogs from '../../organisms/pw/Dialogs'; import ReusableContextMenu from '../../atoms/context-menu/ReusableContextMenu'; import { useSetting } from '../../state/hooks/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() { const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); @@ -37,6 +62,89 @@ function ClientRootLoading() { ); } +function ClientRootOptions({ mx }: { mx: MatrixClient }) { + const [menuAnchor, setMenuAnchor] = useState(); + + const handleToggle: MouseEventHandler = (evt) => { + const cords = evt.currentTarget.getBoundingClientRect(); + setMenuAnchor((currentState) => { + if (currentState) return undefined; + return cords; + }); + }; + + return ( + + + setMenuAnchor(undefined), + clickOutsideDeactivates: true, + isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', + isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, + }} + > + + + clearCacheAndReload(mx)} size="300" radii="300"> + + Clear Cache and Reload + + + logoutClient(mx)} + size="300" + radii="300" + variant="Critical" + fill="None" + > + + Logout + + + + + + } + /> + + ); +} + +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 = { children: ReactNode; }; @@ -44,30 +152,71 @@ export function ClientRoot({ children }: ClientRootProps) { const [loading, setLoading] = useState(true); const { baseUrl } = getSecret(); + const [loadState, loadMatrix] = useAsyncCallback( + useCallback(() => initClient(getSecret() as any), []) + ); + const mx = loadState.status === AsyncStatus.Success ? loadState.data : undefined; + const [startState, startMatrix] = useAsyncCallback( + useCallback((m) => startClient(m), []) + ); + + useLogoutListener(mx); + useEffect(() => { - const handleStart = () => { - setLoading(false); - }; - initMatrix.once('init_loading_finished', handleStart); - if (!initMatrix.matrixClient) initMatrix.init(); - return () => { - initMatrix.removeListener('init_loading_finished', 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); + } + }, []) + ); return ( - {loading ? ( + {mx && } + {loading && mx && } + {(loadState.status === AsyncStatus.Error || startState.status === AsyncStatus.Error) && ( + + + + + {loadState.status === AsyncStatus.Error && ( + {`Failed to load. ${loadState.error.message}`} + )} + {startState.status === AsyncStatus.Error && ( + {`Failed to load. ${startState.error.message}`} + )} + + + + + + )} + {loading || !mx ? ( ) : ( - + {(capabilities, mediaConfig) => ( {children} - - {/* TODO: remove these components after navigation refactor */} diff --git a/src/app/pages/client/SyncStatus.tsx b/src/app/pages/client/SyncStatus.tsx new file mode 100644 index 00000000..9cd4b0b2 --- /dev/null +++ b/src/app/pages/client/SyncStatus.tsx @@ -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({ + 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 ( + + + Connecting... + + + + ); + } + + if (stateData.current === SyncState.Reconnecting) { + return ( + + + Connection Lost! Reconnecting... + + + + ); + } + + if (stateData.current === SyncState.Error) { + return ( + + + Connection Lost! + + + + ); + } + + return null; +} diff --git a/src/app/pages/client/WelcomePage.tsx b/src/app/pages/client/WelcomePage.tsx index 2486625f..5040d15f 100644 --- a/src/app/pages/client/WelcomePage.tsx +++ b/src/app/pages/client/WelcomePage.tsx @@ -18,7 +18,7 @@ export function WelcomePage() { title="Welcome to Cinny" subTitle={ - Yet anothor matrix client.{' '} + Yet another matrix client.{' '} void; }; const DirectMenu = forwardRef(({ requestClose }, ref) => { + const mx = useMatrixClient(); const orphanRooms = useDirectRooms(); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); const handleMarkAsRead = () => { if (!unread) return; - orphanRooms.forEach((rId) => markAsRead(rId)); + orphanRooms.forEach((rId) => markAsRead(mx, rId)); requestClose(); }; diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index a06e108e..f9923f46 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -55,10 +55,11 @@ type HomeMenuProps = { const HomeMenu = forwardRef(({ requestClose }, ref) => { const orphanRooms = useHomeRooms(); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); + const mx = useMatrixClient(); const handleMarkAsRead = () => { if (!unread) return; - orphanRooms.forEach((rId) => markAsRead(rId)); + orphanRooms.forEach((rId) => markAsRead(mx, rId)); requestClose(); }; diff --git a/src/app/pages/client/inbox/Notifications.tsx b/src/app/pages/client/inbox/Notifications.tsx index 8b9d1847..a01ecb8e 100644 --- a/src/app/pages/client/inbox/Notifications.tsx +++ b/src/app/pages/client/inbox/Notifications.tsx @@ -356,7 +356,7 @@ function RoomNotificationsGroupComp({ onOpen(room.roomId, eventId); }; const handleMarkAsRead = () => { - markAsRead(room.roomId); + markAsRead(mx, room.roomId); }; return ( diff --git a/src/app/pages/client/sidebar/DirectTab.tsx b/src/app/pages/client/sidebar/DirectTab.tsx index 0beb17d8..849fc365 100644 --- a/src/app/pages/client/sidebar/DirectTab.tsx +++ b/src/app/pages/client/sidebar/DirectTab.tsx @@ -30,10 +30,11 @@ type DirectMenuProps = { const DirectMenu = forwardRef(({ requestClose }, ref) => { const orphanRooms = useDirectRooms(); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); + const mx = useMatrixClient(); const handleMarkAsRead = () => { if (!unread) return; - orphanRooms.forEach((rId) => markAsRead(rId)); + orphanRooms.forEach((rId) => markAsRead(mx, rId)); requestClose(); }; diff --git a/src/app/pages/client/sidebar/HomeTab.tsx b/src/app/pages/client/sidebar/HomeTab.tsx index 41f7c648..dcb0a498 100644 --- a/src/app/pages/client/sidebar/HomeTab.tsx +++ b/src/app/pages/client/sidebar/HomeTab.tsx @@ -31,10 +31,11 @@ type HomeMenuProps = { const HomeMenu = forwardRef(({ requestClose }, ref) => { const orphanRooms = useHomeRooms(); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); + const mx = useMatrixClient(); const handleMarkAsRead = () => { if (!unread) return; - orphanRooms.forEach((rId) => markAsRead(rId)); + orphanRooms.forEach((rId) => markAsRead(mx, rId)); requestClose(); }; diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx index 8635a35f..7b3e61e7 100644 --- a/src/app/pages/client/sidebar/SpaceTabs.tsx +++ b/src/app/pages/client/sidebar/SpaceTabs.tsx @@ -114,7 +114,7 @@ const SpaceMenu = forwardRef( const unread = useRoomsUnread(allChild, roomToUnreadAtom); const handleMarkAsRead = () => { - allChild.forEach((childRoomId) => markAsRead(childRoomId)); + allChild.forEach((childRoomId) => markAsRead(mx, childRoomId)); requestClose(); }; diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx index e947373b..e280c603 100644 --- a/src/app/pages/client/space/Space.tsx +++ b/src/app/pages/client/space/Space.tsx @@ -95,7 +95,7 @@ const SpaceMenu = forwardRef(({ room, requestClo const unread = useRoomsUnread(allChild, roomToUnreadAtom); const handleMarkAsRead = () => { - allChild.forEach((childRoomId) => markAsRead(childRoomId)); + allChild.forEach((childRoomId) => markAsRead(mx, childRoomId)); requestClose(); }; diff --git a/src/client/action/notifications.js b/src/client/action/notifications.ts similarity index 69% rename from src/client/action/notifications.js rename to src/client/action/notifications.ts index 579c7c3c..17ea1ed6 100644 --- a/src/client/action/notifications.js +++ b/src/client/action/notifications.ts @@ -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(roomId) { - const mx = initMatrix.matrixClient; +export async function markAsRead(mx: MatrixClient, roomId: string) { const room = mx.getRoom(roomId); if (!room) return; const timeline = room.getLiveTimeline().getEvents(); - const readEventId = room.getEventReadUpTo(mx.getUserId()); + const readEventId = room.getEventReadUpTo(mx.getUserId()!); const getLatestValidEvent = () => { for (let i = timeline.length - 1; i >= 0; i -= 1) { diff --git a/src/client/action/room.js b/src/client/action/room.js index c2d11438..cd4995b9 100644 --- a/src/client/action/room.js +++ b/src/client/action/room.js @@ -1,16 +1,13 @@ -import initMatrix from '../initMatrix'; -import appDispatcher from '../dispatcher'; -import cons from '../state/cons'; import { getIdServer } from '../../util/matrixUtil'; /** * 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} userId User id to which dm || undefined to remove * @returns {Promise} A promise */ -function addRoomToMDirect(roomId, userId) { - const mx = initMatrix.matrixClient; +function addRoomToMDirect(mx, roomId, userId) { const mDirectsEvent = mx.getAccountData('m.direct'); let userIdToRoomIds = {}; @@ -79,24 +76,22 @@ function guessDMRoomTargetId(room, myUserId) { return oldestMember.userId; } -function convertToDm(roomId) { - const mx = initMatrix.matrixClient; +function convertToDm(mx, roomId) { const room = mx.getRoom(roomId); - return addRoomToMDirect(roomId, guessDMRoomTargetId(room, mx.getUserId())); + return addRoomToMDirect(mx, roomId, guessDMRoomTargetId(room, mx.getUserId())); } -function convertToRoom(roomId) { - return addRoomToMDirect(roomId, undefined); +function convertToRoom(mx, roomId) { + return addRoomToMDirect(mx, roomId, undefined); } /** - * + * @param {MatrixClient} mx * @param {string} roomId * @param {boolean} isDM * @param {string[]} via */ -async function join(roomIdOrAlias, isDM = false, via = undefined) { - const mx = initMatrix.matrixClient; +async function join(mx, roomIdOrAlias, isDM = false, via = undefined) { const roomIdParts = roomIdOrAlias.split(':'); const viaServers = via || [roomIdParts[1]]; @@ -105,7 +100,7 @@ async function join(roomIdOrAlias, isDM = false, via = undefined) { if (isDM) { const targetUserId = guessDMRoomTargetId(mx.getRoom(resultRoom.roomId), mx.getUserId()); - await addRoomToMDirect(resultRoom.roomId, targetUserId); + await addRoomToMDirect(mx, resultRoom.roomId, targetUserId); } return resultRoom.roomId; } catch (e) { @@ -113,12 +108,11 @@ async function join(roomIdOrAlias, isDM = false, via = undefined) { } } -async function create(options, isDM = false) { - const mx = initMatrix.matrixClient; +async function create(mx, options, isDM = false) { try { const result = await mx.createRoom(options); 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; } 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 = { is_direct: true, 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; } -async function createRoom(opts) { +async function createRoom(mx, opts) { // joinRule: 'public' | 'invite' | 'restricted' const { name, topic, joinRule } = opts; const alias = opts.alias ?? undefined; @@ -162,7 +156,6 @@ async function createRoom(opts) { const powerLevel = opts.powerLevel ?? undefined; const blockFederation = opts.blockFederation ?? false; - const mx = initMatrix.matrixClient; const visibility = joinRule === 'public' ? 'public' : 'private'; const options = { creation_content: undefined, @@ -225,7 +218,7 @@ async function createRoom(opts) { }); } - const result = await create(options); + const result = await create(mx, options); if (parentId) { await mx.sendStateEvent(parentId, 'm.space.child', { @@ -238,51 +231,19 @@ async function createRoom(opts) { return result; } -async function invite(roomId, userId, reason) { - 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; +async function ignore(mx, userIds) { let ignoredUsers = mx.getIgnoredUsers().concat(userIds); ignoredUsers = [...new Set(ignoredUsers)]; await mx.setIgnoredUsers(ignoredUsers); } -async function unignore(userIds) { - const mx = initMatrix.matrixClient; - +async function unignore(mx, userIds) { const ignoredUsers = mx.getIgnoredUsers(); await mx.setIgnoredUsers(ignoredUsers.filter((id) => !userIds.includes(id))); } -async function setPowerLevel(roomId, userId, powerLevel) { - const mx = initMatrix.matrixClient; +async function setPowerLevel(mx, roomId, userId, powerLevel) { const room = mx.getRoom(roomId); const powerlevelEvent = room.currentState.getStateEvents('m.room.power_levels')[0]; @@ -291,8 +252,7 @@ async function setPowerLevel(roomId, userId, powerLevel) { return result; } -async function setMyRoomNick(roomId, nick) { - const mx = initMatrix.matrixClient; +async function setMyRoomNick(mx, roomId, nick) { const room = mx.getRoom(roomId); const mEvent = room.currentState.getStateEvents('m.room.member', mx.getUserId()); const content = mEvent?.getContent(); @@ -303,8 +263,7 @@ async function setMyRoomNick(roomId, nick) { }, mx.getUserId()); } -async function setMyRoomAvatar(roomId, mxc) { - const mx = initMatrix.matrixClient; +async function setMyRoomAvatar(mx, roomId, mxc) { const room = mx.getRoom(roomId); const mEvent = room.currentState.getStateEvents('m.room.member', mx.getUserId()); const content = mEvent?.getContent(); @@ -320,7 +279,6 @@ export { convertToRoom, join, createDM, createRoom, - invite, kick, ban, unban, ignore, unignore, setPowerLevel, setMyRoomNick, setMyRoomAvatar, diff --git a/src/client/initMatrix.js b/src/client/initMatrix.js deleted file mode 100644 index 0352ff36..00000000 --- a/src/client/initMatrix.js +++ /dev/null @@ -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; diff --git a/src/client/initMatrix.ts b/src/client/initMatrix.ts new file mode 100644 index 00000000..5a156ad8 --- /dev/null +++ b/src/client/initMatrix.ts @@ -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 => { + 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(); +}; diff --git a/src/client/mx.ts b/src/client/mx.ts deleted file mode 100644 index 30909458..00000000 --- a/src/client/mx.ts +++ /dev/null @@ -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!; -}; diff --git a/src/index.scss b/src/index.scss index 5903858a..1c223051 100644 --- a/src/index.scss +++ b/src/index.scss @@ -409,6 +409,8 @@ body { #root { width: 100%; height: 100%; + display: flex; + flex-direction: column; } *, diff --git a/src/util/matrixUtil.js b/src/util/matrixUtil.js index 74e56ec7..e4fd40c3 100644 --- a/src/util/matrixUtil.js +++ b/src/util/matrixUtil.js @@ -1,5 +1,3 @@ -import initMatrix from '../client/initMatrix'; - import HashIC from '../../public/res/ic/outlined/hash.svg'; import HashGlobeIC from '../../public/res/ic/outlined/hash-globe.svg'; import HashLockIC from '../../public/res/ic/outlined/hash-lock.svg'; @@ -24,8 +22,7 @@ export async function getBaseUrl(servername) { } } -export function getUsername(userId) { - const mx = initMatrix.matrixClient; +export function getUsername(mx, userId) { const user = mx.getUser(userId); if (user === null) return userId; let username = user.displayName; @@ -39,9 +36,9 @@ export function getUsernameOfRoomMember(roomMember) { return roomMember.name || roomMember.userId; } -export async function isRoomAliasAvailable(alias) { +export async function isRoomAliasAvailable(mx, alias) { try { - const result = await initMatrix.matrixClient.getRoomIdForAlias(alias); + const result = await mx.getRoomIdForAlias(alias); if (result.room_id) return false; return false; } catch (e) { @@ -159,9 +156,8 @@ export function genRoomVia(room) { return via.concat(mostPop3.slice(0, 2)); } -export function isCrossVerified(deviceId) { +export function isCrossVerified(mx, deviceId) { try { - const mx = initMatrix.matrixClient; const crossSignInfo = mx.getStoredCrossSigningForUser(mx.getUserId()); const deviceInfo = mx.getStoredDevice(mx.getUserId(), deviceId); const deviceTrust = crossSignInfo.checkDeviceTrust(crossSignInfo, deviceInfo, false, true); @@ -172,14 +168,12 @@ export function isCrossVerified(deviceId) { } } -export function hasCrossSigningAccountData() { - const mx = initMatrix.matrixClient; +export function hasCrossSigningAccountData(mx) { const masterKeyData = mx.getAccountData('m.cross_signing.master'); return !!masterKeyData; } -export function getDefaultSSKey() { - const mx = initMatrix.matrixClient; +export function getDefaultSSKey(mx) { try { return mx.getAccountData('m.secret_storage.default_key').getContent().key; } catch { @@ -187,8 +181,7 @@ export function getDefaultSSKey() { } } -export function getSSKeyInfo(key) { - const mx = initMatrix.matrixClient; +export function getSSKeyInfo(mx, key) { try { return mx.getAccountData(`m.secret_storage.key.${key}`).getContent(); } catch { @@ -196,8 +189,7 @@ export function getSSKeyInfo(key) { } } -export async function hasDevices(userId) { - const mx = initMatrix.matrixClient; +export async function hasDevices(mx, userId) { try { const usersDeviceMap = await mx.downloadKeys([userId, mx.getUserId()]); return Object.values(usersDeviceMap) diff --git a/src/util/sort.js b/src/util/sort.js index f9a0b790..cc73cf57 100644 --- a/src/util/sort.js +++ b/src/util/sort.js @@ -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) { const aName = m1.name; const bName = m2.name;