const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
const handleMarkAsRead = () => {
- markAsRead(room.roomId);
+ markAsRead(mx, room.roomId);
requestClose();
};
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();
useCallback(
(evt) => {
if (isKeyHotkey('escape', evt)) {
- markAsRead(room.roomId);
+ markAsRead(mx, room.roomId);
}
},
[room.roomId]
// 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()) {
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) => {
};
const handleMarkAsRead = () => {
- markAsRead(room.roomId);
+ markAsRead(mx, room.roomId);
};
const handleOpenReply: MouseEventHandler<HTMLButtonElement> = useCallback(
</Text>
)}
</Box>
- {replacementRoom?.getMyMembership() === Membership.Join ||
- joinState.status === AsyncStatus.Success ? (
- <Button onClick={handleOpen} size="300" variant="Success" fill="Solid" radii="300">
- <Text size="B300">Open New Room</Text>
- </Button>
- ) : (
- <Button
- onClick={handleJoin}
- size="300"
- variant="Primary"
- fill="Solid"
- radii="300"
- before={
- joinState.status === AsyncStatus.Loading && (
- <Spinner size="100" variant="Primary" fill="Solid" />
- )
- }
- disabled={joinState.status === AsyncStatus.Loading}
- >
- <Text size="B300">Join New Room</Text>
- </Button>
- )}
+ <Box shrink="No">
+ {replacementRoom?.getMyMembership() === Membership.Join ||
+ joinState.status === AsyncStatus.Success ? (
+ <Button onClick={handleOpen} size="300" variant="Success" fill="Solid" radii="300">
+ <Text size="B300">Open New Room</Text>
+ </Button>
+ ) : (
+ <Button
+ onClick={handleJoin}
+ size="300"
+ variant="Primary"
+ fill="Solid"
+ radii="300"
+ before={
+ joinState.status === AsyncStatus.Loading && (
+ <Spinner size="100" variant="Primary" fill="Solid" />
+ )
+ }
+ disabled={joinState.status === AsyncStatus.Loading}
+ >
+ <Text size="B300">Join New Room</Text>
+ </Button>
+ )}
+ </Box>
</RoomInputPlaceholder>
);
}
const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
const handleMarkAsRead = () => {
- markAsRead(room.roomId);
+ markAsRead(mx, room.roomId);
requestClose();
};
/* 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(() => {
return () => {
mx.removeListener('accountData', handleChange);
};
- }, [eventType]);
+ }, [mx, eventType]);
return event;
}
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);
},
},
const roomIds = rawIds.filter(
(idOrAlias) => isRoomId(idOrAlias) || isRoomAlias(idOrAlias)
);
- roomIds.map((id) => roomActions.join(id));
+ roomIds.map((id) => roomActions.join(mx, id));
},
},
[Command.Leave]: {
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]: {
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]: {
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]: {
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]: {
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]: {
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]: {
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]: {
exe: async (payload) => {
const nick = payload.trim();
if (nick === '') return;
- roomActions.setMyRoomNick(room.roomId, nick);
+ roomActions.setMyRoomNick(mx, room.roomId, nick);
},
},
[Command.MyRoomAvatar]: {
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);
}
},
},
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);
},
},
}),
/* 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;
return () => {
mx.removeListener('accountData', handleAccountData);
};
- }, [isCSEnabled === false]);
+ }, [mx, isCSEnabled]);
return isCSEnabled;
}
/* 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(() => {
mx.removeListener('crypto.devicesUpdated', handleDevicesUpdate);
isMounted = false;
};
- }, []);
+ }, [mx]);
return deviceList;
}
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]);
};
import React from 'react';
-import initMatrix from '../../../client/initMatrix';
import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common';
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',
}
function useGlobalNotif() {
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const pushRules = useAccountData('m.push_rules')?.getContent();
const underride = pushRules?.global?.underride ?? [];
const rulesToType = {
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';
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();
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 (
key={uId}
text={uId}
iconColor={CrossIC}
- onClick={() => roomActions.unignore([uId])}
+ onClick={() => roomActions.unignore(mx, [uId])}
/>
))}
</div>
import React from 'react';
import './KeywordNotification.scss';
-import initMatrix from '../../../client/initMatrix';
import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common';
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';
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 ?? [];
import PropTypes from 'prop-types';
import './ImagePack.scss';
-import initMatrix from '../../../client/initMatrix';
import { openReusableDialog } from '../../../client/action/navigation';
import { suffixRename } from '../../../util/common';
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;
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;
}
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);
}
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);
}
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);
};
function ImagePackUser() {
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const [viewMore, setViewMore] = useState(false);
const { pack, sendPackContent } = useUserImagePack();
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: {} };
return () => {
mx.removeListener('accountData', handleEvent);
};
- }, []);
+ }, [mx]);
return roomIdToStateKeys;
}
function ImagePackGlobal() {
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const roomIdToStateKeys = useGlobalImagePack();
const handleChange = (roomId, stateKey) => {
import PropTypes from 'prop-types';
import './ImagePackUpload.scss';
-import initMatrix from '../../../client/initMatrix';
import { scaleDownImage } from '../../../util/common';
import Text from '../../atoms/text/Text';
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);
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';
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,
}) {
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;
}
function cancelUpload() {
- initMatrix.matrixClient.cancelUpload(uploadPromise);
+ mx.cancelUpload(uploadPromise);
setUploadPromise(null);
uploadImageRef.current.value = null;
}
import FileSaver from 'file-saver';
-import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import { encryptMegolmKeyFile } from '../../../util/cryptE2ERoomKeys';
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,
type: cons.status.IN_FLIGHT,
});
try {
- const keys = await initMatrix.matrixClient.exportRoomKeys();
+ const keys = await mx.exportRoomKeys();
if (isMountStore.getItem()) {
setStatus({
isOngoing: true,
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';
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({
type: cons.status.IN_FLIGHT,
});
}
- await initMatrix.matrixClient.importRoomKeys(JSON.parse(keys));
+ await mx.importRoomKeys(JSON.parse(keys));
if (isMountStore.getItem()) {
setStatus({
isOngoing: false,
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';
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 });
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 };
return [validate, setValidateToDefault, handleAliasChange];
}
-function getAliases(roomId) {
- const mx = initMatrix.matrixClient;
+function getAliases(mx, roomId) {
const room = mx.getRoom(roomId);
const main = room.getCanonicalAlias();
}
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);
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);
return () => {
isUnmounted = true;
};
- }, [roomId]);
+ }, [mx, roomId]);
const toggleDirectoryVisibility = () => {
mx.setRoomDirectoryVisibility(roomId, isPublic ? 'private' : 'public');
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';
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');
}
function RoomEmojis({ roomId }) {
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const room = mx.getRoom(roomId);
const { usablePacks, createPack, deletePack } = useRoomPacks(room);
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);
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',
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;
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';
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),
}
function useMemberOfMembership(roomId, membership) {
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const room = mx.getRoom(roomId);
const [members, setMembers] = useState([]);
if (isLoadingMembers) return;
if (event && event?.getRoomId() !== roomId) return;
const memberOfMembership = normalizeMembers(
+ mx,
room.getMembersWithMembership(membership)
.sort(memberByAtoZ).sort(memberByPowerLevel),
);
mx.removeListener('RoomMember.membership', updateMemberList);
mx.removeListener('RoomMember.powerLevel', updateMemberList);
};
- }, [membership]);
+ }, [mx, membership]);
return [members];
}
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';
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 = [
{
},
];
-function setRoomNotifType(roomId, newType) {
- const mx = initMatrix.matrixClient;
+function setRoomNotifType(mx, roomId, newType) {
let roomPushRule;
try {
roomPushRule = mx.getRoomPushRule('global', roomId);
}
function useNotifications(roomId) {
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const [activeType, setActiveType] = useState(getNotificationType(mx, roomId));
useEffect(() => {
setActiveType(getNotificationType(mx, 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];
}
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';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import { useForceUpdate } from '../../hooks/useForceUpdate';
+import { useMatrixClient } from '../../hooks/useMatrixClient';
const permissionsInfo = {
users_default: {
function useRoomStateUpdate(roomId) {
const [, forceUpdate] = useForceUpdate();
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
useEffect(() => {
const handleStateEvent = (event) => {
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();
import Linkify from 'linkify-react';
import './RoomProfile.scss';
-import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import colorMXID from '../../../util/colorMXID';
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();
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');
});
setIsEditing(false);
};
- }, [roomId]);
+ }, [mx, roomId]);
const handleOnSubmit = async (e) => {
e.preventDefault();
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';
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',
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);
}
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;
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';
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);
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);
(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;
function SpaceAddExisting() {
const [data, requestClose] = useVisibilityToggle();
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const room = mx.getRoom(data?.roomId);
return (
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';
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');
const addressRef = useRef(null);
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const userHs = getIdServer(mx.getUserId());
const handleSubmit = async (evt) => {
const powerLevel = roleIndex === 1 ? 101 : undefined;
try {
- const data = await roomActions.createRoom({
+ const data = await roomActions.createRoom(mx, {
name,
topic,
joinRule,
if (roomAlias === '') return;
const roomAddress = `#${roomAlias}:${userHs}`;
- if (await isRoomAliasAvailable(roomAddress)) {
+ if (await isRoomAliasAvailable(mx, roomAddress)) {
setIsValidAddress(true);
} else {
setIsValidAddress(false);
function CreateRoom() {
const [create, onRequestClose] = useWindowToggle();
const { isSpace, parentId } = create ?? {};
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const room = mx.getRoom(parentId);
return (
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';
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;
function useVisibilityToggle() {
const [data, setData] = useState(null);
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
useEffect(() => {
const handleOpen = (request, targetDevice) => {
navigation.removeListener(cons.events.navigation.EMOJI_VERIFICATION_OPENED, handleOpen);
mx.removeListener('crypto.verification.request', handleOpen);
};
- }, []);
+ }, [mx]);
const requestClose = () => setData(null);
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';
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);
const usernameRef = useRef(null);
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const { navigateRoom } = useRoomNavigate();
function getMapCopy(myMap) {
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);
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)));
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';
import { useStore } from '../../hooks/useStore';
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
+import { useMatrixClient } from '../../hooks/useMatrixClient';
const ALIAS_OR_ID_REG = /^[#|!].+:.+\..+$/;
const [process, setProcess] = useState(false);
const [error, setError] = useState(undefined);
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const mountStore = useStore();
const { navigateRoom } = useRoomNavigate();
}
}
try {
- const roomId = await join(alias, false, via);
+ const roomId = await join(mx, alias, false, via);
if (!mountStore.getItem()) return;
openRoom(roomId);
} catch {
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';
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);
return () => {
isMounted = false;
};
- }, [userId]);
+ }, [mx, userId]);
const handleAvatarUpload = async (url) => {
if (url === null) {
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';
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);
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 (
function SessionInfo({ userId }) {
const [devices, setDevices] = useState(null);
const [isVisible, setIsVisible] = useState(false);
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
useEffect(() => {
let isUnmounted = false;
return () => {
isUnmounted = true;
};
- }, [userId]);
+ }, [mx, userId]);
function renderSessionChips() {
if (!isVisible) return null;
>
<Text variant="b2">{`View ${
devices?.length > 0
- ? `${devices.length} ${devices.length == 1 ? 'session' : 'sessions'}`
+ ? `${devices.length} ${devices.length === 1 ? 'session' : 'sessions'}`
: 'sessions'
}`}</Text>
</MenuItem>
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);
};
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.
// 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;
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;
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;
{isCreatingDM ? 'Creating room...' : 'Message'}
</Button>
{isBanned && canIKick && (
- <Button variant="positive" onClick={() => roomActions.unban(roomId, userId)}>
+ <Button variant="positive" onClick={() => mx.unban(roomId, userId)}>
Unban
</Button>
)}
}
function useRerenderOnProfileChange(roomId, userId) {
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const [, forceUpdate] = useForceUpdate();
useEffect(() => {
const handleProfileChange = (mEvent, member) => {
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;
'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);
}
};
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';
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',
];
function GeneralSettings({ roomId }) {
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const room = mx.getRoom(roomId);
return (
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);
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';
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);
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);
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);
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);
const loadRecentRooms = () => {
const recentRooms = [];
- handleSearchResults(mapRoomIds(recentRooms, directs, roomToParents).reverse());
+ handleSearchResults(mapRoomIds(mx, recentRooms, directs, roomToParents).reverse());
};
const handleAfterOpen = () => {
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';
import Spinner from '../../atoms/spinner/Spinner';
import { useStore } from '../../hooks/useStore';
+import { getSecret } from '../../../client/state/auth';
let lastUsedPassword;
const getAuthId = (password) => ({
password,
identifier: {
type: 'm.id.user',
- user: initMatrix.matrixClient.getUserId(),
+ user: getSecret().userId,
},
});
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';
import { authRequest } from './AuthRequest';
import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus';
+import { useMatrixClient } from '../../hooks/useMatrixClient';
const failedDialog = () => {
const renderFailure = (requestClose) => (
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();
import './DeviceManage.scss';
import dateFormat from 'dateformat';
-import initMatrix from '../../../client/initMatrix';
import { isCrossVerified } from '../../../util/matrixUtil';
import { openReusableDialog, openEmojiVerification } from '../../../client/action/navigation';
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;
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([]);
};
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();
)}
{isCurrentDevice && (
<Text style={{ marginTop: 'var(--sp-ultra-tight)' }} variant="b3">
- {`Session Key: ${initMatrix.matrixClient.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`}
+ {`Session Key: ${mx.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`}
</Text>
)}
</>
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) {
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';
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 () => {
function RestoreKeyBackupDialog({ keyData }) {
const [status, setStatus] = useState(false);
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const mountStore = useStore();
const restoreBackup = async () => {
function DeleteKeyBackupDialog({ requestClose }) {
const [isDeleting, setIsDeleting] = useState(false);
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const mountStore = useStore();
const deleteBackup = async () => {
};
function KeyBackup() {
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const isCSEnabled = useCrossSigningStatus();
const [keyBackup, setKeyBackup] = useState(undefined);
const mountStore = useStore();
}, [isCSEnabled]);
const openCreateKeyBackup = async () => {
- const keyData = await accessSecretStorage('Create Key Backup');
+ const keyData = await accessSecretStorage(mx, 'Create Key Backup');
if (keyData === null) return;
openReusableDialog(
};
const openRestoreKeyBackup = async () => {
- const keyData = await accessSecretStorage('Restore Key Backup');
+ const keyData = await accessSecretStorage(mx, 'Restore Key Backup');
if (keyData === null) return;
openReusableDialog(
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';
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);
};
/**
+ * @param {MatrixClient} mx Matrix client
* @param {string} title Title of secret storage access dialog
* @returns {Promise<keyData | null>} resolve to keyData or null
*/
-export const accessSecretStorage = (title) => new Promise((resolve) => {
+export const accessSecretStorage = (mx, title) => new Promise((resolve) => {
let isCompleted = false;
- const defaultSSKey = getDefaultSSKey();
+ const defaultSSKey = getDefaultSSKey(mx);
if (hasPrivateKey(defaultSSKey)) {
resolve({ keyId: defaultSSKey, privateKey: getPrivateKey(defaultSSKey) });
return;
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';
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({});
}
function AboutSection() {
+ const mx = useMatrixClient();
+
return (
<div className="settings-about">
<div className="settings-about__card">
<div className="settings-about__btns">
<Button onClick={() => window.open('https://github.com/ajbura/cinny')}>Source code</Button>
<Button onClick={() => window.open('https://cinny.in/#sponsor')}>Support</Button>
- <Button onClick={() => initMatrix.clearCacheAndReload()} variant="danger">Clear cache & reload</Button>
+ <Button onClick={() => clearCacheAndReload(mx)} variant="danger">Clear cache & reload</Button>
</div>
</div>
</div>
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);
}
};
>
{isOpen && (
<div className="settings-window__content">
- <ProfileEditor userId={initMatrix.matrixClient.getUserId()} />
+ <ProfileEditor userId={mx.getUserId()} />
<Tabs
items={tabItems}
defaultSelected={tabItems.findIndex((tab) => tab.text === selectedTab.text)}
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';
];
function GeneralSettings({ roomId }) {
- const roomName = initMatrix.matrixClient.getRoom(roomId)?.name;
const mx = useMatrixClient();
+ const roomName = mx.getRoom(roomId)?.name;
return (
<>
const isOpen = window !== null;
const roomId = window?.roomId;
- const mx = initMatrix.matrixClient;
+ const mx = useMatrixClient();
const room = mx.getRoom(roomId);
const handleTabChange = (tabItem) => {
};
export function ClientLayout({ nav, children }: ClientLayoutProps) {
return (
- <Box style={{ height: '100%' }}>
+ <Box grow="Yes">
<Box shrink="No">{nav}</Box>
<Box grow="Yes">{children}</Box>
</Box>
-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';
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');
);
}
+function ClientRootOptions({ mx }: { mx: MatrixClient }) {
+ const [menuAnchor, setMenuAnchor] = useState<RectCords>();
+
+ const handleToggle: MouseEventHandler<HTMLButtonElement> = (evt) => {
+ const cords = evt.currentTarget.getBoundingClientRect();
+ setMenuAnchor((currentState) => {
+ if (currentState) return undefined;
+ return cords;
+ });
+ };
+
+ return (
+ <IconButton
+ style={{
+ position: 'absolute',
+ top: config.space.S100,
+ right: config.space.S100,
+ }}
+ variant="Background"
+ fill="None"
+ onClick={handleToggle}
+ >
+ <Icon size="200" src={Icons.VerticalDots} />
+ <PopOut
+ anchor={menuAnchor}
+ position="Bottom"
+ align="End"
+ offset={6}
+ content={
+ <FocusTrap
+ focusTrapOptions={{
+ initialFocus: false,
+ returnFocusOnDeactivate: false,
+ onDeactivate: () => setMenuAnchor(undefined),
+ clickOutsideDeactivates: true,
+ isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
+ isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
+ escapeDeactivates: stopPropagation,
+ }}
+ >
+ <Menu>
+ <Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
+ <MenuItem onClick={() => clearCacheAndReload(mx)} size="300" radii="300">
+ <Text as="span" size="T300" truncate>
+ Clear Cache and Reload
+ </Text>
+ </MenuItem>
+ <MenuItem
+ onClick={() => logoutClient(mx)}
+ size="300"
+ radii="300"
+ variant="Critical"
+ fill="None"
+ >
+ <Text as="span" size="T300" truncate>
+ Logout
+ </Text>
+ </MenuItem>
+ </Box>
+ </Menu>
+ </FocusTrap>
+ }
+ />
+ </IconButton>
+ );
+}
+
+const useLogoutListener = (mx?: MatrixClient) => {
+ useEffect(() => {
+ const handleLogout: HttpApiEventHandlerMap[HttpApiEvent.SessionLoggedOut] = async () => {
+ mx?.stopClient();
+ await mx?.clearStores();
+ window.localStorage.clear();
+ window.location.reload();
+ };
+
+ mx?.on(HttpApiEvent.SessionLoggedOut, handleLogout);
+ return () => {
+ mx?.removeListener(HttpApiEvent.SessionLoggedOut, handleLogout);
+ };
+ }, [mx]);
+};
+
type ClientRootProps = {
children: ReactNode;
};
const [loading, setLoading] = useState(true);
const { baseUrl } = getSecret();
+ const [loadState, loadMatrix] = useAsyncCallback<MatrixClient, Error, []>(
+ useCallback(() => initClient(getSecret() as any), [])
+ );
+ const mx = loadState.status === AsyncStatus.Success ? loadState.data : undefined;
+ const [startState, startMatrix] = useAsyncCallback<void, Error, [MatrixClient]>(
+ useCallback((m) => startClient(m), [])
+ );
+
+ useLogoutListener(mx);
+
useEffect(() => {
- 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 (
<SpecVersions baseUrl={baseUrl!}>
- {loading ? (
+ {mx && <SyncStatus mx={mx} />}
+ {loading && mx && <ClientRootOptions mx={mx} />}
+ {(loadState.status === AsyncStatus.Error || startState.status === AsyncStatus.Error) && (
+ <SplashScreen>
+ <Box direction="Column" grow="Yes" alignItems="Center" justifyContent="Center" gap="400">
+ <Dialog>
+ <Box direction="Column" gap="400" style={{ padding: config.space.S400 }}>
+ {loadState.status === AsyncStatus.Error && (
+ <Text>{`Failed to load. ${loadState.error.message}`}</Text>
+ )}
+ {startState.status === AsyncStatus.Error && (
+ <Text>{`Failed to load. ${startState.error.message}`}</Text>
+ )}
+ <Button variant="Critical" onClick={loadMatrix}>
+ <Text as="span" size="B400">
+ Retry
+ </Text>
+ </Button>
+ </Box>
+ </Dialog>
+ </Box>
+ </SplashScreen>
+ )}
+ {loading || !mx ? (
<ClientRootLoading />
) : (
- <MatrixClientProvider value={initMatrix.matrixClient!}>
+ <MatrixClientProvider value={mx}>
<CapabilitiesAndMediaConfigLoader>
{(capabilities, mediaConfig) => (
<CapabilitiesProvider value={capabilities ?? {}}>
<MediaConfigProvider value={mediaConfig ?? {}}>
{children}
-
- {/* TODO: remove these components after navigation refactor */}
<Windows />
<Dialogs />
<ReusableContextMenu />
--- /dev/null
+import { MatrixClient, SyncState } from 'matrix-js-sdk';
+import React, { useCallback, useState } from 'react';
+import { Box, config, Line, Text } from 'folds';
+import { useSyncState } from '../../hooks/useSyncState';
+import { ContainerColor } from '../../styles/ContainerColor.css';
+
+type StateData = {
+ current: SyncState | null;
+ previous: SyncState | null | undefined;
+};
+
+type SyncStatusProps = {
+ mx: MatrixClient;
+};
+export function SyncStatus({ mx }: SyncStatusProps) {
+ const [stateData, setStateData] = useState<StateData>({
+ current: null,
+ previous: undefined,
+ });
+
+ useSyncState(
+ mx,
+ useCallback((current, previous) => {
+ setStateData((s) => {
+ if (s.current === current && s.previous === previous) {
+ return s;
+ }
+ return { current, previous };
+ });
+ }, [])
+ );
+
+ if (
+ (stateData.current === SyncState.Prepared ||
+ stateData.current === SyncState.Syncing ||
+ stateData.current === SyncState.Catchup) &&
+ stateData.previous !== SyncState.Syncing
+ ) {
+ return (
+ <Box direction="Column" shrink="No">
+ <Box
+ className={ContainerColor({ variant: 'Success' })}
+ style={{ padding: `${config.space.S100} 0` }}
+ alignItems="Center"
+ justifyContent="Center"
+ >
+ <Text size="L400">Connecting...</Text>
+ </Box>
+ <Line variant="Success" size="300" />
+ </Box>
+ );
+ }
+
+ if (stateData.current === SyncState.Reconnecting) {
+ return (
+ <Box direction="Column" shrink="No">
+ <Box
+ className={ContainerColor({ variant: 'Warning' })}
+ style={{ padding: `${config.space.S100} 0` }}
+ alignItems="Center"
+ justifyContent="Center"
+ >
+ <Text size="L400">Connection Lost! Reconnecting...</Text>
+ </Box>
+ <Line variant="Warning" size="300" />
+ </Box>
+ );
+ }
+
+ if (stateData.current === SyncState.Error) {
+ return (
+ <Box direction="Column" shrink="No">
+ <Box
+ className={ContainerColor({ variant: 'Critical' })}
+ style={{ padding: `${config.space.S100} 0` }}
+ alignItems="Center"
+ justifyContent="Center"
+ >
+ <Text size="L400">Connection Lost!</Text>
+ </Box>
+ <Line variant="Critical" size="300" />
+ </Box>
+ );
+ }
+
+ return null;
+}
title="Welcome to Cinny"
subTitle={
<span>
- Yet anothor matrix client.{' '}
+ Yet another matrix client.{' '}
<a
href="https://github.com/cinnyapp/cinny/releases"
target="_blank"
requestClose: () => void;
};
const DirectMenu = forwardRef<HTMLDivElement, DirectMenuProps>(({ 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();
};
const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ 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();
};
onOpen(room.roomId, eventId);
};
const handleMarkAsRead = () => {
- markAsRead(room.roomId);
+ markAsRead(mx, room.roomId);
};
return (
const DirectMenu = forwardRef<HTMLDivElement, DirectMenuProps>(({ 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();
};
const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ 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();
};
const unread = useRoomsUnread(allChild, roomToUnreadAtom);
const handleMarkAsRead = () => {
- allChild.forEach((childRoomId) => markAsRead(childRoomId));
+ allChild.forEach((childRoomId) => markAsRead(mx, childRoomId));
requestClose();
};
const unread = useRoomsUnread(allChild, roomToUnreadAtom);
const handleMarkAsRead = () => {
- allChild.forEach((childRoomId) => markAsRead(childRoomId));
+ allChild.forEach((childRoomId) => markAsRead(mx, childRoomId));
requestClose();
};
+++ /dev/null
-import initMatrix from '../initMatrix';
-
-// eslint-disable-next-line import/prefer-default-export
-export async function markAsRead(roomId) {
- const mx = initMatrix.matrixClient;
- const room = mx.getRoom(roomId);
- if (!room) return;
-
- const timeline = room.getLiveTimeline().getEvents();
- const readEventId = room.getEventReadUpTo(mx.getUserId());
-
- const getLatestValidEvent = () => {
- for (let i = timeline.length - 1; i >= 0; i -= 1) {
- const latestEvent = timeline[i];
- if (latestEvent.getId() === readEventId) return null;
- if (!latestEvent.isSending()) return latestEvent;
- }
- return null;
- };
- if (timeline.length === 0) return;
- const latestEvent = getLatestValidEvent();
- if (latestEvent === null) return;
-
- await mx.sendReadReceipt(latestEvent);
-}
--- /dev/null
+import { MatrixClient } from "matrix-js-sdk";
+
+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 getLatestValidEvent = () => {
+ for (let i = timeline.length - 1; i >= 0; i -= 1) {
+ const latestEvent = timeline[i];
+ if (latestEvent.getId() === readEventId) return null;
+ if (!latestEvent.isSending()) return latestEvent;
+ }
+ return null;
+ };
+ if (timeline.length === 0) return;
+ const latestEvent = getLatestValidEvent();
+ if (latestEvent === null) return;
+
+ await mx.sendReadReceipt(latestEvent);
+}
-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 = {};
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]];
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) {
}
}
-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) {
}
}
-async function createDM(userIdOrIds, isEncrypted = true) {
+async function createDM(mx, userIdOrIds, isEncrypted = true) {
const options = {
is_direct: true,
invite: Array.isArray(userIdOrIds) ? userIdOrIds : [userIdOrIds],
});
}
- 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;
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,
});
}
- const result = await create(options);
+ const result = await create(mx, options);
if (parentId) {
await mx.sendStateEvent(parentId, 'm.space.child', {
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];
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();
}, 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();
convertToRoom,
join,
createDM, createRoom,
- invite, kick, ban, unban,
ignore, unignore,
setPowerLevel,
setMyRoomNick, setMyRoomAvatar,
+++ /dev/null
-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;
--- /dev/null
+import { createClient, MatrixClient, IndexedDBStore, IndexedDBCryptoStore } from 'matrix-js-sdk';
+import Olm from '@matrix-org/olm';
+import { logger } from 'matrix-js-sdk/lib/logger';
+
+import { cryptoCallbacks } from './state/secretStorageKeys';
+
+global.Olm = Olm;
+
+if (import.meta.env.PROD) {
+ logger.disableAll();
+}
+
+type Session = {
+ baseUrl: string;
+ accessToken: string;
+ userId: string;
+ deviceId: string;
+};
+
+export const initClient = async (session: Session): Promise<MatrixClient> => {
+ const indexedDBStore = new IndexedDBStore({
+ indexedDB: global.indexedDB,
+ localStorage: global.localStorage,
+ dbName: 'web-sync-store',
+ });
+ await indexedDBStore.startup();
+
+ const mx = createClient({
+ baseUrl: session.baseUrl,
+ accessToken: session.accessToken,
+ userId: session.userId,
+ store: indexedDBStore,
+ cryptoStore: new IndexedDBCryptoStore(global.indexedDB, 'crypto-store'),
+ deviceId: session.deviceId,
+ timelineSupport: true,
+ cryptoCallbacks: cryptoCallbacks as any,
+ verificationMethods: ['m.sas.v1'],
+ });
+
+ await mx.initCrypto();
+
+ mx.setGlobalErrorOnUnknownDevices(false);
+ mx.setMaxListeners(50);
+
+ return mx;
+};
+
+export const startClient = async (mx: MatrixClient) => {
+ await mx.startClient({
+ lazyLoadMembers: true,
+ });
+};
+
+export const clearCacheAndReload = async (mx: MatrixClient) => {
+ mx.stopClient();
+ await mx.store.deleteAllData();
+ window.location.reload();
+};
+
+export const logoutClient = async (mx: MatrixClient) => {
+ mx.stopClient();
+ try {
+ await mx.logout();
+ } catch {
+ // ignore if failed to logout
+ }
+ await mx.clearStores();
+ window.localStorage.clear();
+ window.location.reload();
+};
+++ /dev/null
-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!;
-};
#root {
width: 100%;
height: 100%;
+ display: flex;
+ flex-direction: column;
}
*,
-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';
}
}
-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;
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) {
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);
}
}
-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 {
}
}
-export function getSSKeyInfo(key) {
- const mx = initMatrix.matrixClient;
+export function getSSKeyInfo(mx, key) {
try {
return mx.getAccountData(`m.secret_storage.key.${key}`).getContent();
} catch {
}
}
-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)
-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;