Resolved merge conflict

This commit is contained in:
GigiaJ 2025-05-22 20:28:19 -05:00
commit cd0d4c9704
21 changed files with 481 additions and 119 deletions

80
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "cinny",
"version": "4.6.0",
"version": "4.7.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cinny",
"version": "4.6.0",
"version": "4.7.1",
"license": "AGPL-3.0-only",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
@ -45,7 +45,7 @@
"jotai": "2.6.0",
"linkify-react": "4.1.3",
"linkifyjs": "4.1.3",
"matrix-js-sdk": "35.0.0",
"matrix-js-sdk": "37.5.0",
"millify": "6.1.0",
"pdfjs-dist": "4.2.67",
"prismjs": "1.30.0",
@ -2263,17 +2263,19 @@
}
},
"node_modules/@matrix-org/matrix-sdk-crypto-wasm": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-11.1.0.tgz",
"integrity": "sha512-JPuO9RCVDklDjbFzMvZfQb7PuiFkLY72bniRSu81lRzkkrcbZtmKqBFMm9H4f2FSz+tHVkDnmsvn12I4sdJJ5A==",
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-14.1.0.tgz",
"integrity": "sha512-vcSxHJIr6lP0Fgo8jl0sTHg+OZxZn+skGjiyB62erfgw/R2QqJl0ZVSY8SRcbk9LtHo/ZGld1tnaOyjL2e3cLQ==",
"license": "Apache-2.0",
"engines": {
"node": ">= 10"
"node": ">= 18"
}
},
"node_modules/@matrix-org/olm": {
"version": "3.2.15",
"resolved": "https://registry.npmjs.org/@matrix-org/olm/-/olm-3.2.15.tgz",
"integrity": "sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q=="
"integrity": "sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q==",
"license": "Apache-2.0"
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
@ -4589,7 +4591,8 @@
"node_modules/@types/events": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz",
"integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g=="
"integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==",
"license": "MIT"
},
"node_modules/@types/file-saver": {
"version": "2.0.5",
@ -4678,7 +4681,8 @@
"node_modules/@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
"license": "MIT"
},
"node_modules/@types/sanitize-html": {
"version": "2.9.0",
@ -5088,7 +5092,8 @@
"node_modules/another-json": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz",
"integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg=="
"integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg==",
"license": "Apache-2.0"
},
"node_modules/ansi-regex": {
"version": "5.0.1",
@ -5438,9 +5443,10 @@
"devOptional": true
},
"node_modules/base-x": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.0.tgz",
"integrity": "sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ=="
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz",
"integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==",
"license": "MIT"
},
"node_modules/base64-js": {
"version": "1.5.1",
@ -5546,6 +5552,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz",
"integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==",
"license": "MIT",
"dependencies": {
"base-x": "^5.0.0"
}
@ -5848,6 +5855,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
@ -6999,6 +7007,7 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"license": "MIT",
"engines": {
"node": ">=0.8.x"
}
@ -8557,6 +8566,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
@ -8689,6 +8699,7 @@
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
"integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
},
@ -8764,21 +8775,23 @@
"node_modules/matrix-events-sdk": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz",
"integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA=="
"integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==",
"license": "Apache-2.0"
},
"node_modules/matrix-js-sdk": {
"version": "35.0.0",
"resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-35.0.0.tgz",
"integrity": "sha512-X8hIsd/8x1SC9vRr8DiNKQxmdrfRujtvEWPz8mY4FxVDJG8HEGDHvqUmaSy2jrtnOUn4oHzGQVLFO3DnhsSf8w==",
"version": "37.5.0",
"resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-37.5.0.tgz",
"integrity": "sha512-5tyuAi5hnKud1UkVq8Z2/3c22hWGELBZzErJPZkE6Hju2uGUfGtrIx6uj6puv0ZjvsUU3X6Qgm8vdReKO1PGig==",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/matrix-sdk-crypto-wasm": "^11.0.0",
"@matrix-org/matrix-sdk-crypto-wasm": "^14.0.1",
"@matrix-org/olm": "3.2.15",
"another-json": "^0.2.0",
"bs58": "^6.0.0",
"content-type": "^1.0.4",
"jwt-decode": "^4.0.0",
"loglevel": "^1.7.1",
"loglevel": "^1.9.2",
"matrix-events-sdk": "0.0.1",
"matrix-widget-api": "^1.10.0",
"oidc-client-ts": "^3.0.1",
@ -8792,21 +8805,23 @@
}
},
"node_modules/matrix-js-sdk/node_modules/uuid": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz",
"integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==",
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/matrix-widget-api": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.12.0.tgz",
"integrity": "sha512-6JRd9fJGGvuBRhcTg9wX+Skn/Q1wox3jdp5yYQKJ6pPw4urW9bkTR90APBKVDB1vorJKT44jml+lCzkDMRBjww==",
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.13.1.tgz",
"integrity": "sha512-mkOHUVzaN018TCbObfGOSaMW2GoUxOfcxNNlTVx5/HeMk3OSQPQM0C9oEME5Liiv/dBUoSrEB64V8wF7e/gb1w==",
"license": "Apache-2.0",
"dependencies": {
"@types/events": "^3.0.0",
"events": "^3.2.0"
@ -9198,9 +9213,10 @@
}
},
"node_modules/oidc-client-ts": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.1.0.tgz",
"integrity": "sha512-IDopEXjiwjkmJLYZo6BTlvwOtnlSniWZkKZoXforC/oLZHC9wkIxd25Kwtmo5yKFMMVcsp3JY6bhcNJqdYk8+g==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.2.1.tgz",
"integrity": "sha512-hS5AJ5s/x4bXhHvNJT1v+GGvzHUwdRWqNQQbSrp10L1IRmzfRGKQ3VWN3dstJb+oF3WtAyKezwD2+dTEIyBiAA==",
"license": "Apache-2.0",
"dependencies": {
"jwt-decode": "^4.0.0"
},
@ -9288,6 +9304,7 @@
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
"license": "MIT",
"dependencies": {
"@types/retry": "0.12.0",
"retry": "^0.13.1"
@ -10051,6 +10068,7 @@
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
@ -10264,6 +10282,7 @@
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.15.0.tgz",
"integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==",
"license": "MIT",
"bin": {
"sdp-verify": "checker.js"
}
@ -11172,7 +11191,8 @@
"node_modules/unhomoglyph": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz",
"integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg=="
"integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==",
"license": "MIT"
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.1",

View file

@ -1,6 +1,6 @@
{
"name": "cinny",
"version": "4.6.0",
"version": "4.7.1",
"description": "Yet another matrix client",
"main": "index.js",
"type": "module",
@ -57,8 +57,8 @@
"jotai": "2.6.0",
"linkify-react": "4.1.3",
"linkifyjs": "4.1.3",
"matrix-js-sdk": "35.0.0",
"matrix-widget-api": "1.11.0",
"matrix-js-sdk": "37.5.0",
"millify": "6.1.0",
"pdfjs-dist": "4.2.67",
"prismjs": "1.30.0",

View file

@ -17,12 +17,16 @@ import { JoinRule } from 'matrix-js-sdk';
import FocusTrap from 'focus-trap-react';
import { stopPropagation } from '../utils/keyboard';
type JoinRuleIcons = Record<JoinRule, IconSrc>;
export type ExtraJoinRules = 'knock_restricted';
export type ExtendedJoinRules = JoinRule | ExtraJoinRules;
type JoinRuleIcons = Record<ExtendedJoinRules, IconSrc>;
export const useRoomJoinRuleIcon = (): JoinRuleIcons =>
useMemo(
() => ({
[JoinRule.Invite]: Icons.HashLock,
[JoinRule.Knock]: Icons.HashLock,
knock_restricted: Icons.Hash,
[JoinRule.Restricted]: Icons.Hash,
[JoinRule.Public]: Icons.HashGlobe,
[JoinRule.Private]: Icons.HashLock,
@ -34,6 +38,7 @@ export const useSpaceJoinRuleIcon = (): JoinRuleIcons =>
() => ({
[JoinRule.Invite]: Icons.SpaceLock,
[JoinRule.Knock]: Icons.SpaceLock,
knock_restricted: Icons.Space,
[JoinRule.Restricted]: Icons.Space,
[JoinRule.Public]: Icons.SpaceGlobe,
[JoinRule.Private]: Icons.SpaceLock,
@ -41,12 +46,13 @@ export const useSpaceJoinRuleIcon = (): JoinRuleIcons =>
[]
);
type JoinRuleLabels = Record<JoinRule, string>;
type JoinRuleLabels = Record<ExtendedJoinRules, string>;
export const useRoomJoinRuleLabel = (): JoinRuleLabels =>
useMemo(
() => ({
[JoinRule.Invite]: 'Invite Only',
[JoinRule.Knock]: 'Knock & Invite',
knock_restricted: 'Space Members or Knock',
[JoinRule.Restricted]: 'Space Members',
[JoinRule.Public]: 'Public',
[JoinRule.Private]: 'Invite Only',
@ -54,7 +60,7 @@ export const useRoomJoinRuleLabel = (): JoinRuleLabels =>
[]
);
type JoinRulesSwitcherProps<T extends JoinRule[]> = {
type JoinRulesSwitcherProps<T extends ExtendedJoinRules[]> = {
icons: JoinRuleIcons;
labels: JoinRuleLabels;
rules: T;
@ -63,7 +69,7 @@ type JoinRulesSwitcherProps<T extends JoinRule[]> = {
disabled?: boolean;
changing?: boolean;
};
export function JoinRulesSwitcher<T extends JoinRule[]>({
export function JoinRulesSwitcher<T extends ExtendedJoinRules[]>({
icons,
labels,
rules,
@ -79,7 +85,7 @@ export function JoinRulesSwitcher<T extends JoinRule[]>({
};
const handleChange = useCallback(
(selectedRule: JoinRule) => {
(selectedRule: ExtendedJoinRules) => {
setCords(undefined);
onChange(selectedRule);
},
@ -131,7 +137,7 @@ export function JoinRulesSwitcher<T extends JoinRule[]>({
fill="Soft"
radii="300"
outlined
before={<Icon size="100" src={icons[value]} />}
before={<Icon size="100" src={icons[value] ?? icons[JoinRule.Restricted]} />}
after={
changing ? (
<Spinner size="100" variant="Secondary" fill="Soft" />
@ -142,7 +148,7 @@ export function JoinRulesSwitcher<T extends JoinRule[]>({
onClick={handleOpenMenu}
disabled={disabled}
>
<Text size="B300">{labels[value]}</Text>
<Text size="B300">{labels[value] ?? 'Unsupported'}</Text>
</Button>
</PopOut>
);

View file

@ -1,7 +1,6 @@
import React, { FormEventHandler, useCallback } from 'react';
import { Box, Text, Button, Spinner, color } from 'folds';
import { decodeRecoveryKey } from 'matrix-js-sdk/lib/crypto-api';
import { deriveKey } from 'matrix-js-sdk/lib/crypto/key_passphrase';
import { decodeRecoveryKey, deriveRecoveryKeyFromPassphrase } from 'matrix-js-sdk/lib/crypto-api';
import { PasswordInput } from './password-input';
import {
SecretStorageKeyContent,
@ -29,11 +28,16 @@ export function SecretStorageRecoveryPassphrase({
const [driveKeyState, submitPassphrase] = useAsyncCallback<
Uint8Array,
Error,
Parameters<typeof deriveKey>
Parameters<typeof deriveRecoveryKeyFromPassphrase>
>(
useCallback(
async (passphrase, salt, iterations, bits) => {
const decodedRecoveryKey = await deriveKey(passphrase, salt, iterations, bits);
const decodedRecoveryKey = await deriveRecoveryKeyFromPassphrase(
passphrase,
salt,
iterations,
bits
);
const match = await mx.secretStorage.checkKey(decodedRecoveryKey, keyContent as any);

View file

@ -30,6 +30,7 @@ import { stopPropagation } from '../../../utils/keyboard';
import { decryptFile, downloadEncryptedMedia, mxcUrlToHttp } from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { ModalWide } from '../../../styles/Modal.css';
import { validBlurHash } from '../../../utils/blurHash';
type RenderViewerProps = {
src: string;
@ -77,7 +78,7 @@ export const ImageContent = as<'div', ImageContentProps>(
) => {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const blurHash = info?.[MATRIX_BLUR_HASH_PROPERTY_NAME];
const blurHash = validBlurHash(info?.[MATRIX_BLUR_HASH_PROPERTY_NAME]);
const [load, setLoad] = useState(false);
const [error, setError] = useState(false);

View file

@ -31,6 +31,7 @@ import {
mxcUrlToHttp,
} from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { validBlurHash } from '../../../utils/blurHash';
type RenderVideoProps = {
title: string;
@ -68,7 +69,7 @@ export const VideoContent = as<'div', VideoContentProps>(
) => {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const blurHash = info.thumbnail_info?.[MATRIX_BLUR_HASH_PROPERTY_NAME];
const blurHash = validBlurHash(info.thumbnail_info?.[MATRIX_BLUR_HASH_PROPERTY_NAME]);
const [load, setLoad] = useState(false);
const [error, setError] = useState(false);

View file

@ -24,7 +24,7 @@ export const TextViewerContent = forwardRef<HTMLPreElement, TextViewerContentPro
>
<ErrorBoundary fallback={<code>{text}</code>}>
<Suspense fallback={<code>{text}</code>}>
<ReactPrism>{(codeRef) => <code ref={codeRef}>{text}</code>}</ReactPrism>
<ReactPrism key={text}>{(codeRef) => <code ref={codeRef}>{text}</code>}</ReactPrism>
</Suspense>
</ErrorBoundary>
</Text>

View file

@ -4,6 +4,7 @@ import { JoinRule, MatrixError, RestrictedAllowType } from 'matrix-js-sdk';
import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types';
import { IPowerLevels, powerLevelAPI } from '../../../hooks/usePowerLevels';
import {
ExtendedJoinRules,
JoinRulesSwitcher,
useRoomJoinRuleIcon,
useRoomJoinRuleLabel,
@ -32,6 +33,7 @@ export function RoomJoinRules({ powerLevels }: RoomJoinRulesProps) {
const mx = useMatrixClient();
const room = useRoom();
const roomVersion = parseInt(room.getVersion(), 10);
const allowKnockRestricted = roomVersion >= 10;
const allowRestricted = roomVersion >= 8;
const allowKnock = roomVersion >= 7;
const space = useSpaceOptionally();
@ -47,18 +49,21 @@ export function RoomJoinRules({ powerLevels }: RoomJoinRulesProps) {
const content = joinRuleEvent?.getContent<RoomJoinRulesEventContent>();
const rule: JoinRule = content?.join_rule ?? JoinRule.Invite;
const joinRules: Array<JoinRule> = useMemo(() => {
const r: JoinRule[] = [JoinRule.Invite];
const joinRules: Array<ExtendedJoinRules> = useMemo(() => {
const r: ExtendedJoinRules[] = [JoinRule.Invite];
if (allowKnock) {
r.push(JoinRule.Knock);
}
if (allowRestricted && space) {
r.push(JoinRule.Restricted);
}
if (allowKnockRestricted && space) {
r.push('knock_restricted');
}
r.push(JoinRule.Public);
return r;
}, [allowRestricted, allowKnock, space]);
}, [allowKnockRestricted, allowRestricted, allowKnock, space]);
const icons = useRoomJoinRuleIcon();
const spaceIcons = useSpaceJoinRuleIcon();
@ -66,9 +71,9 @@ export function RoomJoinRules({ powerLevels }: RoomJoinRulesProps) {
const [submitState, submit] = useAsyncCallback(
useCallback(
async (joinRule: JoinRule) => {
async (joinRule: ExtendedJoinRules) => {
const allow: RestrictedRoomAllowContent[] = [];
if (joinRule === JoinRule.Restricted) {
if (joinRule === JoinRule.Restricted || joinRule === 'knock_restricted') {
const parents = getStateEvents(room, StateEvent.SpaceParent).map((event) =>
event.getStateKey()
);
@ -82,7 +87,7 @@ export function RoomJoinRules({ powerLevels }: RoomJoinRulesProps) {
}
const c: RoomJoinRulesEventContent = {
join_rule: joinRule,
join_rule: joinRule as JoinRule,
};
if (allow.length > 0) c.allow = allow;
await mx.sendStateEvent(room.roomId, StateEvent.RoomJoinRules as any, c);

View file

@ -1,6 +1,6 @@
import React, { KeyboardEvent as ReactKeyboardEvent, useCallback, useEffect, useMemo } from 'react';
import { Editor } from 'slate';
import { Box, MenuItem, Text } from 'folds';
import { Box, config, MenuItem, Text } from 'folds';
import { Room } from 'matrix-js-sdk';
import { Command, useCommands } from '../../hooks/useCommands';
import {
@ -75,9 +75,6 @@ export function CommandAutocomplete({
headerContent={
<Box grow="Yes" direction="Row" gap="200" justifyContent="SpaceBetween">
<Text size="L400">Commands</Text>
<Text size="T200" priority="300" truncate>
Begin your message with command
</Text>
</Box>
}
requestClose={requestClose}
@ -87,17 +84,22 @@ export function CommandAutocomplete({
key={commandName}
as="button"
radii="300"
style={{ height: 'unset' }}
onKeyDown={(evt: ReactKeyboardEvent<HTMLButtonElement>) =>
onTabPress(evt, () => handleAutocomplete(commandName))
}
onClick={() => handleAutocomplete(commandName)}
>
<Box grow="Yes" direction="Row" gap="200" justifyContent="SpaceBetween">
<Box shrink="No">
<Text style={{ flexGrow: 1 }} size="B400" truncate>
{`/${commandName}`}
</Text>
</Box>
<Box
style={{ padding: `${config.space.S300} 0` }}
grow="Yes"
direction="Column"
gap="100"
justifyContent="SpaceBetween"
>
<Text style={{ flexGrow: 1 }} size="B400" truncate>
{`/${commandName}`}
</Text>
<Text truncate priority="300" size="T200">
{commands[commandName].description}
</Text>

View file

@ -1,34 +1,127 @@
import { MatrixClient, Room } from 'matrix-js-sdk';
import { Direction, IContextResponse, MatrixClient, Method, Room, RoomMember } from 'matrix-js-sdk';
import { RoomServerAclEventContent } from 'matrix-js-sdk/lib/types';
import { useMemo } from 'react';
import { getDMRoomFor, isRoomAlias, isRoomId, isUserId } from '../utils/matrix';
import {
getDMRoomFor,
isRoomAlias,
isRoomId,
isServerName,
isUserId,
rateLimitedActions,
} from '../utils/matrix';
import { hasDevices } from '../../util/matrixUtil';
import * as roomActions from '../../client/action/room';
import { useRoomNavigate } from './useRoomNavigate';
import { Membership, StateEvent } from '../../types/matrix/room';
import { getStateEvent } from '../utils/room';
import { splitWithSpace } from '../utils/common';
export const SHRUG = '¯\\_(ツ)_/¯';
export const TABLEFLIP = '(╯°□°)╯︵ ┻━┻';
export const UNFLIP = '┬─┬ノ( º_º)';
export function parseUsersAndReason(payload: string): {
users: string[];
reason?: string;
} {
let reason: string | undefined;
let ids: string = payload;
const FLAG_PAT = '(?:^|\\s)-(\\w+)\\b';
const FLAG_REG = new RegExp(FLAG_PAT);
const FLAG_REG_G = new RegExp(FLAG_PAT, 'g');
const reasonMatch = payload.match(/\s-r\s/);
if (reasonMatch) {
ids = payload.slice(0, reasonMatch.index);
reason = payload.slice((reasonMatch.index ?? 0) + reasonMatch[0].length);
if (reason.trim() === '') reason = undefined;
export const splitPayloadContentAndFlags = (payload: string): [string, string | undefined] => {
const flagMatch = payload.match(FLAG_REG);
if (!flagMatch) {
return [payload, undefined];
}
const rawIds = ids.split(' ');
const users = rawIds.filter((id) => isUserId(id));
return {
users,
reason,
};
}
const content = payload.slice(0, flagMatch.index);
const flags = payload.slice(flagMatch.index);
return [content, flags];
};
export const parseFlags = (flags: string | undefined): Record<string, string | undefined> => {
const result: Record<string, string> = {};
if (!flags) return result;
const matches: { key: string; index: number; match: string }[] = [];
for (let match = FLAG_REG_G.exec(flags); match !== null; match = FLAG_REG_G.exec(flags)) {
matches.push({ key: match[1], index: match.index, match: match[0] });
}
for (let i = 0; i < matches.length; i += 1) {
const { key, match } = matches[i];
const start = matches[i].index + match.length;
const end = i + 1 < matches.length ? matches[i + 1].index : flags.length;
const value = flags.slice(start, end).trim();
result[key] = value;
}
return result;
};
export const parseUsers = (payload: string): string[] => {
const users: string[] = [];
splitWithSpace(payload).forEach((item) => {
if (isUserId(item)) {
users.push(item);
}
});
return users;
};
export const parseServers = (payload: string): string[] => {
const servers: string[] = [];
splitWithSpace(payload).forEach((item) => {
if (isServerName(item)) {
servers.push(item);
}
});
return servers;
};
const getServerMembers = (room: Room, server: string): RoomMember[] => {
const members: RoomMember[] = room
.getMembers()
.filter((member) => member.userId.endsWith(`:${server}`));
return members;
};
export const parseTimestampFlag = (input: string): number | undefined => {
const match = input.match(/^(\d+(?:\.\d+)?)([dhms])$/); // supports floats like 1.5d
if (!match) {
return undefined;
}
const value = parseFloat(match[1]); // supports decimal values
const unit = match[2];
const now = Date.now(); // in milliseconds
let delta = 0;
switch (unit) {
case 'd':
delta = value * 24 * 60 * 60 * 1000;
break;
case 'h':
delta = value * 60 * 60 * 1000;
break;
case 'm':
delta = value * 60 * 1000;
break;
case 's':
delta = value * 1000;
break;
default:
return undefined;
}
const timestamp = now - delta;
return timestamp;
};
export type CommandExe = (payload: string) => Promise<void>;
@ -52,6 +145,8 @@ export enum Command {
ConvertToRoom = 'converttoroom',
TableFlip = 'tableflip',
UnFlip = 'unflip',
Delete = 'delete',
Acl = 'acl',
}
export type CommandContent = {
@ -96,7 +191,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
name: Command.StartDm,
description: 'Start direct message with user. Example: /startdm userId1',
exe: async (payload) => {
const rawIds = payload.split(' ');
const rawIds = splitWithSpace(payload);
const userIds = rawIds.filter((id) => isUserId(id) && id !== mx.getUserId());
if (userIds.length === 0) return;
if (userIds.length === 1) {
@ -106,7 +201,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
return;
}
}
const devices = await Promise.all(userIds.map(uid => hasDevices(mx, uid)));
const devices = await Promise.all(userIds.map((uid) => hasDevices(mx, uid)));
const isEncrypt = devices.every((hasDevice) => hasDevice);
const result = await roomActions.createDM(mx, userIds, isEncrypt);
navigateRoom(result.room_id);
@ -116,7 +211,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
name: Command.Join,
description: 'Join room with address. Example: /join address1 address2',
exe: async (payload) => {
const rawIds = payload.split(' ');
const rawIds = splitWithSpace(payload);
const roomIds = rawIds.filter(
(idOrAlias) => isRoomId(idOrAlias) || isRoomAlias(idOrAlias)
);
@ -131,7 +226,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
mx.leave(room.roomId);
return;
}
const rawIds = payload.split(' ');
const rawIds = splitWithSpace(payload);
const roomIds = rawIds.filter((id) => isRoomId(id));
roomIds.map((id) => mx.leave(id));
},
@ -140,7 +235,10 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
name: Command.Invite,
description: 'Invite user to room. Example: /invite userId1 userId2 [-r reason]',
exe: async (payload) => {
const { users, reason } = parseUsersAndReason(payload);
const [content, flags] = splitPayloadContentAndFlags(payload);
const users = parseUsers(content);
const flagToContent = parseFlags(flags);
const reason = flagToContent.r;
users.map((id) => mx.invite(room.roomId, id, reason));
},
},
@ -148,31 +246,64 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
name: Command.DisInvite,
description: 'Disinvite user to room. Example: /disinvite userId1 userId2 [-r reason]',
exe: async (payload) => {
const { users, reason } = parseUsersAndReason(payload);
const [content, flags] = splitPayloadContentAndFlags(payload);
const users = parseUsers(content);
const flagToContent = parseFlags(flags);
const reason = flagToContent.r;
users.map((id) => mx.kick(room.roomId, id, reason));
},
},
[Command.Kick]: {
name: Command.Kick,
description: 'Kick user from room. Example: /kick userId1 userId2 [-r reason]',
description: 'Kick user from room. Example: /kick userId1 userId2 servername [-r reason]',
exe: async (payload) => {
const { users, reason } = parseUsersAndReason(payload);
users.map((id) => mx.kick(room.roomId, id, reason));
const [content, flags] = splitPayloadContentAndFlags(payload);
const users = parseUsers(content);
const servers = parseServers(content);
const flagToContent = parseFlags(flags);
const reason = flagToContent.r;
const serverMembers = servers?.flatMap((server) => getServerMembers(room, server));
const serverUsers = serverMembers
?.filter((m) => m.membership !== Membership.Ban)
.map((m) => m.userId);
if (Array.isArray(serverUsers)) {
serverUsers.forEach((user) => {
if (!users.includes(user)) users.push(user);
});
}
rateLimitedActions(users, (id) => mx.kick(room.roomId, id, reason));
},
},
[Command.Ban]: {
name: Command.Ban,
description: 'Ban user from room. Example: /ban userId1 userId2 [-r reason]',
description: 'Ban user from room. Example: /ban userId1 userId2 servername [-r reason]',
exe: async (payload) => {
const { users, reason } = parseUsersAndReason(payload);
users.map((id) => mx.ban(room.roomId, id, reason));
const [content, flags] = splitPayloadContentAndFlags(payload);
const users = parseUsers(content);
const servers = parseServers(content);
const flagToContent = parseFlags(flags);
const reason = flagToContent.r;
const serverMembers = servers?.flatMap((server) => getServerMembers(room, server));
const serverUsers = serverMembers?.map((m) => m.userId);
if (Array.isArray(serverUsers)) {
serverUsers.forEach((user) => {
if (!users.includes(user)) users.push(user);
});
}
rateLimitedActions(users, (id) => mx.ban(room.roomId, id, reason));
},
},
[Command.UnBan]: {
name: Command.UnBan,
description: 'Unban user from room. Example: /unban userId1 userId2',
exe: async (payload) => {
const rawIds = payload.split(' ');
const rawIds = splitWithSpace(payload);
const users = rawIds.filter((id) => isUserId(id));
users.map((id) => mx.unban(room.roomId, id));
},
@ -181,7 +312,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
name: Command.Ignore,
description: 'Ignore user. Example: /ignore userId1 userId2',
exe: async (payload) => {
const rawIds = payload.split(' ');
const rawIds = splitWithSpace(payload);
const userIds = rawIds.filter((id) => isUserId(id));
if (userIds.length > 0) roomActions.ignore(mx, userIds);
},
@ -190,7 +321,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
name: Command.UnIgnore,
description: 'Unignore user. Example: /unignore userId1 userId2',
exe: async (payload) => {
const rawIds = payload.split(' ');
const rawIds = splitWithSpace(payload);
const userIds = rawIds.filter((id) => isUserId(id));
if (userIds.length > 0) roomActions.unignore(mx, userIds);
},
@ -227,6 +358,124 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
roomActions.convertToRoom(mx, room.roomId);
},
},
[Command.Delete]: {
name: Command.Delete,
description:
'Delete messages from users. Example: /delete userId1 servername -past 1d|2h|5m|30s [-t m.room.message] [-r spam]',
exe: async (payload) => {
const [content, flags] = splitPayloadContentAndFlags(payload);
const users = parseUsers(content);
const servers = parseServers(content);
const flagToContent = parseFlags(flags);
const reason = flagToContent.r;
const pastContent = flagToContent.past ?? '';
const msgTypeContent = flagToContent.t;
const messageTypes: string[] = msgTypeContent ? splitWithSpace(msgTypeContent) : [];
const ts = parseTimestampFlag(pastContent);
if (!ts) return;
const serverMembers = servers?.flatMap((server) => getServerMembers(room, server));
const serverUsers = serverMembers?.map((m) => m.userId);
if (Array.isArray(serverUsers)) {
serverUsers.forEach((user) => {
if (!users.includes(user)) users.push(user);
});
}
const result = await mx.timestampToEvent(room.roomId, ts, Direction.Forward);
const startEventId = result.event_id;
const path = `/rooms/${encodeURIComponent(room.roomId)}/context/${encodeURIComponent(
startEventId
)}`;
const eventContext = await mx.http.authedRequest<IContextResponse>(Method.Get, path, {
limit: 0,
});
let token: string | undefined = eventContext.start;
while (token) {
// eslint-disable-next-line no-await-in-loop
const response = await mx.createMessagesRequest(
room.roomId,
token,
20,
Direction.Forward,
undefined
);
const { end, chunk } = response;
// remove until the latest event;
token = end;
const eventsToDelete = chunk.filter(
(roomEvent) =>
(messageTypes.length > 0 ? messageTypes.includes(roomEvent.type) : true) &&
users.includes(roomEvent.sender) &&
roomEvent.unsigned?.redacted_because === undefined
);
const eventIds = eventsToDelete.map((roomEvent) => roomEvent.event_id);
// eslint-disable-next-line no-await-in-loop
await rateLimitedActions(eventIds, (eventId) =>
mx.redactEvent(room.roomId, eventId, undefined, { reason })
);
}
},
},
[Command.Acl]: {
name: Command.Acl,
description:
'Manage server access control list. Example /acl [-a servername1] [-d servername2] [-ra servername1] [-rd servername2]',
exe: async (payload) => {
const [, flags] = splitPayloadContentAndFlags(payload);
const flagToContent = parseFlags(flags);
const allowFlag = flagToContent.a;
const denyFlag = flagToContent.d;
const removeAllowFlag = flagToContent.ra;
const removeDenyFlag = flagToContent.rd;
const allowList = allowFlag ? splitWithSpace(allowFlag) : [];
const denyList = denyFlag ? splitWithSpace(denyFlag) : [];
const removeAllowList = removeAllowFlag ? splitWithSpace(removeAllowFlag) : [];
const removeDenyList = removeDenyFlag ? splitWithSpace(removeDenyFlag) : [];
const serverAcl = getStateEvent(
room,
StateEvent.RoomServerAcl
)?.getContent<RoomServerAclEventContent>();
const aclContent: RoomServerAclEventContent = {
allow: serverAcl?.allow ? [...serverAcl.allow] : [],
allow_ip_literals: serverAcl?.allow_ip_literals,
deny: serverAcl?.deny ? [...serverAcl.deny] : [],
};
allowList.forEach((servername) => {
if (!Array.isArray(aclContent.allow) || aclContent.allow.includes(servername)) return;
aclContent.allow.push(servername);
});
denyList.forEach((servername) => {
if (!Array.isArray(aclContent.deny) || aclContent.deny.includes(servername)) return;
aclContent.deny.push(servername);
});
aclContent.allow = aclContent.allow?.filter(
(servername) => !removeAllowList.includes(servername)
);
aclContent.deny = aclContent.deny?.filter(
(servername) => !removeDenyList.includes(servername)
);
aclContent.allow?.sort();
aclContent.deny?.sort();
await mx.sendStateEvent(room.roomId, StateEvent.RoomServerAcl as any, aclContent);
},
},
}),
[mx, room, navigateRoom]
);

View file

@ -1,7 +1,7 @@
import { useEffect, useCallback, useMemo } from 'react';
import { IMyDevice } from 'matrix-js-sdk';
import { useQuery } from '@tanstack/react-query';
import { CryptoEvent, CryptoEventHandlerMap } from 'matrix-js-sdk/lib/crypto';
import { CryptoEvent, CryptoEventHandlerMap } from 'matrix-js-sdk/lib/crypto-api';
import { useMatrixClient } from './useMatrixClient';
export const useDeviceListChange = (

View file

@ -15,7 +15,7 @@ export function AuthFooter() {
target="_blank"
rel="noreferrer"
>
v4.6.0
v4.7.1
</Text>
<Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer">
Twitter

View file

@ -73,7 +73,7 @@ export const login = async (
}
const mx = createClient({ baseUrl: url });
const [err, res] = await to<LoginResponse, MatrixError>(mx.login(data.type, data));
const [err, res] = await to<LoginResponse, MatrixError>(mx.loginRequest(data));
if (err) {
if (err.httpStatus === 400) {

View file

@ -24,7 +24,7 @@ export function WelcomePage() {
target="_blank"
rel="noreferrer noopener"
>
v4.6.0
v4.7.1
</a>
</span>
}

View file

@ -1,5 +1,5 @@
import { atom } from 'jotai';
import { ImportRoomKeyProgressData } from 'matrix-js-sdk/lib/crypto-api';
import { ImportRoomKeyProgressData, ImportRoomKeyStage } from 'matrix-js-sdk/lib/crypto-api';
export enum BackupProgressStatus {
Idle,
@ -39,22 +39,16 @@ export const backupRestoreProgressAtom = atom<
>(
(get) => get(baseBackupRestoreProgressAtom),
(get, set, progress) => {
if (progress.stage === 'fetch') {
if (progress.stage === ImportRoomKeyStage.Fetch) {
set(baseBackupRestoreProgressAtom, {
status: BackupProgressStatus.Fetching,
});
return;
}
if (progress.stage === 'load_keys') {
if (progress.stage === ImportRoomKeyStage.LoadKeys) {
const { total, successes, failures } = progress;
if (total === undefined || successes === undefined || failures === undefined) {
// Setting to idle as https://github.com/matrix-org/matrix-js-sdk/issues/4703
set(baseBackupRestoreProgressAtom, {
status: BackupProgressStatus.Idle,
});
return;
}
const downloaded = successes + failures;
if (downloaded === total) {
set(baseBackupRestoreProgressAtom, {

View file

@ -1,4 +1,4 @@
import { encode } from 'blurhash';
import { encode, isBlurhashValid } from 'blurhash';
export const encodeBlurHash = (
img: HTMLImageElement | HTMLVideoElement,
@ -17,3 +17,13 @@ export const encodeBlurHash = (
const data = context.getImageData(0, 0, canvas.width, canvas.height);
return encode(data.data, data.width, data.height, 4, 4);
};
export const validBlurHash = (hash?: string): string | undefined => {
if (typeof hash === 'string') {
const validity = isBlurhashValid(hash);
return validity.result ? hash : undefined;
}
return undefined;
};

View file

@ -125,3 +125,9 @@ export const suffixRename = (name: string, validator: (newName: string) => boole
};
export const replaceSpaceWithDash = (str: string): string => str.replace(/ /g, '-');
export const splitWithSpace = (content: string): string[] => {
const trimmedContent = content.trim();
if (trimmedContent === '') return [];
return trimmedContent.split(' ');
};

View file

@ -13,11 +13,16 @@ import {
UploadProgress,
UploadResponse,
} from 'matrix-js-sdk';
import to from 'await-to-js';
import { IImageInfo, IThumbnailContent, IVideoInfo } from '../../types/matrix/common';
import { AccountDataEvent } from '../../types/matrix/accountData';
import { getStateEvent } from './room';
import { StateEvent } from '../../types/matrix/room';
const DOMAIN_REGEX = /\b(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}\b/;
export const isServerName = (serverName: string): boolean => DOMAIN_REGEX.test(serverName);
export const matchMxId = (id: string): RegExpMatchArray | null => id.match(/^([@!$+#])(.+):(\S+)$/);
export const validMxId = (id: string): boolean => !!matchMxId(id);
@ -292,3 +297,35 @@ export const downloadEncryptedMedia = async (
return decryptedContent;
};
export const rateLimitedActions = async <T, R = void>(
data: T[],
callback: (item: T) => Promise<R>,
maxRetryCount?: number
) => {
let retryCount = 0;
const performAction = async (dataItem: T) => {
const [err] = await to<R, MatrixError>(callback(dataItem));
if (err?.httpStatus === 429) {
if (retryCount === maxRetryCount) {
return;
}
const waitMS = err.getRetryAfterMs() ?? 200;
await new Promise((resolve) => {
setTimeout(resolve, waitMS);
});
retryCount += 1;
await performAction(dataItem);
}
};
for (let i = 0; i < data.length; i += 1) {
const dataItem = data[i];
retryCount = 0;
// eslint-disable-next-line no-await-in-loop
await performAction(dataItem);
}
};

View file

@ -1,12 +1,7 @@
import { createClient, MatrixClient, IndexedDBStore, IndexedDBCryptoStore } from 'matrix-js-sdk';
import { logger } from 'matrix-js-sdk/lib/logger';
import { cryptoCallbacks } from './state/secretStorageKeys';
if (import.meta.env.PROD) {
logger.disableAll();
}
type Session = {
baseUrl: string;
accessToken: string;
@ -38,7 +33,6 @@ export const initClient = async (session: Session): Promise<MatrixClient> => {
await indexedDBStore.startup();
await mx.initRustCrypto();
mx.setGlobalErrorOnUnknownDevices(false);
mx.setMaxListeners(50);
return mx;

View file

@ -1,5 +1,5 @@
const cons = {
version: '4.6.0',
version: '4.7.1',
secretKey: {
ACCESS_TOKEN: 'cinny_access_token',
DEVICE_ID: 'cinny_device_id',

View file

@ -7,6 +7,8 @@ import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfil
import inject from '@rollup/plugin-inject';
import topLevelAwait from 'vite-plugin-top-level-await';
import { VitePWA } from 'vite-plugin-pwa';
import fs from 'fs';
import path from 'path';
import buildConfig from './build.config';
const copyFiles = {
@ -43,6 +45,32 @@ const copyFiles = {
],
};
function serverMatrixSdkCryptoWasm(wasmFilePath) {
return {
name: 'vite-plugin-serve-matrix-sdk-crypto-wasm',
configureServer(server) {
server.middlewares.use((req, res, next) => {
if (req.url === wasmFilePath) {
const resolvedPath = path.join(path.resolve(), "/node_modules/@matrix-org/matrix-sdk-crypto-wasm/pkg/matrix_sdk_crypto_wasm_bg.wasm");
if (fs.existsSync(resolvedPath)) {
res.setHeader('Content-Type', 'application/wasm');
res.setHeader('Cache-Control', 'no-cache');
const fileStream = fs.createReadStream(resolvedPath);
fileStream.pipe(res);
} else {
res.writeHead(404);
res.end('File not found');
}
} else {
next();
}
});
},
};
}
export default defineConfig({
appType: 'spa',
publicDir: false,
@ -50,8 +78,13 @@ export default defineConfig({
server: {
port: 8080,
host: true,
fs: {
// Allow serving files from one level up to the project root
allow: ['..'],
},
},
plugins: [
serverMatrixSdkCryptoWasm('/node_modules/.vite/deps/pkg/matrix_sdk_crypto_wasm_bg.wasm'),
topLevelAwait({
// The export name of top-level await promise for each chunk module
promiseExportName: '__tla',