diff --git a/package-lock.json b/package-lock.json index f85dd74d..5fd5b686 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 56595f39..4b198147 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/components/JoinRulesSwitcher.tsx b/src/app/components/JoinRulesSwitcher.tsx index e78c19ce..9507317a 100644 --- a/src/app/components/JoinRulesSwitcher.tsx +++ b/src/app/components/JoinRulesSwitcher.tsx @@ -17,12 +17,16 @@ import { JoinRule } from 'matrix-js-sdk'; import FocusTrap from 'focus-trap-react'; import { stopPropagation } from '../utils/keyboard'; -type JoinRuleIcons = Record; +export type ExtraJoinRules = 'knock_restricted'; +export type ExtendedJoinRules = JoinRule | ExtraJoinRules; + +type JoinRuleIcons = Record; 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; +type JoinRuleLabels = Record; 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 = { +type JoinRulesSwitcherProps = { icons: JoinRuleIcons; labels: JoinRuleLabels; rules: T; @@ -63,7 +69,7 @@ type JoinRulesSwitcherProps = { disabled?: boolean; changing?: boolean; }; -export function JoinRulesSwitcher({ +export function JoinRulesSwitcher({ icons, labels, rules, @@ -79,7 +85,7 @@ export function JoinRulesSwitcher({ }; const handleChange = useCallback( - (selectedRule: JoinRule) => { + (selectedRule: ExtendedJoinRules) => { setCords(undefined); onChange(selectedRule); }, @@ -131,7 +137,7 @@ export function JoinRulesSwitcher({ fill="Soft" radii="300" outlined - before={} + before={} after={ changing ? ( @@ -142,7 +148,7 @@ export function JoinRulesSwitcher({ onClick={handleOpenMenu} disabled={disabled} > - {labels[value]} + {labels[value] ?? 'Unsupported'} ); diff --git a/src/app/components/SecretStorage.tsx b/src/app/components/SecretStorage.tsx index 55d466d7..9d8628e5 100644 --- a/src/app/components/SecretStorage.tsx +++ b/src/app/components/SecretStorage.tsx @@ -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 + Parameters >( 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); diff --git a/src/app/components/message/content/ImageContent.tsx b/src/app/components/message/content/ImageContent.tsx index 69c7ade8..cc0c0c91 100644 --- a/src/app/components/message/content/ImageContent.tsx +++ b/src/app/components/message/content/ImageContent.tsx @@ -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); diff --git a/src/app/components/message/content/VideoContent.tsx b/src/app/components/message/content/VideoContent.tsx index f6ddbb5a..0505f204 100644 --- a/src/app/components/message/content/VideoContent.tsx +++ b/src/app/components/message/content/VideoContent.tsx @@ -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); diff --git a/src/app/components/text-viewer/TextViewer.tsx b/src/app/components/text-viewer/TextViewer.tsx index f39ef953..ec4ed0a5 100644 --- a/src/app/components/text-viewer/TextViewer.tsx +++ b/src/app/components/text-viewer/TextViewer.tsx @@ -24,7 +24,7 @@ export const TextViewerContent = forwardRef {text}}> {text}}> - {(codeRef) => {text}} + {(codeRef) => {text}} diff --git a/src/app/features/common-settings/general/RoomJoinRules.tsx b/src/app/features/common-settings/general/RoomJoinRules.tsx index 158ca25b..ebd4cad5 100644 --- a/src/app/features/common-settings/general/RoomJoinRules.tsx +++ b/src/app/features/common-settings/general/RoomJoinRules.tsx @@ -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(); const rule: JoinRule = content?.join_rule ?? JoinRule.Invite; - const joinRules: Array = useMemo(() => { - const r: JoinRule[] = [JoinRule.Invite]; + const joinRules: Array = 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); diff --git a/src/app/features/room/CommandAutocomplete.tsx b/src/app/features/room/CommandAutocomplete.tsx index 31903ac6..6b7ba56e 100644 --- a/src/app/features/room/CommandAutocomplete.tsx +++ b/src/app/features/room/CommandAutocomplete.tsx @@ -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={ Commands - - Begin your message with command - } requestClose={requestClose} @@ -87,17 +84,22 @@ export function CommandAutocomplete({ key={commandName} as="button" radii="300" + style={{ height: 'unset' }} onKeyDown={(evt: ReactKeyboardEvent) => onTabPress(evt, () => handleAutocomplete(commandName)) } onClick={() => handleAutocomplete(commandName)} > - - - - {`/${commandName}`} - - + + + {`/${commandName}`} + {commands[commandName].description} diff --git a/src/app/hooks/useCommands.ts b/src/app/hooks/useCommands.ts index bc7d2892..c95142e8 100644 --- a/src/app/hooks/useCommands.ts +++ b/src/app/hooks/useCommands.ts @@ -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 => { + const result: Record = {}; + 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; @@ -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(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(); + + 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] ); diff --git a/src/app/hooks/useDeviceList.ts b/src/app/hooks/useDeviceList.ts index 5586ae8d..f89cb3d9 100644 --- a/src/app/hooks/useDeviceList.ts +++ b/src/app/hooks/useDeviceList.ts @@ -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 = ( diff --git a/src/app/pages/auth/AuthFooter.tsx b/src/app/pages/auth/AuthFooter.tsx index 71891a8d..fbb6e01b 100644 --- a/src/app/pages/auth/AuthFooter.tsx +++ b/src/app/pages/auth/AuthFooter.tsx @@ -15,7 +15,7 @@ export function AuthFooter() { target="_blank" rel="noreferrer" > - v4.6.0 + v4.7.1 Twitter diff --git a/src/app/pages/auth/login/loginUtil.ts b/src/app/pages/auth/login/loginUtil.ts index 1e2248d9..7e1c7153 100644 --- a/src/app/pages/auth/login/loginUtil.ts +++ b/src/app/pages/auth/login/loginUtil.ts @@ -73,7 +73,7 @@ export const login = async ( } const mx = createClient({ baseUrl: url }); - const [err, res] = await to(mx.login(data.type, data)); + const [err, res] = await to(mx.loginRequest(data)); if (err) { if (err.httpStatus === 400) { diff --git a/src/app/pages/client/WelcomePage.tsx b/src/app/pages/client/WelcomePage.tsx index d2133adc..99d70647 100644 --- a/src/app/pages/client/WelcomePage.tsx +++ b/src/app/pages/client/WelcomePage.tsx @@ -24,7 +24,7 @@ export function WelcomePage() { target="_blank" rel="noreferrer noopener" > - v4.6.0 + v4.7.1 } diff --git a/src/app/state/backupRestore.ts b/src/app/state/backupRestore.ts index 2f86b4d5..ad14e5d7 100644 --- a/src/app/state/backupRestore.ts +++ b/src/app/state/backupRestore.ts @@ -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, { diff --git a/src/app/utils/blurHash.ts b/src/app/utils/blurHash.ts index 3fe1ade0..566f6d18 100644 --- a/src/app/utils/blurHash.ts +++ b/src/app/utils/blurHash.ts @@ -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; +}; diff --git a/src/app/utils/common.ts b/src/app/utils/common.ts index d230c6bb..34e1ecbf 100644 --- a/src/app/utils/common.ts +++ b/src/app/utils/common.ts @@ -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(' '); +}; diff --git a/src/app/utils/matrix.ts b/src/app/utils/matrix.ts index cd3c0862..75430c20 100644 --- a/src/app/utils/matrix.ts +++ b/src/app/utils/matrix.ts @@ -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 ( + data: T[], + callback: (item: T) => Promise, + maxRetryCount?: number +) => { + let retryCount = 0; + const performAction = async (dataItem: T) => { + const [err] = await to(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); + } +}; diff --git a/src/client/initMatrix.ts b/src/client/initMatrix.ts index 7c774cf1..b513e27c 100644 --- a/src/client/initMatrix.ts +++ b/src/client/initMatrix.ts @@ -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 => { await indexedDBStore.startup(); await mx.initRustCrypto(); - mx.setGlobalErrorOnUnknownDevices(false); mx.setMaxListeners(50); return mx; diff --git a/src/client/state/cons.js b/src/client/state/cons.js index c79229b9..ae746f7a 100644 --- a/src/client/state/cons.js +++ b/src/client/state/cons.js @@ -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', diff --git a/vite.config.js b/vite.config.js index e5cb6232..5c1c68a2 100644 --- a/vite.config.js +++ b/vite.config.js @@ -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',