Add authenticated media support (#1930)

* chore: Bump matrix-js-sdk to 34.4.0

* feat: Authenticated media support

* chore: Use Vite PWA for service worker support

* fix: Fix Vite PWA SW entry point

Forget this. :P

* fix: Also add Nginx rewrite for sw.js

* fix: Correct Nginx rewrite

* fix: Add Netlify redirect for sw.js

Otherwise the generic SPA rewrite to index.html would take effect, breaking Service Worker.

* fix: Account for subpath when regisering service worker

* chore: Correct types
This commit is contained in:
夜坂雅 2024-09-07 21:45:55 +08:00 committed by GitHub
parent 043012e809
commit c6a8fb1117
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 3562 additions and 487 deletions

3
.npmrc
View file

@ -1,3 +1,2 @@
legacy-peer-deps=true legacy-peer-deps=true
save-exact=true save-exact=true
@matrix-org:registry=https://gitlab.matrix.org/api/v4/projects/27/packages/npm/

View file

@ -24,6 +24,7 @@ server {
rewrite ^/manifest.json$ /manifest.json break; rewrite ^/manifest.json$ /manifest.json break;
rewrite ^.*/olm.wasm$ /olm.wasm break; rewrite ^.*/olm.wasm$ /olm.wasm break;
rewrite ^/sw.js$ /sw.js break;
rewrite ^/pdf.worker.min.js$ /pdf.worker.min.js break; rewrite ^/pdf.worker.min.js$ /pdf.worker.min.js break;
rewrite ^/public/(.*)$ /public/$1 break; rewrite ^/public/(.*)$ /public/$1 break;

View file

@ -9,6 +9,7 @@ server {
rewrite ^/manifest.json$ /manifest.json break; rewrite ^/manifest.json$ /manifest.json break;
rewrite ^.*/olm.wasm$ /olm.wasm break; rewrite ^.*/olm.wasm$ /olm.wasm break;
rewrite ^/sw.js$ /sw.js break;
rewrite ^/pdf.worker.min.js$ /pdf.worker.min.js break; rewrite ^/pdf.worker.min.js$ /pdf.worker.min.js break;
rewrite ^/public/(.*)$ /public/$1 break; rewrite ^/public/(.*)$ /public/$1 break;

View file

@ -7,7 +7,12 @@
from = "/manifest.json" from = "/manifest.json"
to = "/manifest.json" to = "/manifest.json"
status = 200 status = 200
[[redirects]]
from = "/sw.js"
to = "/sw.js"
status = 200
[[redirects]] [[redirects]]
from = "*/olm.wasm" from = "*/olm.wasm"
to = "/olm.wasm" to = "/olm.wasm"

3365
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,7 @@
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0", "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3", "@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3",
"@fontsource/inter": "4.5.14", "@fontsource/inter": "4.5.14",
"@matrix-org/olm": "3.2.14", "@matrix-org/olm": "3.2.15",
"@tanstack/react-query": "5.24.1", "@tanstack/react-query": "5.24.1",
"@tanstack/react-query-devtools": "5.24.1", "@tanstack/react-query-devtools": "5.24.1",
"@tanstack/react-virtual": "3.2.0", "@tanstack/react-virtual": "3.2.0",
@ -56,7 +56,7 @@
"jotai": "2.6.0", "jotai": "2.6.0",
"linkify-react": "4.1.3", "linkify-react": "4.1.3",
"linkifyjs": "4.1.3", "linkifyjs": "4.1.3",
"matrix-js-sdk": "29.1.0", "matrix-js-sdk": "34.4.0",
"millify": "6.1.0", "millify": "6.1.0",
"pdfjs-dist": "4.2.67", "pdfjs-dist": "4.2.67",
"prismjs": "1.29.0", "prismjs": "1.29.0",
@ -90,6 +90,7 @@
"@types/react-dom": "18.2.17", "@types/react-dom": "18.2.17",
"@types/react-google-recaptcha": "2.1.8", "@types/react-google-recaptcha": "2.1.8",
"@types/sanitize-html": "2.9.0", "@types/sanitize-html": "2.9.0",
"@types/serviceworker": "0.0.95",
"@types/ua-parser-js": "0.7.36", "@types/ua-parser-js": "0.7.36",
"@typescript-eslint/eslint-plugin": "5.46.1", "@typescript-eslint/eslint-plugin": "5.46.1",
"@typescript-eslint/parser": "5.46.1", "@typescript-eslint/parser": "5.46.1",
@ -106,6 +107,7 @@
"sass": "1.56.2", "sass": "1.56.2",
"typescript": "4.9.4", "typescript": "4.9.4",
"vite": "5.0.13", "vite": "5.0.13",
"vite-plugin-pwa": "0.20.5",
"vite-plugin-static-copy": "1.0.4", "vite-plugin-static-copy": "1.0.4",
"vite-plugin-top-level-await": "1.4.1" "vite-plugin-top-level-await": "1.4.1"
} }

View file

@ -13,6 +13,8 @@ import { CommandElement, EmoticonElement, LinkElement, MentionElement } from './
import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMatrixClient } from '../../hooks/useMatrixClient';
import { getBeginCommand } from './utils'; import { getBeginCommand } from './utils';
import { BlockType } from './types'; import { BlockType } from './types';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useSpecVersions } from '../../hooks/useSpecVersions';
// Put this at the start and end of an inline component to work around this Chromium bug: // Put this at the start and end of an inline component to work around this Chromium bug:
// https://bugs.chromium.org/p/chromium/issues/detail?id=1249405 // https://bugs.chromium.org/p/chromium/issues/detail?id=1249405
@ -76,6 +78,8 @@ function RenderEmoticonElement({
children, children,
}: { element: EmoticonElement } & RenderElementProps) { }: { element: EmoticonElement } & RenderElementProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const selected = useSelected(); const selected = useSelected();
const focused = useFocused(); const focused = useFocused();
@ -90,7 +94,7 @@ function RenderEmoticonElement({
{element.key.startsWith('mxc://') ? ( {element.key.startsWith('mxc://') ? (
<img <img
className={css.EmoticonImg} className={css.EmoticonImg}
src={mx.mxcUrlToHttp(element.key) ?? element.key} src={mxcUrlToHttp(mx, element.key, useAuthentication) ?? element.key}
alt={element.shortcode} alt={element.shortcode}
/> />
) : ( ) : (

View file

@ -18,6 +18,8 @@ import { useRelevantImagePacks } from '../../../hooks/useImagePacks';
import { IEmoji, emojis } from '../../../plugins/emoji'; import { IEmoji, emojis } from '../../../plugins/emoji';
import { ExtendedPackImage, PackUsage } from '../../../plugins/custom-emoji'; import { ExtendedPackImage, PackUsage } from '../../../plugins/custom-emoji';
import { useKeyDown } from '../../../hooks/useKeyDown'; import { useKeyDown } from '../../../hooks/useKeyDown';
import { mxcUrlToHttp } from '../../../utils/matrix';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
type EmoticonCompleteHandler = (key: string, shortcode: string) => void; type EmoticonCompleteHandler = (key: string, shortcode: string) => void;
@ -48,6 +50,8 @@ export function EmoticonAutocomplete({
requestClose, requestClose,
}: EmoticonAutocompleteProps) { }: EmoticonAutocompleteProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const imagePacks = useRelevantImagePacks(mx, PackUsage.Emoticon, imagePackRooms); const imagePacks = useRelevantImagePacks(mx, PackUsage.Emoticon, imagePackRooms);
const recentEmoji = useRecentEmoji(mx, 20); const recentEmoji = useRecentEmoji(mx, 20);
@ -103,7 +107,7 @@ export function EmoticonAutocomplete({
<Box <Box
shrink="No" shrink="No"
as="img" as="img"
src={mx.mxcUrlToHttp(key) || key} src={mxcUrlToHttp(mx, key, useAuthentication) || key}
alt={emoticon.shortcode} alt={emoticon.shortcode}
style={{ width: toRem(24), height: toRem(24), objectFit: 'contain' }} style={{ width: toRem(24), height: toRem(24), objectFit: 'contain' }}
/> />

View file

@ -18,6 +18,7 @@ import { useKeyDown } from '../../../hooks/useKeyDown';
import { getMxIdLocalPart, getMxIdServer, validMxId } from '../../../utils/matrix'; import { getMxIdLocalPart, getMxIdServer, validMxId } from '../../../utils/matrix';
import { getMemberDisplayName, getMemberSearchStr } from '../../../utils/room'; import { getMemberDisplayName, getMemberSearchStr } from '../../../utils/room';
import { UserAvatar } from '../../user-avatar'; import { UserAvatar } from '../../user-avatar';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
type MentionAutoCompleteHandler = (userId: string, name: string) => void; type MentionAutoCompleteHandler = (userId: string, name: string) => void;
@ -84,6 +85,8 @@ export function UserMentionAutocomplete({
requestClose, requestClose,
}: UserMentionAutocompleteProps) { }: UserMentionAutocompleteProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const roomId: string = room.roomId!; const roomId: string = room.roomId!;
const roomAliasOrId = room.getCanonicalAlias() || roomId; const roomAliasOrId = room.getCanonicalAlias() || roomId;
const members = useRoomMembers(mx, roomId); const members = useRoomMembers(mx, roomId);
@ -143,7 +146,8 @@ export function UserMentionAutocomplete({
/> />
) : ( ) : (
autoCompleteMembers.map((roomMember) => { autoCompleteMembers.map((roomMember) => {
const avatarUrl = roomMember.getAvatarUrl(mx.baseUrl, 32, 32, 'crop', undefined, false); const avatarMxcUrl = roomMember.getMxcAvatarUrl();
const avatarUrl = avatarMxcUrl ? mx.mxcUrlToHttp(avatarMxcUrl, 32, 32, 'crop', undefined, false, useAuthentication) : undefined;
return ( return (
<MenuItem <MenuItem
key={roomMember.userId} key={roomMember.userId}

View file

@ -42,13 +42,14 @@ import { useRelevantImagePacks } from '../../hooks/useImagePacks';
import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRecentEmoji } from '../../hooks/useRecentEmoji'; import { useRecentEmoji } from '../../hooks/useRecentEmoji';
import { ExtendedPackImage, ImagePack, PackUsage } from '../../plugins/custom-emoji'; import { ExtendedPackImage, ImagePack, PackUsage } from '../../plugins/custom-emoji';
import { isUserId } from '../../utils/matrix'; import { isUserId, mxcUrlToHttp } from '../../utils/matrix';
import { editableActiveElement, isIntersectingScrollView, targetFromEvent } from '../../utils/dom'; import { editableActiveElement, isIntersectingScrollView, targetFromEvent } from '../../utils/dom';
import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch'; import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch';
import { useDebounce } from '../../hooks/useDebounce'; import { useDebounce } from '../../hooks/useDebounce';
import { useThrottle } from '../../hooks/useThrottle'; import { useThrottle } from '../../hooks/useThrottle';
import { addRecentEmoji } from '../../plugins/recent-emoji'; import { addRecentEmoji } from '../../plugins/recent-emoji';
import { mobileOrTablet } from '../../utils/user-agent'; import { mobileOrTablet } from '../../utils/user-agent';
import { useSpecVersions } from '../../hooks/useSpecVersions';
const RECENT_GROUP_ID = 'recent_group'; const RECENT_GROUP_ID = 'recent_group';
const SEARCH_GROUP_ID = 'search_group'; const SEARCH_GROUP_ID = 'search_group';
@ -354,11 +355,13 @@ function ImagePackSidebarStack({
packs, packs,
usage, usage,
onItemClick, onItemClick,
useAuthentication,
}: { }: {
mx: MatrixClient; mx: MatrixClient;
packs: ImagePack[]; packs: ImagePack[];
usage: PackUsage; usage: PackUsage;
onItemClick: (id: string) => void; onItemClick: (id: string) => void;
useAuthentication?: boolean;
}) { }) {
const activeGroupId = useAtomValue(activeGroupIdAtom); const activeGroupId = useAtomValue(activeGroupIdAtom);
return ( return (
@ -381,7 +384,7 @@ function ImagePackSidebarStack({
height: toRem(24), height: toRem(24),
objectFit: 'contain', objectFit: 'contain',
}} }}
src={mx.mxcUrlToHttp(pack.getPackAvatarUrl(usage) ?? '') || pack.avatarUrl} src={mxcUrlToHttp(mx, pack.getPackAvatarUrl(usage) ?? '', useAuthentication) || pack.avatarUrl}
alt={label || 'Unknown Pack'} alt={label || 'Unknown Pack'}
/> />
</SidebarBtn> </SidebarBtn>
@ -453,68 +456,70 @@ export function SearchEmojiGroup({
label, label,
id, id,
emojis: searchResult, emojis: searchResult,
useAuthentication,
}: { }: {
mx: MatrixClient; mx: MatrixClient;
tab: EmojiBoardTab; tab: EmojiBoardTab;
label: string; label: string;
id: string; id: string;
emojis: Array<ExtendedPackImage | IEmoji>; emojis: Array<ExtendedPackImage | IEmoji>;
useAuthentication?: boolean;
}) { }) {
return ( return (
<EmojiGroup key={id} id={id} label={label}> <EmojiGroup key={id} id={id} label={label}>
{tab === EmojiBoardTab.Emoji {tab === EmojiBoardTab.Emoji
? searchResult.map((emoji) => ? searchResult.map((emoji) =>
'unicode' in emoji ? ( 'unicode' in emoji ? (
<EmojiItem <EmojiItem
key={emoji.unicode} key={emoji.unicode}
label={emoji.label} label={emoji.label}
type={EmojiType.Emoji} type={EmojiType.Emoji}
data={emoji.unicode} data={emoji.unicode}
shortcode={emoji.shortcode} shortcode={emoji.shortcode}
> >
{emoji.unicode} {emoji.unicode}
</EmojiItem> </EmojiItem>
) : ( ) : (
<EmojiItem <EmojiItem
key={emoji.shortcode} key={emoji.shortcode}
label={emoji.body || emoji.shortcode} label={emoji.body || emoji.shortcode}
type={EmojiType.CustomEmoji} type={EmojiType.CustomEmoji}
data={emoji.url} data={emoji.url}
shortcode={emoji.shortcode} shortcode={emoji.shortcode}
> >
<img <img
loading="lazy" loading="lazy"
className={css.CustomEmojiImg} className={css.CustomEmojiImg}
alt={emoji.body || emoji.shortcode} alt={emoji.body || emoji.shortcode}
src={mx.mxcUrlToHttp(emoji.url) ?? emoji.url} src={mxcUrlToHttp(mx, emoji.url, useAuthentication) ?? emoji.url}
/> />
</EmojiItem> </EmojiItem>
)
) )
)
: searchResult.map((emoji) => : searchResult.map((emoji) =>
'unicode' in emoji ? null : ( 'unicode' in emoji ? null : (
<StickerItem <StickerItem
key={emoji.shortcode} key={emoji.shortcode}
label={emoji.body || emoji.shortcode} label={emoji.body || emoji.shortcode}
type={EmojiType.Sticker} type={EmojiType.Sticker}
data={emoji.url} data={emoji.url}
shortcode={emoji.shortcode} shortcode={emoji.shortcode}
> >
<img <img
loading="lazy" loading="lazy"
className={css.StickerImg} className={css.StickerImg}
alt={emoji.body || emoji.shortcode} alt={emoji.body || emoji.shortcode}
src={mx.mxcUrlToHttp(emoji.url) ?? emoji.url} src={mxcUrlToHttp(mx, emoji.url, useAuthentication) ?? emoji.url}
/> />
</StickerItem> </StickerItem>
) )
)} )}
</EmojiGroup> </EmojiGroup>
); );
} }
export const CustomEmojiGroups = memo( export const CustomEmojiGroups = memo(
({ mx, groups }: { mx: MatrixClient; groups: ImagePack[] }) => ( ({ mx, groups, useAuthentication }: { mx: MatrixClient; groups: ImagePack[]; useAuthentication?: boolean }) => (
<> <>
{groups.map((pack) => ( {groups.map((pack) => (
<EmojiGroup key={pack.id} id={pack.id} label={pack.displayName || 'Unknown'}> <EmojiGroup key={pack.id} id={pack.id} label={pack.displayName || 'Unknown'}>
@ -530,7 +535,7 @@ export const CustomEmojiGroups = memo(
loading="lazy" loading="lazy"
className={css.CustomEmojiImg} className={css.CustomEmojiImg}
alt={image.body || image.shortcode} alt={image.body || image.shortcode}
src={mx.mxcUrlToHttp(image.url) ?? image.url} src={mxcUrlToHttp(mx, image.url, useAuthentication) ?? image.url}
/> />
</EmojiItem> </EmojiItem>
))} ))}
@ -540,7 +545,7 @@ export const CustomEmojiGroups = memo(
) )
); );
export const StickerGroups = memo(({ mx, groups }: { mx: MatrixClient; groups: ImagePack[] }) => ( export const StickerGroups = memo(({ mx, groups, useAuthentication }: { mx: MatrixClient; groups: ImagePack[]; useAuthentication?: boolean }) => (
<> <>
{groups.length === 0 && ( {groups.length === 0 && (
<Box <Box
@ -573,7 +578,7 @@ export const StickerGroups = memo(({ mx, groups }: { mx: MatrixClient; groups: I
loading="lazy" loading="lazy"
className={css.StickerImg} className={css.StickerImg}
alt={image.body || image.shortcode} alt={image.body || image.shortcode}
src={mx.mxcUrlToHttp(image.url) ?? image.url} src={mxcUrlToHttp(mx, image.url, useAuthentication) ?? image.url}
/> />
</StickerItem> </StickerItem>
))} ))}
@ -645,6 +650,8 @@ export function EmojiBoard({
const setActiveGroupId = useSetAtom(activeGroupIdAtom); const setActiveGroupId = useSetAtom(activeGroupIdAtom);
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const emojiGroupLabels = useEmojiGroupLabels(); const emojiGroupLabels = useEmojiGroupLabels();
const emojiGroupIcons = useEmojiGroupIcons(); const emojiGroupIcons = useEmojiGroupIcons();
const imagePacks = useRelevantImagePacks(mx, usage, imagePackRooms); const imagePacks = useRelevantImagePacks(mx, usage, imagePackRooms);
@ -729,14 +736,14 @@ export function EmojiBoard({
} else if (emojiInfo.type === EmojiType.CustomEmoji && emojiPreviewRef.current) { } else if (emojiInfo.type === EmojiType.CustomEmoji && emojiPreviewRef.current) {
const img = document.createElement('img'); const img = document.createElement('img');
img.className = css.CustomEmojiImg; img.className = css.CustomEmojiImg;
img.setAttribute('src', mx.mxcUrlToHttp(emojiInfo.data) || emojiInfo.data); img.setAttribute('src', mxcUrlToHttp(mx, emojiInfo.data, useAuthentication) || emojiInfo.data);
img.setAttribute('alt', emojiInfo.shortcode); img.setAttribute('alt', emojiInfo.shortcode);
emojiPreviewRef.current.textContent = ''; emojiPreviewRef.current.textContent = '';
emojiPreviewRef.current.appendChild(img); emojiPreviewRef.current.appendChild(img);
} }
emojiPreviewTextRef.current.textContent = `:${emojiInfo.shortcode}:`; emojiPreviewTextRef.current.textContent = `:${emojiInfo.shortcode}:`;
}, },
[mx] [mx, useAuthentication]
); );
const throttleEmojiHover = useThrottle(handleEmojiPreview, { const throttleEmojiHover = useThrottle(handleEmojiPreview, {
@ -829,6 +836,7 @@ export function EmojiBoard({
usage={usage} usage={usage}
packs={imagePacks} packs={imagePacks}
onItemClick={handleScrollToGroup} onItemClick={handleScrollToGroup}
useAuthentication={useAuthentication}
/> />
)} )}
{emojiTab && ( {emojiTab && (
@ -890,13 +898,14 @@ export function EmojiBoard({
id={SEARCH_GROUP_ID} id={SEARCH_GROUP_ID}
label={result.items.length ? 'Search Results' : 'No Results found'} label={result.items.length ? 'Search Results' : 'No Results found'}
emojis={result.items} emojis={result.items}
useAuthentication={useAuthentication}
/> />
)} )}
{emojiTab && recentEmojis.length > 0 && ( {emojiTab && recentEmojis.length > 0 && (
<RecentEmojiGroup id={RECENT_GROUP_ID} label="Recent" emojis={recentEmojis} /> <RecentEmojiGroup id={RECENT_GROUP_ID} label="Recent" emojis={recentEmojis} />
)} )}
{emojiTab && <CustomEmojiGroups mx={mx} groups={imagePacks} />} {emojiTab && <CustomEmojiGroups mx={mx} groups={imagePacks} useAuthentication={useAuthentication} />}
{stickerTab && <StickerGroups mx={mx} groups={imagePacks} />} {stickerTab && <StickerGroups mx={mx} groups={imagePacks} useAuthentication={useAuthentication} />}
{emojiTab && <NativeEmojiGroups groups={emojiGroups} labels={emojiGroupLabels} />} {emojiTab && <NativeEmojiGroups groups={emojiGroups} labels={emojiGroupLabels} />}
</Box> </Box>
</Scroll> </Scroll>

View file

@ -21,6 +21,7 @@ import * as css from './EventReaders.css';
import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMatrixClient } from '../../hooks/useMatrixClient';
import { openProfileViewer } from '../../../client/action/navigation'; import { openProfileViewer } from '../../../client/action/navigation';
import { UserAvatar } from '../user-avatar'; import { UserAvatar } from '../user-avatar';
import { useSpecVersions } from '../../hooks/useSpecVersions';
export type EventReadersProps = { export type EventReadersProps = {
room: Room; room: Room;
@ -30,6 +31,8 @@ export type EventReadersProps = {
export const EventReaders = as<'div', EventReadersProps>( export const EventReaders = as<'div', EventReadersProps>(
({ className, room, eventId, requestClose, ...props }, ref) => { ({ className, room, eventId, requestClose, ...props }, ref) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const latestEventReaders = useRoomEventReaders(room, eventId); const latestEventReaders = useRoomEventReaders(room, eventId);
const getName = (userId: string) => const getName = (userId: string) =>
@ -55,9 +58,10 @@ export const EventReaders = as<'div', EventReadersProps>(
<Box className={css.Content} direction="Column"> <Box className={css.Content} direction="Column">
{latestEventReaders.map((readerId) => { {latestEventReaders.map((readerId) => {
const name = getName(readerId); const name = getName(readerId);
const avatarUrl = room const avatarMxcUrl = room
.getMember(readerId) .getMember(readerId)
?.getAvatarUrl(mx.baseUrl, 100, 100, 'crop', undefined, false); ?.getMxcAvatarUrl();
const avatarUrl = avatarMxcUrl ? mx.mxcUrlToHttp(avatarMxcUrl, 100, 100, 'crop', undefined, false, useAuthentication) : undefined;
return ( return (
<MenuItem <MenuItem

View file

@ -5,7 +5,7 @@ import { MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk';
import * as css from './Reaction.css'; import * as css from './Reaction.css';
import { getHexcodeForEmoji, getShortcodeFor } from '../../plugins/emoji'; import { getHexcodeForEmoji, getShortcodeFor } from '../../plugins/emoji';
import { getMemberDisplayName } from '../../utils/room'; import { getMemberDisplayName } from '../../utils/room';
import { eventWithShortcode, getMxIdLocalPart } from '../../utils/matrix'; import { eventWithShortcode, getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
export const Reaction = as< export const Reaction = as<
'button', 'button',
@ -13,8 +13,9 @@ export const Reaction = as<
mx: MatrixClient; mx: MatrixClient;
count: number; count: number;
reaction: string; reaction: string;
useAuthentication?: boolean;
} }
>(({ className, mx, count, reaction, ...props }, ref) => ( >(({ className, mx, count, reaction, useAuthentication, ...props }, ref) => (
<Box <Box
as="button" as="button"
className={classNames(css.Reaction, className)} className={classNames(css.Reaction, className)}
@ -28,7 +29,8 @@ export const Reaction = as<
{reaction.startsWith('mxc://') ? ( {reaction.startsWith('mxc://') ? (
<img <img
className={css.ReactionImg} className={css.ReactionImg}
src={mx.mxcUrlToHttp(reaction) ?? reaction} src={mxcUrlToHttp(mx, reaction, useAuthentication) ?? reaction
}
alt={reaction} alt={reaction}
/> />
) : ( ) : (

View file

@ -17,6 +17,8 @@ import {
} from '../../../hooks/media'; } from '../../../hooks/media';
import { useThrottle } from '../../../hooks/useThrottle'; import { useThrottle } from '../../../hooks/useThrottle';
import { secondsToMinutesAndSeconds } from '../../../utils/common'; import { secondsToMinutesAndSeconds } from '../../../utils/common';
import { mxcUrlToHttp } from '../../../utils/matrix';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
const PLAY_TIME_THROTTLE_OPS = { const PLAY_TIME_THROTTLE_OPS = {
wait: 500, wait: 500,
@ -44,11 +46,13 @@ export function AudioContent({
renderMediaControl, renderMediaControl,
}: AudioContentProps) { }: AudioContentProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const [srcState, loadSrc] = useAsyncCallback( const [srcState, loadSrc] = useAsyncCallback(
useCallback( useCallback(
() => getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo), () => getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo),
[mx, url, mimeType, encInfo] [mx, url, useAuthentication, mimeType, encInfo]
) )
); );

View file

@ -30,6 +30,8 @@ import {
} from '../../../utils/mimeTypes'; } from '../../../utils/mimeTypes';
import * as css from './style.css'; import * as css from './style.css';
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { mxcUrlToHttp } from '../../../utils/matrix';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
const renderErrorButton = (retry: () => void, text: string) => ( const renderErrorButton = (retry: () => void, text: string) => (
<TooltipProvider <TooltipProvider
@ -75,11 +77,13 @@ type ReadTextFileProps = {
}; };
export function ReadTextFile({ body, mimeType, url, encInfo, renderViewer }: ReadTextFileProps) { export function ReadTextFile({ body, mimeType, url, encInfo, renderViewer }: ReadTextFileProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const [textViewer, setTextViewer] = useState(false); const [textViewer, setTextViewer] = useState(false);
const loadSrc = useCallback( const loadSrc = useCallback(
() => getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo), () => getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo),
[mx, url, mimeType, encInfo] [mx, url, useAuthentication, mimeType, encInfo]
); );
const [textState, loadText] = useAsyncCallback( const [textState, loadText] = useAsyncCallback(
@ -166,14 +170,16 @@ export type ReadPdfFileProps = {
}; };
export function ReadPdfFile({ body, mimeType, url, encInfo, renderViewer }: ReadPdfFileProps) { export function ReadPdfFile({ body, mimeType, url, encInfo, renderViewer }: ReadPdfFileProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const [pdfViewer, setPdfViewer] = useState(false); const [pdfViewer, setPdfViewer] = useState(false);
const [pdfState, loadPdf] = useAsyncCallback( const [pdfState, loadPdf] = useAsyncCallback(
useCallback(async () => { useCallback(async () => {
const httpUrl = await getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo); const httpUrl = await getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo);
setPdfViewer(true); setPdfViewer(true);
return httpUrl; return httpUrl;
}, [mx, url, mimeType, encInfo]) }, [mx, url, useAuthentication, mimeType, encInfo])
); );
return ( return (
@ -240,13 +246,15 @@ export type DownloadFileProps = {
}; };
export function DownloadFile({ body, mimeType, url, info, encInfo }: DownloadFileProps) { export function DownloadFile({ body, mimeType, url, info, encInfo }: DownloadFileProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const [downloadState, download] = useAsyncCallback( const [downloadState, download] = useAsyncCallback(
useCallback(async () => { useCallback(async () => {
const httpUrl = await getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo); const httpUrl = await getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo);
FileSaver.saveAs(httpUrl, body); FileSaver.saveAs(httpUrl, body);
return httpUrl; return httpUrl;
}, [mx, url, mimeType, encInfo, body]) }, [mx, url, useAuthentication, mimeType, encInfo, body])
); );
return downloadState.status === AsyncStatus.Error ? ( return downloadState.status === AsyncStatus.Error ? (

View file

@ -27,6 +27,8 @@ import * as css from './style.css';
import { bytesToSize } from '../../../utils/common'; import { bytesToSize } from '../../../utils/common';
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes'; import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { mxcUrlToHttp } from '../../../utils/matrix';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
type RenderViewerProps = { type RenderViewerProps = {
src: string; src: string;
@ -69,6 +71,8 @@ export const ImageContent = as<'div', ImageContentProps>(
ref ref
) => { ) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const blurHash = info?.[MATRIX_BLUR_HASH_PROPERTY_NAME]; const blurHash = info?.[MATRIX_BLUR_HASH_PROPERTY_NAME];
const [load, setLoad] = useState(false); const [load, setLoad] = useState(false);
@ -77,8 +81,8 @@ export const ImageContent = as<'div', ImageContentProps>(
const [srcState, loadSrc] = useAsyncCallback( const [srcState, loadSrc] = useAsyncCallback(
useCallback( useCallback(
() => getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType || FALLBACK_MIMETYPE, encInfo), () => getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType || FALLBACK_MIMETYPE, encInfo),
[mx, url, mimeType, encInfo] [mx, url, useAuthentication, mimeType, encInfo]
) )
); );

View file

@ -3,6 +3,8 @@ import { IThumbnailContent } from '../../../../types/matrix/common';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { getFileSrcUrl } from './util'; import { getFileSrcUrl } from './util';
import { mxcUrlToHttp } from '../../../utils/matrix';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
export type ThumbnailContentProps = { export type ThumbnailContentProps = {
info: IThumbnailContent; info: IThumbnailContent;
@ -10,6 +12,8 @@ export type ThumbnailContentProps = {
}; };
export function ThumbnailContent({ info, renderImage }: ThumbnailContentProps) { export function ThumbnailContent({ info, renderImage }: ThumbnailContentProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const [thumbSrcState, loadThumbSrc] = useAsyncCallback( const [thumbSrcState, loadThumbSrc] = useAsyncCallback(
useCallback(() => { useCallback(() => {
@ -19,11 +23,11 @@ export function ThumbnailContent({ info, renderImage }: ThumbnailContentProps) {
throw new Error('Failed to load thumbnail'); throw new Error('Failed to load thumbnail');
} }
return getFileSrcUrl( return getFileSrcUrl(
mx.mxcUrlToHttp(thumbMxcUrl) ?? '', mxcUrlToHttp(mx, thumbMxcUrl, useAuthentication) ?? '',
thumbInfo.mimetype, thumbInfo.mimetype,
info.thumbnail_file info.thumbnail_file
); );
}, [mx, info]) }, [mx, info, useAuthentication])
); );
useEffect(() => { useEffect(() => {

View file

@ -25,6 +25,8 @@ import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { getFileSrcUrl } from './util'; import { getFileSrcUrl } from './util';
import { bytesToSize } from '../../../../util/common'; import { bytesToSize } from '../../../../util/common';
import { millisecondsToMinutesAndSeconds } from '../../../utils/common'; import { millisecondsToMinutesAndSeconds } from '../../../utils/common';
import { mxcUrlToHttp } from '../../../utils/matrix';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
type RenderVideoProps = { type RenderVideoProps = {
title: string; title: string;
@ -61,6 +63,8 @@ export const VideoContent = as<'div', VideoContentProps>(
ref ref
) => { ) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const blurHash = info.thumbnail_info?.[MATRIX_BLUR_HASH_PROPERTY_NAME]; const blurHash = info.thumbnail_info?.[MATRIX_BLUR_HASH_PROPERTY_NAME];
const [load, setLoad] = useState(false); const [load, setLoad] = useState(false);
@ -68,8 +72,8 @@ export const VideoContent = as<'div', VideoContentProps>(
const [srcState, loadSrc] = useAsyncCallback( const [srcState, loadSrc] = useAsyncCallback(
useCallback( useCallback(
() => getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo), () => getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo),
[mx, url, mimeType, encInfo] [mx, url, useAuthentication, mimeType, encInfo]
) )
); );

View file

@ -21,7 +21,7 @@ import classNames from 'classnames';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import * as css from './style.css'; import * as css from './style.css';
import { RoomAvatar } from '../room-avatar'; import { RoomAvatar } from '../room-avatar';
import { getMxIdLocalPart } from '../../utils/matrix'; import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
import { nameInitials } from '../../utils/common'; import { nameInitials } from '../../utils/common';
import { millify } from '../../plugins/millify'; import { millify } from '../../plugins/millify';
import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMatrixClient } from '../../hooks/useMatrixClient';
@ -32,6 +32,7 @@ import { useJoinedRoomId } from '../../hooks/useJoinedRoomId';
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver'; import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
import { getRoomAvatarUrl, getStateEvent } from '../../utils/room'; import { getRoomAvatarUrl, getStateEvent } from '../../utils/room';
import { useStateEventCallback } from '../../hooks/useStateEventCallback'; import { useStateEventCallback } from '../../hooks/useStateEventCallback';
import { useSpecVersions } from '../../hooks/useSpecVersions';
type GridColumnCount = '1' | '2' | '3'; type GridColumnCount = '1' | '2' | '3';
const getGridColumnCount = (gridWidth: number): GridColumnCount => { const getGridColumnCount = (gridWidth: number): GridColumnCount => {
@ -161,6 +162,8 @@ export const RoomCard = as<'div', RoomCardProps>(
ref ref
) => { ) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const joinedRoomId = useJoinedRoomId(allRooms, roomIdOrAlias); const joinedRoomId = useJoinedRoomId(allRooms, roomIdOrAlias);
const joinedRoom = mx.getRoom(joinedRoomId); const joinedRoom = mx.getRoom(joinedRoomId);
const [topicEvent, setTopicEvent] = useState(() => const [topicEvent, setTopicEvent] = useState(() =>
@ -171,8 +174,8 @@ export const RoomCard = as<'div', RoomCardProps>(
const fallbackTopic = roomIdOrAlias; const fallbackTopic = roomIdOrAlias;
const avatar = joinedRoom const avatar = joinedRoom
? getRoomAvatarUrl(mx, joinedRoom, 96) ? getRoomAvatarUrl(mx, joinedRoom, 96, useAuthentication)
: avatarUrl && mx.mxcUrlToHttp(avatarUrl, 96, 96, 'crop'); : avatarUrl && mxcUrlToHttp(mx, avatarUrl, useAuthentication, 96, 96, 'crop');
const roomName = joinedRoom?.name || name || fallbackName; const roomName = joinedRoom?.name || name || fallbackName;
const roomTopic = const roomTopic =

View file

@ -6,7 +6,7 @@ import { openInviteUser } from '../../../client/action/navigation';
import { IRoomCreateContent, Membership, StateEvent } from '../../../types/matrix/room'; import { IRoomCreateContent, Membership, StateEvent } from '../../../types/matrix/room';
import { getMemberDisplayName, getStateEvent } from '../../utils/room'; import { getMemberDisplayName, getStateEvent } from '../../utils/room';
import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMatrixClient } from '../../hooks/useMatrixClient';
import { getMxIdLocalPart } from '../../utils/matrix'; import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { timeDayMonthYear, timeHourMinute } from '../../utils/time'; import { timeDayMonthYear, timeHourMinute } from '../../utils/time';
import { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { useRoomNavigate } from '../../hooks/useRoomNavigate';
@ -14,6 +14,7 @@ import { RoomAvatar } from '../room-avatar';
import { nameInitials } from '../../utils/common'; import { nameInitials } from '../../utils/common';
import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta'; import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
import { mDirectAtom } from '../../state/mDirectList'; import { mDirectAtom } from '../../state/mDirectList';
import { useSpecVersions } from '../../hooks/useSpecVersions';
export type RoomIntroProps = { export type RoomIntroProps = {
room: Room; room: Room;
@ -21,6 +22,8 @@ export type RoomIntroProps = {
export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) => { export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const { navigateRoom } = useRoomNavigate(); const { navigateRoom } = useRoomNavigate();
const mDirects = useAtomValue(mDirectAtom); const mDirects = useAtomValue(mDirectAtom);
@ -28,7 +31,7 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) =>
const avatarMxc = useRoomAvatar(room, mDirects.has(room.roomId)); const avatarMxc = useRoomAvatar(room, mDirects.has(room.roomId));
const name = useRoomName(room); const name = useRoomName(room);
const topic = useRoomTopic(room); const topic = useRoomTopic(room);
const avatarHttpUrl = avatarMxc ? mx.mxcUrlToHttp(avatarMxc) : undefined; const avatarHttpUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication) : undefined;
const createContent = createEvent?.getContent<IRoomCreateContent>(); const createContent = createEvent?.getContent<IRoomCreateContent>();
const ts = createEvent?.getTs(); const ts = createEvent?.getTs();

View file

@ -10,12 +10,16 @@ import {
} from '../../hooks/useIntersectionObserver'; } from '../../hooks/useIntersectionObserver';
import * as css from './UrlPreviewCard.css'; import * as css from './UrlPreviewCard.css';
import { tryDecodeURIComponent } from '../../utils/dom'; import { tryDecodeURIComponent } from '../../utils/dom';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useSpecVersions } from '../../hooks/useSpecVersions';
const linkStyles = { color: color.Success.Main }; const linkStyles = { color: color.Success.Main };
export const UrlPreviewCard = as<'div', { url: string; ts: number }>( export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
({ url, ts, ...props }, ref) => { ({ url, ts, ...props }, ref) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const [previewStatus, loadPreview] = useAsyncCallback( const [previewStatus, loadPreview] = useAsyncCallback(
useCallback(() => mx.getUrlPreview(url, ts), [url, ts, mx]) useCallback(() => mx.getUrlPreview(url, ts), [url, ts, mx])
); );
@ -27,7 +31,7 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
if (previewStatus.status === AsyncStatus.Error) return null; if (previewStatus.status === AsyncStatus.Error) return null;
const renderContent = (prev: IPreviewUrlResponse) => { const renderContent = (prev: IPreviewUrlResponse) => {
const imgUrl = mx.mxcUrlToHttp(prev['og:image'] || '', 256, 256, 'scale', false); const imgUrl = mxcUrlToHttp(mx, prev['og:image'] || '', useAuthentication, 256, 256, 'scale', false);
return ( return (
<> <>

View file

@ -33,6 +33,8 @@ import { LeaveSpacePrompt } from '../../components/leave-space-prompt';
import { stopPropagation } from '../../utils/keyboard'; import { stopPropagation } from '../../utils/keyboard';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { BackRouteHandler } from '../../components/BackRouteHandler'; import { BackRouteHandler } from '../../components/BackRouteHandler';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useSpecVersions } from '../../hooks/useSpecVersions';
type LobbyMenuProps = { type LobbyMenuProps = {
roomId: string; roomId: string;
@ -122,6 +124,8 @@ type LobbyHeaderProps = {
}; };
export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) { export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const space = useSpace(); const space = useSpace();
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer'); const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
const [menuAnchor, setMenuAnchor] = useState<RectCords>(); const [menuAnchor, setMenuAnchor] = useState<RectCords>();
@ -129,7 +133,7 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
const name = useRoomName(space); const name = useRoomName(space);
const avatarMxc = useRoomAvatar(space); const avatarMxc = useRoomAvatar(space);
const avatarUrl = avatarMxc ? mx.mxcUrlToHttp(avatarMxc, 96, 96, 'crop') ?? undefined : undefined; const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined : undefined;
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => { const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
setMenuAnchor(evt.currentTarget.getBoundingClientRect()); setMenuAnchor(evt.currentTarget.getBoundingClientRect());

View file

@ -11,15 +11,19 @@ import { RoomTopicViewer } from '../../components/room-topic-viewer';
import * as css from './LobbyHero.css'; import * as css from './LobbyHero.css';
import { PageHero } from '../../components/page'; import { PageHero } from '../../components/page';
import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard'; import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useSpecVersions } from '../../hooks/useSpecVersions';
export function LobbyHero() { export function LobbyHero() {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const space = useSpace(); const space = useSpace();
const name = useRoomName(space); const name = useRoomName(space);
const topic = useRoomTopic(space); const topic = useRoomTopic(space);
const avatarMxc = useRoomAvatar(space); const avatarMxc = useRoomAvatar(space);
const avatarUrl = avatarMxc ? mx.mxcUrlToHttp(avatarMxc, 96, 96, 'crop') ?? undefined : undefined; const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined : undefined;
return ( return (
<PageHero <PageHero

View file

@ -39,6 +39,8 @@ import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { ErrorCode } from '../../cs-errorcode'; import { ErrorCode } from '../../cs-errorcode';
import { getDirectRoomAvatarUrl, getRoomAvatarUrl } from '../../utils/room'; import { getDirectRoomAvatarUrl, getRoomAvatarUrl } from '../../utils/room';
import { ItemDraggableTarget, useDraggableItem } from './DnD'; import { ItemDraggableTarget, useDraggableItem } from './DnD';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useSpecVersions } from '../../hooks/useSpecVersions';
type RoomJoinButtonProps = { type RoomJoinButtonProps = {
roomId: string; roomId: string;
@ -334,6 +336,8 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
ref ref
) => { ) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const { roomId, content } = item; const { roomId, content } = item;
const room = getRoom(roomId); const room = getRoom(roomId);
const targetRef = useRef<HTMLDivElement>(null); const targetRef = useRef<HTMLDivElement>(null);
@ -364,7 +368,7 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
name={localSummary.name} name={localSummary.name}
topic={localSummary.topic} topic={localSummary.topic}
avatarUrl={ avatarUrl={
dm ? getDirectRoomAvatarUrl(mx, room, 96) : getRoomAvatarUrl(mx, room, 96) dm ? getDirectRoomAvatarUrl(mx, room, 96, useAuthentication) : getRoomAvatarUrl(mx, room, 96, useAuthentication)
} }
memberCount={localSummary.memberCount} memberCount={localSummary.memberCount}
suggested={content.suggested} suggested={content.suggested}
@ -418,8 +422,8 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
topic={summaryState.data.topic} topic={summaryState.data.topic}
avatarUrl={ avatarUrl={
summaryState.data?.avatar_url summaryState.data?.avatar_url
? mx.mxcUrlToHttp(summaryState.data.avatar_url, 96, 96, 'crop') ?? ? mxcUrlToHttp(mx, summaryState.data.avatar_url, useAuthentication, 96, 96, 'crop') ??
undefined undefined
: undefined : undefined
} }
memberCount={summaryState.data.num_joined_members} memberCount={summaryState.data.num_joined_members}

View file

@ -35,6 +35,8 @@ import { ErrorCode } from '../../cs-errorcode';
import { useDraggableItem } from './DnD'; import { useDraggableItem } from './DnD';
import { openCreateRoom, openSpaceAddExisting } from '../../../client/action/navigation'; import { openCreateRoom, openSpaceAddExisting } from '../../../client/action/navigation';
import { stopPropagation } from '../../utils/keyboard'; import { stopPropagation } from '../../utils/keyboard';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useSpecVersions } from '../../hooks/useSpecVersions';
function SpaceProfileLoading() { function SpaceProfileLoading() {
return ( return (
@ -408,6 +410,8 @@ export const SpaceItemCard = as<'div', SpaceItemCardProps>(
ref ref
) => { ) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const { roomId, content } = item; const { roomId, content } = item;
const space = getRoom(roomId); const space = getRoom(roomId);
const targetRef = useRef<HTMLDivElement>(null); const targetRef = useRef<HTMLDivElement>(null);
@ -432,7 +436,7 @@ export const SpaceItemCard = as<'div', SpaceItemCardProps>(
<SpaceProfile <SpaceProfile
roomId={roomId} roomId={roomId}
name={localSummary.name} name={localSummary.name}
avatarUrl={getRoomAvatarUrl(mx, space, 96)} avatarUrl={getRoomAvatarUrl(mx, space, 96, useAuthentication)}
suggested={content.suggested} suggested={content.suggested}
closed={closed} closed={closed}
categoryId={categoryId} categoryId={categoryId}
@ -469,8 +473,8 @@ export const SpaceItemCard = as<'div', SpaceItemCardProps>(
name={summaryState.data.name || summaryState.data.canonical_alias || roomId} name={summaryState.data.name || summaryState.data.canonical_alias || roomId}
avatarUrl={ avatarUrl={
summaryState.data?.avatar_url summaryState.data?.avatar_url
? mx.mxcUrlToHttp(summaryState.data.avatar_url, 96, 96, 'crop') ?? ? mxcUrlToHttp(mx, summaryState.data.avatar_url, useAuthentication, 96, 96, 'crop') ??
undefined undefined
: undefined : undefined
} }
suggested={content.suggested} suggested={content.suggested}

View file

@ -13,7 +13,7 @@ import {
makeMentionCustomProps, makeMentionCustomProps,
renderMatrixMention, renderMatrixMention,
} from '../../plugins/react-custom-html-parser'; } from '../../plugins/react-custom-html-parser';
import { getMxIdLocalPart } from '../../utils/matrix'; import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
import { useMatrixEventRenderer } from '../../hooks/useMatrixEventRenderer'; import { useMatrixEventRenderer } from '../../hooks/useMatrixEventRenderer';
import { GetContentCallback, MessageEvent, StateEvent } from '../../../types/matrix/room'; import { GetContentCallback, MessageEvent, StateEvent } from '../../../types/matrix/room';
import { import {
@ -38,6 +38,7 @@ import { SequenceCard } from '../../components/sequence-card';
import { UserAvatar } from '../../components/user-avatar'; import { UserAvatar } from '../../components/user-avatar';
import { useMentionClickHandler } from '../../hooks/useMentionClickHandler'; import { useMentionClickHandler } from '../../hooks/useMentionClickHandler';
import { useSpoilerClickHandler } from '../../hooks/useSpoilerClickHandler'; import { useSpoilerClickHandler } from '../../hooks/useSpoilerClickHandler';
import { useSpecVersions } from '../../hooks/useSpecVersions';
type SearchResultGroupProps = { type SearchResultGroupProps = {
room: Room; room: Room;
@ -56,6 +57,8 @@ export function SearchResultGroup({
onOpen, onOpen,
}: SearchResultGroupProps) { }: SearchResultGroupProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const highlightRegex = useMemo(() => makeHighlightRegex(highlights), [highlights]); const highlightRegex = useMemo(() => makeHighlightRegex(highlights), [highlights]);
const mentionClickHandler = useMentionClickHandler(room.roomId); const mentionClickHandler = useMentionClickHandler(room.roomId);
@ -75,10 +78,11 @@ export function SearchResultGroup({
getReactCustomHtmlParser(mx, room.roomId, { getReactCustomHtmlParser(mx, room.roomId, {
linkifyOpts, linkifyOpts,
highlightRegex, highlightRegex,
useAuthentication,
handleSpoilerClick: spoilerClickHandler, handleSpoilerClick: spoilerClickHandler,
handleMentionClick: mentionClickHandler, handleMentionClick: mentionClickHandler,
}), }),
[mx, room, linkifyOpts, highlightRegex, mentionClickHandler, spoilerClickHandler] [mx, room, linkifyOpts, highlightRegex, mentionClickHandler, spoilerClickHandler, useAuthentication]
); );
const renderMatrixEvent = useMatrixEventRenderer<[IEventWithRoomId, string, GetContentCallback]>( const renderMatrixEvent = useMatrixEventRenderer<[IEventWithRoomId, string, GetContentCallback]>(
@ -161,7 +165,7 @@ export function SearchResultGroup({
<Avatar size="200" radii="300"> <Avatar size="200" radii="300">
<RoomAvatar <RoomAvatar
roomId={room.roomId} roomId={room.roomId}
src={getRoomAvatarUrl(mx, room, 96)} src={getRoomAvatarUrl(mx, room, 96, useAuthentication)}
alt={room.name} alt={room.name}
renderFallback={() => ( renderFallback={() => (
<RoomIcon size="50" joinRule={room.getJoinRule() ?? JoinRule.Restricted} filled /> <RoomIcon size="50" joinRule={room.getJoinRule() ?? JoinRule.Restricted} filled />
@ -209,7 +213,7 @@ export function SearchResultGroup({
userId={event.sender} userId={event.sender}
src={ src={
senderAvatarMxc senderAvatarMxc
? mx.mxcUrlToHttp(senderAvatarMxc, 48, 48, 'crop') ?? undefined ? mxcUrlToHttp(mx, senderAvatarMxc, useAuthentication, 48, 48, 'crop') ?? undefined
: undefined : undefined
} }
alt={displayName} alt={displayName}

View file

@ -38,6 +38,7 @@ import { stopPropagation } from '../../utils/keyboard';
import { getMatrixToRoom } from '../../plugins/matrix-to'; import { getMatrixToRoom } from '../../plugins/matrix-to';
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix'; import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix';
import { getViaServers } from '../../plugins/via-servers'; import { getViaServers } from '../../plugins/via-servers';
import { useSpecVersions } from '../../hooks/useSpecVersions';
type RoomNavItemMenuProps = { type RoomNavItemMenuProps = {
room: Room; room: Room;
@ -175,6 +176,8 @@ export function RoomNavItem({
linkPath, linkPath,
}: RoomNavItemProps) { }: RoomNavItemProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const [hover, setHover] = useState(false); const [hover, setHover] = useState(false);
const { hoverProps } = useHover({ onHoverChange: setHover }); const { hoverProps } = useHover({ onHoverChange: setHover });
const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover }); const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
@ -217,7 +220,7 @@ export function RoomNavItem({
<RoomAvatar <RoomAvatar
roomId={room.roomId} roomId={room.roomId}
src={ src={
direct ? getDirectRoomAvatarUrl(mx, room, 96) : getRoomAvatarUrl(mx, room, 96) direct ? getDirectRoomAvatarUrl(mx, room, 96, useAuthentication) : getRoomAvatarUrl(mx, room, 96, useAuthentication)
} }
alt={room.name} alt={room.name}
renderFallback={() => ( renderFallback={() => (

View file

@ -55,6 +55,7 @@ import { ScrollTopContainer } from '../../components/scroll-top-container';
import { UserAvatar } from '../../components/user-avatar'; import { UserAvatar } from '../../components/user-avatar';
import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers'; import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers';
import { stopPropagation } from '../../utils/keyboard'; import { stopPropagation } from '../../utils/keyboard';
import { useSpecVersions } from '../../hooks/useSpecVersions';
export const MembershipFilters = { export const MembershipFilters = {
filterJoined: (m: RoomMember) => m.membership === Membership.Join, filterJoined: (m: RoomMember) => m.membership === Membership.Join,
@ -171,6 +172,8 @@ type MembersDrawerProps = {
}; };
export function MembersDrawer({ room, members }: MembersDrawerProps) { export function MembersDrawer({ room, members }: MembersDrawerProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const searchInputRef = useRef<HTMLInputElement>(null); const searchInputRef = useRef<HTMLInputElement>(null);
const scrollTopAnchorRef = useRef<HTMLDivElement>(null); const scrollTopAnchorRef = useRef<HTMLDivElement>(null);
@ -426,9 +429,8 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
}} }}
after={<Icon size="50" src={Icons.Cross} />} after={<Icon size="50" src={Icons.Cross} />}
> >
<Text size="B300">{`${result.items.length || 'No'} ${ <Text size="B300">{`${result.items.length || 'No'} ${result.items.length === 1 ? 'Result' : 'Results'
result.items.length === 1 ? 'Result' : 'Results' }`}</Text>
}`}</Text>
</Chip> </Chip>
) )
} }
@ -483,14 +485,16 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
const member = tagOrMember; const member = tagOrMember;
const name = getName(member); const name = getName(member);
const avatarUrl = member.getAvatarUrl( const avatarMxcUrl = member.getMxcAvatarUrl();
mx.baseUrl, const avatarUrl = avatarMxcUrl ? mx.mxcUrlToHttp(
avatarMxcUrl,
100, 100,
100, 100,
'crop', 'crop',
undefined, undefined,
false false,
); useAuthentication
) : undefined;
return ( return (
<MenuItem <MenuItem

View file

@ -56,7 +56,7 @@ import {
} from '../../components/editor'; } from '../../components/editor';
import { EmojiBoard, EmojiBoardTab } from '../../components/emoji-board'; import { EmojiBoard, EmojiBoardTab } from '../../components/emoji-board';
import { UseStateProvider } from '../../components/UseStateProvider'; import { UseStateProvider } from '../../components/UseStateProvider';
import { TUploadContent, encryptFile, getImageInfo, getMxIdLocalPart } from '../../utils/matrix'; import { TUploadContent, encryptFile, getImageInfo, getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
import { useTypingStatusUpdater } from '../../hooks/useTypingStatusUpdater'; import { useTypingStatusUpdater } from '../../hooks/useTypingStatusUpdater';
import { useFilePicker } from '../../hooks/useFilePicker'; import { useFilePicker } from '../../hooks/useFilePicker';
import { useFilePasteHandler } from '../../hooks/useFilePasteHandler'; import { useFilePasteHandler } from '../../hooks/useFilePasteHandler';
@ -108,6 +108,7 @@ import { mobileOrTablet } from '../../utils/user-agent';
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver'; import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
import { ReplyLayout, ThreadIndicator } from '../../components/message'; import { ReplyLayout, ThreadIndicator } from '../../components/message';
import { roomToParentsAtom } from '../../state/room/roomToParents'; import { roomToParentsAtom } from '../../state/room/roomToParents';
import { useSpecVersions } from '../../hooks/useSpecVersions';
interface RoomInputProps { interface RoomInputProps {
editor: Editor; editor: Editor;
@ -118,6 +119,8 @@ interface RoomInputProps {
export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>( export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
({ editor, fileDropContainerRef, roomId, room }, ref) => { ({ editor, fileDropContainerRef, roomId, room }, ref) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline'); const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline');
const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown'); const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown');
const commands = useCommands(mx, room); const commands = useCommands(mx, room);
@ -366,7 +369,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
}; };
const handleStickerSelect = async (mxc: string, shortcode: string, label: string) => { const handleStickerSelect = async (mxc: string, shortcode: string, label: string) => {
const stickerUrl = mx.mxcUrlToHttp(mxc); const stickerUrl = mxcUrlToHttp(mx, mxc, useAuthentication);
if (!stickerUrl) return; if (!stickerUrl) return;
const info = await getImageInfo( const info = await getImageInfo(

View file

@ -122,6 +122,7 @@ import { roomToUnreadAtom } from '../../state/room/roomToUnread';
import { useMentionClickHandler } from '../../hooks/useMentionClickHandler'; import { useMentionClickHandler } from '../../hooks/useMentionClickHandler';
import { useSpoilerClickHandler } from '../../hooks/useSpoilerClickHandler'; import { useSpoilerClickHandler } from '../../hooks/useSpoilerClickHandler';
import { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { useSpecVersions } from '../../hooks/useSpecVersions';
const TimelineFloat = as<'div', css.TimelineFloatVariants>( const TimelineFloat = as<'div', css.TimelineFloatVariants>(
({ position, className, ...props }, ref) => ( ({ position, className, ...props }, ref) => (
@ -310,9 +311,9 @@ const useTimelinePagination = (
range: range:
offsetRange > 0 offsetRange > 0
? { ? {
start: currentTimeline.range.start + offsetRange, start: currentTimeline.range.start + offsetRange,
end: currentTimeline.range.end + offsetRange, end: currentTimeline.range.end + offsetRange,
} }
: { ...currentTimeline.range }, : { ...currentTimeline.range },
})); }));
}; };
@ -331,7 +332,7 @@ const useTimelinePagination = (
if ( if (
!paginationToken && !paginationToken &&
getTimelinesEventsCount(lTimelines) !== getTimelinesEventsCount(lTimelines) !==
getTimelinesEventsCount(getLinkedTimelines(timelineToPaginate)) getTimelinesEventsCount(getLinkedTimelines(timelineToPaginate))
) { ) {
recalibratePagination(lTimelines, timelinesEventsCount, backwards); recalibratePagination(lTimelines, timelinesEventsCount, backwards);
return; return;
@ -437,6 +438,8 @@ const getRoomUnreadInfo = (room: Room, scrollTo = false) => {
export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimelineProps) { export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimelineProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const encryptedRoom = mx.isRoomEncrypted(room.roomId); const encryptedRoom = mx.isRoomEncrypted(room.roomId);
const [messageLayout] = useSetting(settingsAtom, 'messageLayout'); const [messageLayout] = useSetting(settingsAtom, 'messageLayout');
const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing'); const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing');
@ -490,10 +493,10 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const [focusItem, setFocusItem] = useState< const [focusItem, setFocusItem] = useState<
| { | {
index: number; index: number;
scrollTo: boolean; scrollTo: boolean;
highlight: boolean; highlight: boolean;
} }
| undefined | undefined
>(); >();
const alive = useAlive(); const alive = useAlive();
@ -511,10 +514,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
() => () =>
getReactCustomHtmlParser(mx, room.roomId, { getReactCustomHtmlParser(mx, room.roomId, {
linkifyOpts, linkifyOpts,
useAuthentication,
handleSpoilerClick: spoilerClickHandler, handleSpoilerClick: spoilerClickHandler,
handleMentionClick: mentionClickHandler, handleMentionClick: mentionClickHandler,
}), }),
[mx, room, linkifyOpts, spoilerClickHandler, mentionClickHandler] [mx, room, linkifyOpts, spoilerClickHandler, mentionClickHandler, useAuthentication]
); );
const parseMemberEvent = useMemberEventParser(); const parseMemberEvent = useMemberEventParser();
@ -1466,14 +1470,14 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const eventJSX = reactionOrEditEvent(mEvent) const eventJSX = reactionOrEditEvent(mEvent)
? null ? null
: renderMatrixEvent( : renderMatrixEvent(
mEvent.getType(), mEvent.getType(),
typeof mEvent.getStateKey() === 'string', typeof mEvent.getStateKey() === 'string',
mEventId, mEventId,
mEvent, mEvent,
item, item,
timelineSet, timelineSet,
collapsed collapsed
); );
prevEvent = mEvent; prevEvent = mEvent;
isPrevRendered = !!eventJSX; isPrevRendered = !!eventJSX;
@ -1555,9 +1559,8 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
{!canPaginateBack && rangeAtStart && getItems().length > 0 && ( {!canPaginateBack && rangeAtStart && getItems().length > 0 && (
<div <div
style={{ style={{
padding: `${config.space.S700} ${config.space.S400} ${config.space.S600} ${ padding: `${config.space.S700} ${config.space.S400} ${config.space.S600} ${messageLayout === 1 ? config.space.S400 : toRem(64)
messageLayout === 1 ? config.space.S400 : toRem(64) }`,
}`,
}} }}
> >
<RoomIntro room={room} /> <RoomIntro room={room} />

View file

@ -36,7 +36,7 @@ import { useSetSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings'; import { settingsAtom } from '../../state/settings';
import { useSpaceOptionally } from '../../hooks/useSpace'; import { useSpaceOptionally } from '../../hooks/useSpace';
import { getHomeSearchPath, getSpaceSearchPath, withSearchParam } from '../../pages/pathUtils'; import { getHomeSearchPath, getSpaceSearchPath, withSearchParam } from '../../pages/pathUtils';
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix'; import { getCanonicalAliasOrRoomId, isRoomAlias, mxcUrlToHttp } from '../../utils/matrix';
import { _SearchPathSearchParams } from '../../pages/paths'; import { _SearchPathSearchParams } from '../../pages/paths';
import * as css from './RoomViewHeader.css'; import * as css from './RoomViewHeader.css';
import { useRoomUnread } from '../../state/hooks/unread'; import { useRoomUnread } from '../../state/hooks/unread';
@ -53,6 +53,7 @@ import { stopPropagation } from '../../utils/keyboard';
import { getMatrixToRoom } from '../../plugins/matrix-to'; import { getMatrixToRoom } from '../../plugins/matrix-to';
import { getViaServers } from '../../plugins/via-servers'; import { getViaServers } from '../../plugins/via-servers';
import { BackRouteHandler } from '../../components/BackRouteHandler'; import { BackRouteHandler } from '../../components/BackRouteHandler';
import { useSpecVersions } from '../../hooks/useSpecVersions';
type RoomMenuProps = { type RoomMenuProps = {
room: Room; room: Room;
@ -174,6 +175,8 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
export function RoomViewHeader() { export function RoomViewHeader() {
const navigate = useNavigate(); const navigate = useNavigate();
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
const room = useRoom(); const room = useRoom();
const space = useSpaceOptionally(); const space = useSpaceOptionally();
@ -185,7 +188,7 @@ export function RoomViewHeader() {
const avatarMxc = useRoomAvatar(room, mDirects.has(room.roomId)); const avatarMxc = useRoomAvatar(room, mDirects.has(room.roomId));
const name = useRoomName(room); const name = useRoomName(room);
const topic = useRoomTopic(room); const topic = useRoomTopic(room);
const avatarUrl = avatarMxc ? mx.mxcUrlToHttp(avatarMxc, 96, 96, 'crop') ?? undefined : undefined; const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined : undefined;
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer'); const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');

View file

@ -51,7 +51,7 @@ import {
getMemberAvatarMxc, getMemberAvatarMxc,
getMemberDisplayName, getMemberDisplayName,
} from '../../../utils/room'; } from '../../../utils/room';
import { getCanonicalAliasOrRoomId, getMxIdLocalPart, isRoomAlias } from '../../../utils/matrix'; import { getCanonicalAliasOrRoomId, getMxIdLocalPart, isRoomAlias, mxcUrlToHttp } from '../../../utils/matrix';
import { MessageLayout, MessageSpacing } from '../../../state/settings'; import { MessageLayout, MessageSpacing } from '../../../state/settings';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { useRecentEmoji } from '../../../hooks/useRecentEmoji'; import { useRecentEmoji } from '../../../hooks/useRecentEmoji';
@ -67,6 +67,7 @@ import { copyToClipboard } from '../../../utils/dom';
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { getMatrixToRoomEvent } from '../../../plugins/matrix-to'; import { getMatrixToRoomEvent } from '../../../plugins/matrix-to';
import { getViaServers } from '../../../plugins/via-servers'; import { getViaServers } from '../../../plugins/via-servers';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void; export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;
@ -234,9 +235,9 @@ export const MessageSourceCodeItem = as<
const getContent = (evt: MatrixEvent) => const getContent = (evt: MatrixEvent) =>
evt.isEncrypted() evt.isEncrypted()
? { ? {
[`<== DECRYPTED_EVENT ==>`]: evt.getEffectiveEvent(), [`<== DECRYPTED_EVENT ==>`]: evt.getEffectiveEvent(),
[`<== ORIGINAL_EVENT ==>`]: evt.event, [`<== ORIGINAL_EVENT ==>`]: evt.event,
} }
: evt.event; : evt.event;
const getText = (): string => { const getText = (): string => {
@ -650,6 +651,8 @@ export const Message = as<'div', MessageProps>(
ref ref
) => { ) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const senderId = mEvent.getSender() ?? ''; const senderId = mEvent.getSender() ?? '';
const [hover, setHover] = useState(false); const [hover, setHover] = useState(false);
const { hoverProps } = useHover({ onHoverChange: setHover }); const { hoverProps } = useHover({ onHoverChange: setHover });
@ -709,7 +712,7 @@ export const Message = as<'div', MessageProps>(
userId={senderId} userId={senderId}
src={ src={
senderAvatarMxc senderAvatarMxc
? mx.mxcUrlToHttp(senderAvatarMxc, 48, 48, 'crop') ?? undefined ? mxcUrlToHttp(mx, senderAvatarMxc, useAuthentication, 48, 48, 'crop') ?? undefined
: undefined : undefined
} }
alt={senderDisplayName} alt={senderDisplayName}
@ -950,26 +953,26 @@ export const Message = as<'div', MessageProps>(
</Box> </Box>
{((!mEvent.isRedacted() && canDelete) || {((!mEvent.isRedacted() && canDelete) ||
mEvent.getSender() !== mx.getUserId()) && ( mEvent.getSender() !== mx.getUserId()) && (
<> <>
<Line size="300" /> <Line size="300" />
<Box direction="Column" gap="100" className={css.MessageMenuGroup}> <Box direction="Column" gap="100" className={css.MessageMenuGroup}>
{!mEvent.isRedacted() && canDelete && ( {!mEvent.isRedacted() && canDelete && (
<MessageDeleteItem <MessageDeleteItem
room={room} room={room}
mEvent={mEvent} mEvent={mEvent}
onClose={closeMenu} onClose={closeMenu}
/> />
)} )}
{mEvent.getSender() !== mx.getUserId() && ( {mEvent.getSender() !== mx.getUserId() && (
<MessageReportItem <MessageReportItem
room={room} room={room}
mEvent={mEvent} mEvent={mEvent}
onClose={closeMenu} onClose={closeMenu}
/> />
)} )}
</Box> </Box>
</> </>
)} )}
</Menu> </Menu>
</FocusTrap> </FocusTrap>
} }
@ -1093,26 +1096,26 @@ export const Event = as<'div', EventProps>(
</Box> </Box>
{((!mEvent.isRedacted() && canDelete && !stateEvent) || {((!mEvent.isRedacted() && canDelete && !stateEvent) ||
(mEvent.getSender() !== mx.getUserId() && !stateEvent)) && ( (mEvent.getSender() !== mx.getUserId() && !stateEvent)) && (
<> <>
<Line size="300" /> <Line size="300" />
<Box direction="Column" gap="100" className={css.MessageMenuGroup}> <Box direction="Column" gap="100" className={css.MessageMenuGroup}>
{!mEvent.isRedacted() && canDelete && ( {!mEvent.isRedacted() && canDelete && (
<MessageDeleteItem <MessageDeleteItem
room={room} room={room}
mEvent={mEvent} mEvent={mEvent}
onClose={closeMenu} onClose={closeMenu}
/> />
)} )}
{mEvent.getSender() !== mx.getUserId() && ( {mEvent.getSender() !== mx.getUserId() && (
<MessageReportItem <MessageReportItem
room={room} room={room}
mEvent={mEvent} mEvent={mEvent}
onClose={closeMenu} onClose={closeMenu}
/> />
)} )}
</Box> </Box>
</> </>
)} )}
</Menu> </Menu>
</FocusTrap> </FocusTrap>
} }

View file

@ -22,6 +22,7 @@ import { useRelations } from '../../../hooks/useRelations';
import * as css from './styles.css'; import * as css from './styles.css';
import { ReactionViewer } from '../reaction-viewer'; import { ReactionViewer } from '../reaction-viewer';
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
export type ReactionsProps = { export type ReactionsProps = {
room: Room; room: Room;
@ -33,6 +34,8 @@ export type ReactionsProps = {
export const Reactions = as<'div', ReactionsProps>( export const Reactions = as<'div', ReactionsProps>(
({ className, room, relations, mEventId, canSendReaction, onReactionToggle, ...props }, ref) => { ({ className, room, relations, mEventId, canSendReaction, onReactionToggle, ...props }, ref) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const [viewer, setViewer] = useState<boolean | string>(false); const [viewer, setViewer] = useState<boolean | string>(false);
const myUserId = mx.getUserId(); const myUserId = mx.getUserId();
const reactions = useRelations( const reactions = useRelations(
@ -86,6 +89,7 @@ export const Reactions = as<'div', ReactionsProps>(
onClick={canSendReaction ? () => onReactionToggle(mEventId, key) : undefined} onClick={canSendReaction ? () => onReactionToggle(mEventId, key) : undefined}
onContextMenu={handleViewReaction} onContextMenu={handleViewReaction}
aria-disabled={!canSendReaction} aria-disabled={!canSendReaction}
useAuthentication={useAuthentication}
/> />
)} )}
</TooltipProvider> </TooltipProvider>

View file

@ -25,6 +25,7 @@ import { useRelations } from '../../../hooks/useRelations';
import { Reaction } from '../../../components/message'; import { Reaction } from '../../../components/message';
import { getHexcodeForEmoji, getShortcodeFor } from '../../../plugins/emoji'; import { getHexcodeForEmoji, getShortcodeFor } from '../../../plugins/emoji';
import { UserAvatar } from '../../../components/user-avatar'; import { UserAvatar } from '../../../components/user-avatar';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
export type ReactionViewerProps = { export type ReactionViewerProps = {
room: Room; room: Room;
@ -35,6 +36,8 @@ export type ReactionViewerProps = {
export const ReactionViewer = as<'div', ReactionViewerProps>( export const ReactionViewer = as<'div', ReactionViewerProps>(
({ className, room, initialKey, relations, requestClose, ...props }, ref) => { ({ className, room, initialKey, relations, requestClose, ...props }, ref) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const reactions = useRelations( const reactions = useRelations(
relations, relations,
useCallback((rel) => [...(rel.getSortedAnnotationsByKey() ?? [])], []) useCallback((rel) => [...(rel.getSortedAnnotationsByKey() ?? [])], [])
@ -81,6 +84,7 @@ export const ReactionViewer = as<'div', ReactionViewerProps>(
count={evts.size} count={evts.size}
aria-selected={key === selectedKey} aria-selected={key === selectedKey}
onClick={() => setSelectedKey(key)} onClick={() => setSelectedKey(key)}
useAuthentication={useAuthentication}
/> />
); );
})} })}
@ -107,14 +111,16 @@ export const ReactionViewer = as<'div', ReactionViewerProps>(
const member = room.getMember(senderId); const member = room.getMember(senderId);
const name = (member ? getName(member) : getMxIdLocalPart(senderId)) ?? senderId; const name = (member ? getName(member) : getMxIdLocalPart(senderId)) ?? senderId;
const avatarUrl = member?.getAvatarUrl( const avatarMxcUrl = member?.getMxcAvatarUrl();
mx.baseUrl, const avatarUrl = avatarMxcUrl ? mx.mxcUrlToHttp(
avatarMxcUrl,
100, 100,
100, 100,
'crop', 'crop',
undefined, undefined,
false false,
); useAuthentication
) : undefined;
return ( return (
<MenuItem <MenuItem

View file

@ -22,9 +22,10 @@ import {
isNotificationEvent, isNotificationEvent,
} from '../../utils/room'; } from '../../utils/room';
import { NotificationType, UnreadInfo } from '../../../types/matrix/room'; import { NotificationType, UnreadInfo } from '../../../types/matrix/room';
import { getMxIdLocalPart } from '../../utils/matrix'; import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
import { useSelectedRoom } from '../../hooks/router/useSelectedRoom'; import { useSelectedRoom } from '../../hooks/router/useSelectedRoom';
import { useInboxNotificationsSelected } from '../../hooks/router/useInbox'; import { useInboxNotificationsSelected } from '../../hooks/router/useInbox';
import { useSpecVersions } from '../../hooks/useSpecVersions';
function SystemEmojiFeature() { function SystemEmojiFeature() {
const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
@ -132,6 +133,8 @@ function MessageNotifications() {
const notifRef = useRef<Notification>(); const notifRef = useRef<Notification>();
const unreadCacheRef = useRef<Map<string, UnreadInfo>>(new Map()); const unreadCacheRef = useRef<Map<string, UnreadInfo>>(new Map());
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const [showNotifications] = useSetting(settingsAtom, 'showNotifications'); const [showNotifications] = useSetting(settingsAtom, 'showNotifications');
const [notificationSound] = useSetting(settingsAtom, 'isNotificationSounds'); const [notificationSound] = useSetting(settingsAtom, 'isNotificationSounds');
@ -216,7 +219,7 @@ function MessageNotifications() {
notify({ notify({
roomName: room.name ?? 'Unknown', roomName: room.name ?? 'Unknown',
roomAvatar: avatarMxc roomAvatar: avatarMxc
? mx.mxcUrlToHttp(avatarMxc, 96, 96, 'crop') ?? undefined ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined
: undefined, : undefined,
username: getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender) ?? sender, username: getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender) ?? sender,
roomId: room.roomId, roomId: room.roomId,

View file

@ -42,6 +42,7 @@ import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { useRoomTopic } from '../../../hooks/useRoomMeta'; import { useRoomTopic } from '../../../hooks/useRoomMeta';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler'; import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
const COMPACT_CARD_WIDTH = 548; const COMPACT_CARD_WIDTH = 548;
@ -54,6 +55,8 @@ type InviteCardProps = {
}; };
function InviteCard({ room, userId, direct, compact, onNavigate }: InviteCardProps) { function InviteCard({ room, userId, direct, compact, onNavigate }: InviteCardProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const roomName = room.name || room.getCanonicalAlias() || room.roomId; const roomName = room.name || room.getCanonicalAlias() || room.roomId;
const member = room.getMember(userId); const member = room.getMember(userId);
const memberEvent = member?.events.member; const memberEvent = member?.events.member;
@ -110,7 +113,7 @@ function InviteCard({ room, userId, direct, compact, onNavigate }: InviteCardPro
<Avatar size="300"> <Avatar size="300">
<RoomAvatar <RoomAvatar
roomId={room.roomId} roomId={room.roomId}
src={direct ? getDirectRoomAvatarUrl(mx, room, 96) : getRoomAvatarUrl(mx, room, 96)} src={direct ? getDirectRoomAvatarUrl(mx, room, 96, useAuthentication) : getRoomAvatarUrl(mx, room, 96, useAuthentication)}
alt={roomName} alt={roomName}
renderFallback={() => ( renderFallback={() => (
<Text as="span" size="H6"> <Text as="span" size="H6">

View file

@ -28,7 +28,7 @@ import { HTMLReactParserOptions } from 'html-react-parser';
import { Opts as LinkifyOpts } from 'linkifyjs'; import { Opts as LinkifyOpts } from 'linkifyjs';
import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page'; import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getMxIdLocalPart } from '../../../utils/matrix'; import { getMxIdLocalPart, mxcUrlToHttp } from '../../../utils/matrix';
import { InboxNotificationsPathSearchParams } from '../../paths'; import { InboxNotificationsPathSearchParams } from '../../paths';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
@ -81,6 +81,7 @@ import { useMentionClickHandler } from '../../../hooks/useMentionClickHandler';
import { useSpoilerClickHandler } from '../../../hooks/useSpoilerClickHandler'; import { useSpoilerClickHandler } from '../../../hooks/useSpoilerClickHandler';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler'; import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
type RoomNotificationsGroup = { type RoomNotificationsGroup = {
roomId: string; roomId: string;
@ -191,6 +192,8 @@ function RoomNotificationsGroupComp({
onOpen, onOpen,
}: RoomNotificationsGroupProps) { }: RoomNotificationsGroupProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const unread = useRoomUnread(room.roomId, roomToUnreadAtom); const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
const mentionClickHandler = useMentionClickHandler(room.roomId); const mentionClickHandler = useMentionClickHandler(room.roomId);
const spoilerClickHandler = useSpoilerClickHandler(); const spoilerClickHandler = useSpoilerClickHandler();
@ -208,10 +211,11 @@ function RoomNotificationsGroupComp({
() => () =>
getReactCustomHtmlParser(mx, room.roomId, { getReactCustomHtmlParser(mx, room.roomId, {
linkifyOpts, linkifyOpts,
useAuthentication,
handleSpoilerClick: spoilerClickHandler, handleSpoilerClick: spoilerClickHandler,
handleMentionClick: mentionClickHandler, handleMentionClick: mentionClickHandler,
}), }),
[mx, room, linkifyOpts, mentionClickHandler, spoilerClickHandler] [mx, room, linkifyOpts, mentionClickHandler, spoilerClickHandler, useAuthentication]
); );
const renderMatrixEvent = useMatrixEventRenderer<[IRoomEvent, string, GetContentCallback]>( const renderMatrixEvent = useMatrixEventRenderer<[IRoomEvent, string, GetContentCallback]>(
@ -369,7 +373,7 @@ function RoomNotificationsGroupComp({
<Avatar size="200" radii="300"> <Avatar size="200" radii="300">
<RoomAvatar <RoomAvatar
roomId={room.roomId} roomId={room.roomId}
src={getRoomAvatarUrl(mx, room, 96)} src={getRoomAvatarUrl(mx, room, 96, useAuthentication)}
alt={room.name} alt={room.name}
renderFallback={() => ( renderFallback={() => (
<RoomIcon size="50" joinRule={room.getJoinRule() ?? JoinRule.Restricted} filled /> <RoomIcon size="50" joinRule={room.getJoinRule() ?? JoinRule.Restricted} filled />
@ -424,7 +428,7 @@ function RoomNotificationsGroupComp({
userId={event.sender} userId={event.sender}
src={ src={
senderAvatarMxc senderAvatarMxc
? mx.mxcUrlToHttp(senderAvatarMxc, 48, 48, 'crop') ?? undefined ? mxcUrlToHttp(mx, senderAvatarMxc, useAuthentication, 48, 48, 'crop') ?? undefined
: undefined : undefined
} }
alt={displayName} alt={displayName}

View file

@ -86,6 +86,8 @@ import { openInviteUser, openSpaceSettings } from '../../../../client/action/nav
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { getMatrixToRoom } from '../../../plugins/matrix-to'; import { getMatrixToRoom } from '../../../plugins/matrix-to';
import { getViaServers } from '../../../plugins/via-servers'; import { getViaServers } from '../../../plugins/via-servers';
import { getRoomAvatarUrl } from '../../../utils/room';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
type SpaceMenuProps = { type SpaceMenuProps = {
room: Room; room: Room;
@ -225,18 +227,18 @@ const useDraggableItem = (
return !target return !target
? undefined ? undefined
: draggable({ : draggable({
element: target, element: target,
dragHandle, dragHandle,
getInitialData: () => ({ item }), getInitialData: () => ({ item }),
onDragStart: () => { onDragStart: () => {
setDragging(true); setDragging(true);
onDragging?.(item); onDragging?.(item);
}, },
onDrop: () => { onDrop: () => {
setDragging(false); setDragging(false);
onDragging?.(undefined); onDragging?.(undefined);
}, },
}); });
}, [targetRef, dragHandleRef, item, onDragging]); }, [targetRef, dragHandleRef, item, onDragging]);
return dragging; return dragging;
@ -379,15 +381,17 @@ function SpaceTab({
onUnpin, onUnpin,
}: SpaceTabProps) { }: SpaceTabProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const targetRef = useRef<HTMLDivElement>(null); const targetRef = useRef<HTMLDivElement>(null);
const spaceDraggable: SidebarDraggable = useMemo( const spaceDraggable: SidebarDraggable = useMemo(
() => () =>
folder folder
? { ? {
folder, folder,
spaceId: space.roomId, spaceId: space.roomId,
} }
: space.roomId, : space.roomId,
[folder, space] [folder, space]
); );
@ -431,7 +435,7 @@ function SpaceTab({
> >
<RoomAvatar <RoomAvatar
roomId={space.roomId} roomId={space.roomId}
src={space.getAvatarUrl(mx.baseUrl, 96, 96, 'crop') ?? undefined} src={getRoomAvatarUrl(mx, space, 96, useAuthentication) ?? undefined}
alt={space.name} alt={space.name}
renderFallback={() => ( renderFallback={() => (
<Text size={folder ? 'H6' : 'H4'}>{nameInitials(space.name, 2)}</Text> <Text size={folder ? 'H6' : 'H4'}>{nameInitials(space.name, 2)}</Text>
@ -524,6 +528,8 @@ function ClosedSpaceFolder({
disabled, disabled,
}: ClosedSpaceFolderProps) { }: ClosedSpaceFolderProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const handlerRef = useRef<HTMLDivElement>(null); const handlerRef = useRef<HTMLDivElement>(null);
const spaceDraggable: FolderDraggable = useMemo(() => ({ folder }), [folder]); const spaceDraggable: FolderDraggable = useMemo(() => ({ folder }), [folder]);
@ -556,7 +562,7 @@ function ClosedSpaceFolder({
<SidebarAvatar key={sId} size="200" radii="300"> <SidebarAvatar key={sId} size="200" radii="300">
<RoomAvatar <RoomAvatar
roomId={space.roomId} roomId={space.roomId}
src={space.getAvatarUrl(mx.baseUrl, 96, 96, 'crop') ?? undefined} src={getRoomAvatarUrl(mx, space, 96, useAuthentication) ?? undefined}
alt={space.name} alt={space.name}
renderFallback={() => ( renderFallback={() => (
<Text size="Inherit"> <Text size="Inherit">

View file

@ -5,8 +5,9 @@ import { SidebarItem, SidebarItemTooltip, SidebarAvatar } from '../../../compone
import { openSettings } from '../../../../client/action/navigation'; import { openSettings } from '../../../../client/action/navigation';
import { UserAvatar } from '../../../components/user-avatar'; import { UserAvatar } from '../../../components/user-avatar';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getMxIdLocalPart } from '../../../utils/matrix'; import { getMxIdLocalPart, mxcUrlToHttp } from '../../../utils/matrix';
import { nameInitials } from '../../../utils/common'; import { nameInitials } from '../../../utils/common';
import { useSpecVersions } from '../../../hooks/useSpecVersions';
type UserProfile = { type UserProfile = {
avatar_url?: string; avatar_url?: string;
@ -14,12 +15,14 @@ type UserProfile = {
}; };
export function UserTab() { export function UserTab() {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { versions } = useSpecVersions();
const useAuthentication = versions.includes('v1.11');
const userId = mx.getUserId()!; const userId = mx.getUserId()!;
const [profile, setProfile] = useState<UserProfile>({}); const [profile, setProfile] = useState<UserProfile>({});
const displayName = profile.displayname ?? getMxIdLocalPart(userId) ?? userId; const displayName = profile.displayname ?? getMxIdLocalPart(userId) ?? userId;
const avatarUrl = profile.avatar_url const avatarUrl = profile.avatar_url
? mx.mxcUrlToHttp(profile.avatar_url, 96, 96, 'crop') ?? undefined ? mxcUrlToHttp(mx, profile.avatar_url, useAuthentication, 96, 96, 'crop') ?? undefined
: undefined; : undefined;
useEffect(() => { useEffect(() => {

View file

@ -14,7 +14,7 @@ import { IntermediateRepresentation, Opts as LinkifyOpts, OptFn } from 'linkifyj
import Linkify from 'linkify-react'; import Linkify from 'linkify-react';
import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary';
import * as css from '../styles/CustomHtml.css'; import * as css from '../styles/CustomHtml.css';
import { getMxIdLocalPart, getCanonicalAliasRoomId, isRoomAlias } from '../utils/matrix'; import { getMxIdLocalPart, getCanonicalAliasRoomId, isRoomAlias, mxcUrlToHttp } from '../utils/matrix';
import { getMemberDisplayName } from '../utils/room'; import { getMemberDisplayName } from '../utils/room';
import { EMOJI_PATTERN, URL_NEG_LB } from '../utils/regex'; import { EMOJI_PATTERN, URL_NEG_LB } from '../utils/regex';
import { getHexcodeForEmoji, getShortcodeFor } from './emoji'; import { getHexcodeForEmoji, getShortcodeFor } from './emoji';
@ -72,9 +72,8 @@ export const renderMatrixMention = (
className={css.Mention({ highlight: mx.getUserId() === userId })} className={css.Mention({ highlight: mx.getUserId() === userId })}
data-mention-id={userId} data-mention-id={userId}
> >
{`@${ {`@${(currentRoom && getMemberDisplayName(currentRoom, userId)) ?? getMxIdLocalPart(userId)
(currentRoom && getMemberDisplayName(currentRoom, userId)) ?? getMxIdLocalPart(userId) }`}
}`}
</a> </a>
); );
} }
@ -192,6 +191,7 @@ export const getReactCustomHtmlParser = (
highlightRegex?: RegExp; highlightRegex?: RegExp;
handleSpoilerClick?: ReactEventHandler<HTMLElement>; handleSpoilerClick?: ReactEventHandler<HTMLElement>;
handleMentionClick?: ReactEventHandler<HTMLElement>; handleMentionClick?: ReactEventHandler<HTMLElement>;
useAuthentication?: boolean;
} }
): HTMLReactParserOptions => { ): HTMLReactParserOptions => {
const opts: HTMLReactParserOptions = { const opts: HTMLReactParserOptions = {
@ -354,7 +354,7 @@ export const getReactCustomHtmlParser = (
} }
if (name === 'img') { if (name === 'img') {
const htmlSrc = mx.mxcUrlToHttp(props.src); const htmlSrc = mxcUrlToHttp(mx, props.src, params.useAuthentication);
if (htmlSrc && props.src.startsWith('mxc://') === false) { if (htmlSrc && props.src.startsWith('mxc://') === false) {
return ( return (
<a href={htmlSrc} target="_blank" rel="noreferrer noopener"> <a href={htmlSrc} target="_blank" rel="noreferrer noopener">

View file

@ -253,3 +253,23 @@ export const removeRoomIdFromMDirect = async (mx: MatrixClient, roomId: string):
await mx.setAccountData(AccountDataEvent.Direct, userIdToRoomIds); await mx.setAccountData(AccountDataEvent.Direct, userIdToRoomIds);
}; };
export const mxcUrlToHttp = (
mx: MatrixClient,
mxcUrl: string,
useAuthentication?: boolean,
width?: number,
height?: number,
resizeMethod?: string,
allowDirectLinks?: boolean,
allowRedirects?: boolean
): string | null =>
mx.mxcUrlToHttp(
mxcUrl,
width,
height,
resizeMethod,
allowDirectLinks,
allowRedirects,
useAuthentication
);

View file

@ -273,16 +273,26 @@ export const joinRuleToIconSrc = (
export const getRoomAvatarUrl = ( export const getRoomAvatarUrl = (
mx: MatrixClient, mx: MatrixClient,
room: Room, room: Room,
size: 32 | 96 = 32 size: 32 | 96 = 32,
): string | undefined => room.getAvatarUrl(mx.baseUrl, size, size, 'crop') ?? undefined; useAuthentication = false
): string | undefined => {
const mxcUrl = room.getMxcAvatarUrl();
return mxcUrl
? mx.mxcUrlToHttp(mxcUrl, size, size, 'crop', undefined, false, useAuthentication) ?? undefined
: undefined;
};
export const getDirectRoomAvatarUrl = ( export const getDirectRoomAvatarUrl = (
mx: MatrixClient, mx: MatrixClient,
room: Room, room: Room,
size: 32 | 96 = 32 size: 32 | 96 = 32,
): string | undefined => useAuthentication = false
room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, size, size, 'crop', undefined, false) ?? ): string | undefined => {
undefined; const mxcUrl = room.getAvatarFallbackMember()?.getMxcAvatarUrl();
return mxcUrl
? mx.mxcUrlToHttp(mxcUrl, size, size, 'crop', undefined, false, useAuthentication) ?? undefined
: undefined;
};
export const trimReplyFromBody = (body: string): string => { export const trimReplyFromBody = (body: string): string => {
const match = body.match(/^> <.+?> .+\n(>.*\n)*?\n/m); const match = body.match(/^> <.+?> .+\n(>.*\n)*?\n/m);

View file

@ -23,7 +23,6 @@ export const initClient = async (session: Session): Promise<MatrixClient> => {
localStorage: global.localStorage, localStorage: global.localStorage,
dbName: 'web-sync-store', dbName: 'web-sync-store',
}); });
await indexedDBStore.startup();
const mx = createClient({ const mx = createClient({
baseUrl: session.baseUrl, baseUrl: session.baseUrl,
@ -38,6 +37,7 @@ export const initClient = async (session: Session): Promise<MatrixClient> => {
}); });
await mx.initCrypto(); await mx.initCrypto();
await indexedDBStore.startup();
mx.setGlobalErrorOnUnknownDevices(false); mx.setGlobalErrorOnUnknownDevices(false);
mx.setMaxListeners(50); mx.setMaxListeners(50);

View file

@ -12,6 +12,7 @@ import './index.scss';
import settings from './client/state/settings'; import settings from './client/state/settings';
import { trimTrailingSlash } from './app/utils/common';
import App from './app/pages/App'; import App from './app/pages/App';
// import i18n (needs to be bundled ;)) // import i18n (needs to be bundled ;))
@ -20,6 +21,23 @@ import './app/i18n';
document.body.classList.add(configClass, varsClass); document.body.classList.add(configClass, varsClass);
settings.applyTheme(); settings.applyTheme();
// Register Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(
import.meta.env.MODE === 'production' ? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js` : '/dev-sw.js?dev-sw'
)
navigator.serviceWorker.addEventListener('message', (event) => {
if (event.data?.type === 'token' && event.data?.responseKey) {
// Get the token for SW.
const token = localStorage.getItem('cinny_access_token');
event.source!.postMessage({
responseKey: event.data.responseKey,
token,
})
}
})
}
const mountApp = () => { const mountApp = () => {
const rootContainer = document.getElementById('root'); const rootContainer = document.getElementById('root');

43
src/sw.ts Normal file
View file

@ -0,0 +1,43 @@
async function askForAccessToken(client: Client): Promise<string | undefined> {
return new Promise((resolve) => {
const responseKey = Math.random().toString(36);
const listener = (event: ExtendableMessageEvent) => {
if (event.data.responseKey !== responseKey) return;
resolve(event.data.token);
self.removeEventListener('message', listener);
};
self.addEventListener('message', listener);
client.postMessage({ responseKey, type: 'token' });
});
}
function fetchConfig(token?: string): RequestInit | undefined {
if (!token) return undefined;
return {
headers: {
Authorization: `Bearer ${token}`,
},
};
}
self.addEventListener('fetch', (event: FetchEvent) => {
const { url, method } = event.request;
if (method !== 'GET') return;
if (
!url.includes('/_matrix/client/v1/media/download') &&
!url.includes('/_matrix/client/v1/media/thumbnail')
) {
return;
}
event.respondWith(
(async (): Promise<Response> => {
const client = await clients.get(event.clientId);
let token: string | undefined;
if (client) token = await askForAccessToken(client);
// eslint-disable-next-line consistent-return
return fetch(url, fetchConfig(token));
})()
);
});

View file

@ -10,7 +10,8 @@
"moduleResolution": "Node", "moduleResolution": "Node",
"resolveJsonModule": true, "resolveJsonModule": true,
"outDir": "dist", "outDir": "dist",
"skipLibCheck": true "skipLibCheck": true,
"lib": ["ES2016", "DOM"]
}, },
"exclude": ["node_modules", "dist"], "exclude": ["node_modules", "dist"],
"include": ["src"] "include": ["src"]

View file

@ -6,6 +6,7 @@ import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill'; import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill';
import inject from '@rollup/plugin-inject'; import inject from '@rollup/plugin-inject';
import topLevelAwait from 'vite-plugin-top-level-await'; import topLevelAwait from 'vite-plugin-top-level-await';
import { VitePWA } from 'vite-plugin-pwa';
import buildConfig from './build.config'; import buildConfig from './build.config';
const copyFiles = { const copyFiles = {
@ -50,11 +51,11 @@ export default defineConfig({
port: 8080, port: 8080,
host: true, host: true,
proxy: { proxy: {
"^\\/.*?\\/olm\\.wasm$": { '^\\/.*?\\/olm\\.wasm$': {
target: 'http://localhost:8080', target: 'http://localhost:8080',
rewrite: () => '/olm.wasm' rewrite: () => '/olm.wasm',
} },
} },
}, },
plugins: [ plugins: [
topLevelAwait({ topLevelAwait({
@ -67,6 +68,16 @@ export default defineConfig({
vanillaExtractPlugin(), vanillaExtractPlugin(),
wasm(), wasm(),
react(), react(),
VitePWA({
srcDir: 'src',
filename: 'sw.ts',
strategies: 'injectManifest',
injectRegister: false,
manifest: false,
injectManifest: {
injectionPoint: undefined,
},
}),
], ],
optimizeDeps: { optimizeDeps: {
esbuildOptions: { esbuildOptions: {