Compare commits

...

29 commits

Author SHA1 Message Date
3c4b7e5c33
Merge upstream tag 'v4.2.3' 2024-11-29 21:55:26 +09:00
Krishan
a142630ff9
Release v4.2.3 (#2052) 2024-11-12 20:45:34 +11:00
renovate[bot]
492a149c7f
fix(deps): update dependency matrix-js-sdk to v34.11.1 (#2053)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-12 20:43:50 +11:00
Krishan
c110e64341
Release v4.2.2 (#2012) 2024-10-16 21:29:30 +11:00
夜坂雅
0e51e19cab
fix: register service worker immediately and cache media requests (#1977)
* Allow service worker to immediately claim pages
* Allow media requests to be cached by browser
2024-10-16 21:26:03 +11:00
renovate[bot]
35b0b1ea42
fix(deps): update dependency matrix-js-sdk to v34.8.0 (#2011)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-16 21:22:06 +11:00
dependabot[bot]
cca8b5f2b2
Bump actions/setup-node from 4.0.3 to 4.0.4 (#1969)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.0.3 to 4.0.4.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4.0.3...v4.0.4)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 17:03:33 +10:00
dependabot[bot]
48265c4227
Bump actions/checkout from 4.1.7 to 4.2.0 (#1985)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.7 to 4.2.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.7...v4.2.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 17:00:32 +10:00
dependabot[bot]
c38efdfbce
Bump docker/build-push-action from 6.7.0 to 6.9.0 (#1986)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.7.0 to 6.9.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.7.0...v6.9.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 16:59:55 +10:00
dependabot[bot]
d8833a310d
Bump cla-assistant/github-action from 2.5.1 to 2.6.1 (#1987)
Bumps [cla-assistant/github-action](https://github.com/cla-assistant/github-action) from 2.5.1 to 2.6.1.
- [Release notes](https://github.com/cla-assistant/github-action/releases)
- [Commits](https://github.com/cla-assistant/github-action/compare/v2.5.1...v2.6.1)

---
updated-dependencies:
- dependency-name: cla-assistant/github-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 16:59:12 +10:00
Ajay Bura
5824d7c716
fix font-weight in dark theme with unsupported fonts (#1964) 2024-09-22 22:31:32 +10:00
Krishan
6e191d3c79
Fix matrix.to links opening in webview in cinny desktop (#1963) 2024-09-22 10:08:55 +05:30
Krishan
21164a9b61
Release v4.2.1 (#1953) 2024-09-14 23:24:34 +10:00
Krishan
4923b17ad6
Fix auth media check for dendrite (#1952) 2024-09-14 18:54:06 +05:30
Krishan
c75e903619
Release v4.2.0 (#1949) 2024-09-11 19:26:08 +05:30
renovate[bot]
042cbc4453
Update dependency matrix-js-sdk to v34.5.0 (#1945)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-11 17:08:38 +10:00
Ajay Bura
03cc25eec0
Fix authenticated media download (#1947)
* remove dead function

* fix media download in room timeline

* authenticate remaining media endpoints
2024-09-11 17:07:02 +10:00
夜坂雅
f2c31d29a2
fix: Fix video and audio loading with authenicated media (#1946)
Appeareantly Firefox (and maybe Chrome) won't let service workers take over requests from <video> and <audio> tags, so we just fetch the URL ourselves.
2024-09-11 10:43:15 +05:30
Ajay Bura
5482f8e72e
render matrix room and event link content (#1938) 2024-09-09 20:51:52 +10:00
Ajay Bura
96df140153
Improve-auth-media (#1933)
* fix set power level broken after sdk update

* add media authentication hook

* fix service worker types

* fix service worker not working in dev mode

* fix env mode check when registering sw
2024-09-09 14:15:20 +05:30
Ajay Bura
4dfce32730
fix mention url is encoded wrong (#1936) 2024-09-08 22:53:59 +10:00
Ajay Bura
388f606ad2
fix escape to mark as read (#1935) 2024-09-08 22:53:17 +10:00
Ajay Bura
09444f9e08
fix sso login without identity providers (#1934) 2024-09-08 22:51:43 +10:00
夜坂雅
c6a8fb1117
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
2024-09-07 19:15:55 +05:30
Dylan Hackworth
043012e809
pressing up to edit should take you to end of line (#1928) 2024-09-07 18:38:16 +05:30
utf
5c9ee1a988
Fix IPv6 support for the Docker container (#1884)
* Fix `docker-nginx.conf` indentation

* Listen on IPv4 and IPv6 inside Docker
2024-08-23 20:56:03 +10:00
Krishan
22b7f6dd7d
Create Code of Conduct (#1908) 2024-08-21 15:43:40 +05:30
dependabot[bot]
bdba0332e1
Bump cla-assistant/github-action from 2.4.0 to 2.5.1 (#1905)
Bumps [cla-assistant/github-action](https://github.com/cla-assistant/github-action) from 2.4.0 to 2.5.1.
- [Release notes](https://github.com/cla-assistant/github-action/releases)
- [Commits](https://github.com/cla-assistant/github-action/compare/v2.4.0...v2.5.1)

---
updated-dependencies:
- dependency-name: cla-assistant/github-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-21 00:22:26 +10:00
dependabot[bot]
16be69c104
Bump docker/build-push-action from 6.6.1 to 6.7.0 (#1906)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.6.1 to 6.7.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.6.1...v6.7.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-21 00:21:25 +10:00
72 changed files with 4034 additions and 735 deletions

View file

@ -12,9 +12,9 @@ jobs:
PR_NUMBER: ${{github.event.number}}
steps:
- name: Checkout repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Setup node
uses: actions/setup-node@v4.0.3
uses: actions/setup-node@v4.0.4
with:
node-version: 20.12.2
cache: 'npm'

View file

@ -12,7 +12,7 @@ jobs:
- name: 'CLA Assistant'
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
# Beta Release
uses: cla-assistant/github-action@v2.4.0
uses: cla-assistant/github-action@v2.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# the below token should have repo scope and must be manually added by you in the repository's secret

View file

@ -11,9 +11,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Build Docker image
uses: docker/build-push-action@v6.6.1
uses: docker/build-push-action@v6.9.0
with:
context: .
push: false

View file

@ -14,7 +14,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: NPM Lockfile Changes
uses: codepunkt/npm-lockfile-changes@b40543471c36394409466fdb277a73a0856d7891
with:

View file

@ -11,9 +11,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Setup node
uses: actions/setup-node@v4.0.3
uses: actions/setup-node@v4.0.4
with:
node-version: 20.12.2
cache: 'npm'

View file

@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Setup node
uses: actions/setup-node@v4.0.3
uses: actions/setup-node@v4.0.4
with:
node-version: 20.12.2
cache: 'npm'
@ -66,7 +66,7 @@ jobs:
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.2.0
- name: Set up Docker Buildx
@ -90,7 +90,7 @@ jobs:
${{ secrets.DOCKER_USERNAME }}/cinny
ghcr.io/${{ github.repository }}
- name: Build and push Docker image
uses: docker/build-push-action@v6.6.1
uses: docker/build-push-action@v6.9.0
with:
context: .
platforms: linux/amd64,linux/arm64

1
.npmrc
View file

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

128
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
cinnyapp@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View file

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

View file

@ -1,4 +1,7 @@
server {
listen 80;
listen [::]:80;
location / {
root /usr/share/nginx/html;
@ -6,6 +9,7 @@ server {
rewrite ^/manifest.json$ /manifest.json break;
rewrite ^.*/olm.wasm$ /olm.wasm break;
rewrite ^/sw.js$ /sw.js break;
rewrite ^/pdf.worker.min.js$ /pdf.worker.min.js break;
rewrite ^/public/(.*)$ /public/$1 break;

View file

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

3356
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "cinny",
"version": "4.1.0",
"version": "4.2.3",
"description": "Yet another matrix client",
"main": "index.js",
"type": "module",
@ -24,7 +24,7 @@
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3",
"@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-devtools": "5.24.1",
"@tanstack/react-virtual": "3.2.0",
@ -56,7 +56,7 @@
"jotai": "2.6.0",
"linkify-react": "4.1.3",
"linkifyjs": "4.1.3",
"matrix-js-sdk": "29.1.0",
"matrix-js-sdk": "34.11.1",
"millify": "6.1.0",
"pdfjs-dist": "4.2.67",
"prismjs": "1.29.0",
@ -106,6 +106,7 @@
"sass": "1.56.2",
"typescript": "4.9.4",
"vite": "5.0.13",
"vite-plugin-pwa": "0.20.5",
"vite-plugin-static-copy": "1.0.4",
"vite-plugin-top-level-await": "1.4.1"
}

View file

@ -14,6 +14,8 @@ import { useMatrixClient } from '../../hooks/useMatrixClient';
import { getEmojiUrl, isUsingTwemoji } from '../../plugins/emoji';
import { getBeginCommand } from './utils';
import { BlockType } from './types';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
// 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
@ -77,6 +79,7 @@ function RenderEmoticonElement({
children,
}: { element: EmoticonElement } & RenderElementProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const selected = useSelected();
const focused = useFocused();
@ -91,7 +94,7 @@ function RenderEmoticonElement({
{element.key.startsWith('mxc://') ? (
<img
className={css.EmoticonImg}
src={mx.mxcUrlToHttp(element.key) ?? element.key}
src={mxcUrlToHttp(mx, element.key, useAuthentication) ?? element.key}
alt={element.shortcode}
/>
) : (

View file

@ -18,6 +18,8 @@ import { useRelevantImagePacks } from '../../../hooks/useImagePacks';
import { IEmoji, emojis } from '../../../plugins/emoji';
import { ExtendedPackImage, PackUsage } from '../../../plugins/custom-emoji';
import { useKeyDown } from '../../../hooks/useKeyDown';
import { mxcUrlToHttp } from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
type EmoticonCompleteHandler = (key: string, shortcode: string) => void;
@ -48,6 +50,7 @@ export function EmoticonAutocomplete({
requestClose,
}: EmoticonAutocompleteProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const imagePacks = useRelevantImagePacks(mx, PackUsage.Emoticon, imagePackRooms);
const recentEmoji = useRecentEmoji(mx, 20);
@ -103,7 +106,7 @@ export function EmoticonAutocomplete({
<Box
shrink="No"
as="img"
src={mx.mxcUrlToHttp(key) || key}
src={mxcUrlToHttp(mx, key, useAuthentication) || key}
alt={emoticon.shortcode}
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 { getMemberDisplayName, getMemberSearchStr } from '../../../utils/room';
import { UserAvatar } from '../../user-avatar';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
type MentionAutoCompleteHandler = (userId: string, name: string) => void;
@ -84,6 +85,7 @@ export function UserMentionAutocomplete({
requestClose,
}: UserMentionAutocompleteProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const roomId: string = room.roomId!;
const roomAliasOrId = room.getCanonicalAlias() || roomId;
const members = useRoomMembers(mx, roomId);
@ -143,7 +145,10 @@ export function UserMentionAutocomplete({
/>
) : (
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 (
<MenuItem
key={roomMember.userId}

View file

@ -62,7 +62,7 @@ const elementToCustomHtml = (node: CustomElement, children: string): string => {
}
const matrixTo = `https://matrix.to/#/${fragment}`;
return `<a href="${encodeURIComponent(matrixTo)}">${sanitizeText(node.name)}</a>`;
return `<a href="${encodeURI(matrixTo)}">${sanitizeText(node.name)}</a>`;
}
case BlockType.Emoticon:
return node.key.startsWith('mxc://')
@ -71,7 +71,7 @@ const elementToCustomHtml = (node: CustomElement, children: string): string => {
)}" title="${sanitizeText(node.shortcode)}" height="32" />`
: sanitizeText(node.key);
case BlockType.Link:
return `<a href="${encodeURIComponent(node.href)}">${node.children}</a>`;
return `<a href="${encodeURI(node.href)}">${node.children}</a>`;
case BlockType.Command:
return `/${sanitizeText(node.command)}`;
default:

View file

@ -50,13 +50,14 @@ import { useRelevantImagePacks } from '../../hooks/useImagePacks';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRecentEmoji } from '../../hooks/useRecentEmoji';
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 { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch';
import { useDebounce } from '../../hooks/useDebounce';
import { useThrottle } from '../../hooks/useThrottle';
import { addRecentEmoji } from '../../plugins/recent-emoji';
import { mobileOrTablet } from '../../utils/user-agent';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
const RECENT_GROUP_ID = 'recent_group';
const SEARCH_GROUP_ID = 'search_group';
@ -371,11 +372,13 @@ function ImagePackSidebarStack({
packs,
usage,
onItemClick,
useAuthentication,
}: {
mx: MatrixClient;
packs: ImagePack[];
usage: PackUsage;
onItemClick: (id: string) => void;
useAuthentication?: boolean;
}) {
const activeGroupId = useAtomValue(activeGroupIdAtom);
return (
@ -398,7 +401,7 @@ function ImagePackSidebarStack({
height: toRem(24),
objectFit: 'contain',
}}
src={mx.mxcUrlToHttp(pack.getPackAvatarUrl(usage) ?? '') || pack.avatarUrl}
src={mxcUrlToHttp(mx, pack.getPackAvatarUrl(usage) ?? '', useAuthentication) || pack.avatarUrl}
alt={label || 'Unknown Pack'}
/>
</SidebarBtn>
@ -470,12 +473,14 @@ export function SearchEmojiGroup({
label,
id,
emojis: searchResult,
useAuthentication,
}: {
mx: MatrixClient;
tab: EmojiBoardTab;
label: string;
id: string;
emojis: Array<ExtendedPackImage | IEmoji>;
useAuthentication?: boolean;
}) {
return (
<EmojiGroup key={id} id={id} label={label}>
@ -503,7 +508,7 @@ export function SearchEmojiGroup({
loading="lazy"
className={css.CustomEmojiImg}
alt={emoji.body || emoji.shortcode}
src={mx.mxcUrlToHttp(emoji.url) ?? emoji.url}
src={mxcUrlToHttp(mx, emoji.url, useAuthentication) ?? emoji.url}
/>
</EmojiItem>
)
@ -521,7 +526,7 @@ export function SearchEmojiGroup({
loading="lazy"
className={css.StickerImg}
alt={emoji.body || emoji.shortcode}
src={mx.mxcUrlToHttp(emoji.url) ?? emoji.url}
src={mxcUrlToHttp(mx, emoji.url, useAuthentication) ?? emoji.url}
/>
</StickerItem>
)
@ -531,7 +536,7 @@ export function SearchEmojiGroup({
}
export const CustomEmojiGroups = memo(
({ mx, groups }: { mx: MatrixClient; groups: ImagePack[] }) => (
({ mx, groups, useAuthentication }: { mx: MatrixClient; groups: ImagePack[]; useAuthentication?: boolean }) => (
<>
{groups.map((pack) => (
<EmojiGroup key={pack.id} id={pack.id} label={pack.displayName || 'Unknown'}>
@ -547,7 +552,7 @@ export const CustomEmojiGroups = memo(
loading="lazy"
className={css.CustomEmojiImg}
alt={image.body || image.shortcode}
src={mx.mxcUrlToHttp(image.url) ?? image.url}
src={mxcUrlToHttp(mx, image.url, useAuthentication) ?? image.url}
/>
</EmojiItem>
))}
@ -557,7 +562,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 && (
<Box
@ -590,7 +595,7 @@ export const StickerGroups = memo(({ mx, groups }: { mx: MatrixClient; groups: I
loading="lazy"
className={css.StickerImg}
alt={image.body || image.shortcode}
src={mx.mxcUrlToHttp(image.url) ?? image.url}
src={mxcUrlToHttp(mx, image.url, useAuthentication) ?? image.url}
/>
</StickerItem>
))}
@ -662,6 +667,7 @@ export function EmojiBoard({
const setActiveGroupId = useSetAtom(activeGroupIdAtom);
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const emojiGroupLabels = useEmojiGroupLabels();
const emojiGroupIcons = useEmojiGroupIcons();
const imagePacks = useRelevantImagePacks(mx, usage, imagePackRooms);
@ -755,14 +761,14 @@ export function EmojiBoard({
} else if (emojiInfo.type === EmojiType.CustomEmoji && emojiPreviewRef.current) {
const img = document.createElement('img');
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);
emojiPreviewRef.current.textContent = '';
emojiPreviewRef.current.appendChild(img);
}
emojiPreviewTextRef.current.textContent = `:${emojiInfo.shortcode}:`;
},
[mx]
[mx, useAuthentication]
);
const throttleEmojiHover = useThrottle(handleEmojiPreview, {
@ -855,6 +861,7 @@ export function EmojiBoard({
usage={usage}
packs={imagePacks}
onItemClick={handleScrollToGroup}
useAuthentication={useAuthentication}
/>
)}
{emojiTab && (
@ -916,13 +923,14 @@ export function EmojiBoard({
id={SEARCH_GROUP_ID}
label={result.items.length ? 'Search Results' : 'No Results found'}
emojis={result.items}
useAuthentication={useAuthentication}
/>
)}
{emojiTab && recentEmojis.length > 0 && (
<RecentEmojiGroup id={RECENT_GROUP_ID} label="Recent" emojis={recentEmojis} />
)}
{emojiTab && <CustomEmojiGroups mx={mx} groups={imagePacks} />}
{stickerTab && <StickerGroups mx={mx} groups={imagePacks} />}
{emojiTab && <CustomEmojiGroups mx={mx} groups={imagePacks} useAuthentication={useAuthentication} />}
{stickerTab && <StickerGroups mx={mx} groups={imagePacks} useAuthentication={useAuthentication} />}
{emojiTab && <NativeEmojiGroups groups={emojiGroups} labels={emojiGroupLabels} />}
</Box>
</Scroll>

View file

@ -21,6 +21,7 @@ import * as css from './EventReaders.css';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { openProfileViewer } from '../../../client/action/navigation';
import { UserAvatar } from '../user-avatar';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
export type EventReadersProps = {
room: Room;
@ -30,6 +31,7 @@ export type EventReadersProps = {
export const EventReaders = as<'div', EventReadersProps>(
({ className, room, eventId, requestClose, ...props }, ref) => {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const latestEventReaders = useRoomEventReaders(room, eventId);
const getName = (userId: string) =>
@ -55,9 +57,10 @@ export const EventReaders = as<'div', EventReadersProps>(
<Box className={css.Content} direction="Column">
{latestEventReaders.map((readerId) => {
const name = getName(readerId);
const avatarUrl = room
const avatarMxcUrl = room
.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 (
<MenuItem

View file

@ -6,6 +6,7 @@ import { Box, Chip, Header, Icon, IconButton, Icons, Text, as } from 'folds';
import * as css from './ImageViewer.css';
import { useZoom } from '../../hooks/useZoom';
import { usePan } from '../../hooks/usePan';
import { downloadMedia } from '../../utils/matrix';
export type ImageViewerProps = {
alt: string;
@ -18,8 +19,9 @@ export const ImageViewer = as<'div', ImageViewerProps>(
const { zoom, zoomIn, zoomOut, setZoom } = useZoom(0.2);
const { pan, cursor, onMouseDown } = usePan(zoom !== 1);
const handleDownload = () => {
FileSaver.saveAs(src, alt);
const handleDownload = async () => {
const fileContent = await downloadMedia(src);
FileSaver.saveAs(fileContent, alt);
};
return (

View file

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

View file

@ -5,7 +5,6 @@ import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
import { Range } from 'react-range';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { getFileSrcUrl } from './util';
import { IAudioInfo } from '../../../../types/matrix/common';
import {
PlayTimeCallback,
@ -17,6 +16,13 @@ import {
} from '../../../hooks/media';
import { useThrottle } from '../../../hooks/useThrottle';
import { secondsToMinutesAndSeconds } from '../../../utils/common';
import {
decryptFile,
downloadEncryptedMedia,
downloadMedia,
mxcUrlToHttp,
} from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
const PLAY_TIME_THROTTLE_OPS = {
wait: 500,
@ -44,12 +50,16 @@ export function AudioContent({
renderMediaControl,
}: AudioContentProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [srcState, loadSrc] = useAsyncCallback(
useCallback(
() => getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo),
[mx, url, mimeType, encInfo]
)
useCallback(async () => {
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
const fileContent = encInfo
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
: await downloadMedia(mediaUrl);
return URL.createObjectURL(fileContent);
}, [mx, url, useAuthentication, mimeType, encInfo])
);
const audioRef = useRef<HTMLAudioElement | null>(null);

View file

@ -20,7 +20,6 @@ import FocusTrap from 'focus-trap-react';
import { IFileInfo } from '../../../../types/matrix/common';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getFileSrcUrl, getSrcFile } from './util';
import { bytesToSize } from '../../../utils/common';
import {
READABLE_EXT_TO_MIME_TYPE,
@ -30,6 +29,13 @@ import {
} from '../../../utils/mimeTypes';
import * as css from './style.css';
import { stopPropagation } from '../../../utils/keyboard';
import {
decryptFile,
downloadEncryptedMedia,
downloadMedia,
mxcUrlToHttp,
} from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
const renderErrorButton = (retry: () => void, text: string) => (
<TooltipProvider
@ -75,21 +81,20 @@ type ReadTextFileProps = {
};
export function ReadTextFile({ body, mimeType, url, encInfo, renderViewer }: ReadTextFileProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [textViewer, setTextViewer] = useState(false);
const loadSrc = useCallback(
() => getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo),
[mx, url, mimeType, encInfo]
);
const [textState, loadText] = useAsyncCallback(
useCallback(async () => {
const src = await loadSrc();
const blob = await getSrcFile(src);
const text = blob.text();
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
const fileContent = encInfo
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
: await downloadMedia(mediaUrl);
const text = fileContent.text();
setTextViewer(true);
return text;
}, [loadSrc])
}, [mx, useAuthentication, mimeType, encInfo, url])
);
return (
@ -166,14 +171,18 @@ export type ReadPdfFileProps = {
};
export function ReadPdfFile({ body, mimeType, url, encInfo, renderViewer }: ReadPdfFileProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [pdfViewer, setPdfViewer] = useState(false);
const [pdfState, loadPdf] = useAsyncCallback(
useCallback(async () => {
const httpUrl = await getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo);
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
const fileContent = encInfo
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
: await downloadMedia(mediaUrl);
setPdfViewer(true);
return httpUrl;
}, [mx, url, mimeType, encInfo])
return URL.createObjectURL(fileContent);
}, [mx, url, useAuthentication, mimeType, encInfo])
);
return (
@ -240,13 +249,19 @@ export type DownloadFileProps = {
};
export function DownloadFile({ body, mimeType, url, info, encInfo }: DownloadFileProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [downloadState, download] = useAsyncCallback(
useCallback(async () => {
const httpUrl = await getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo);
FileSaver.saveAs(httpUrl, body);
return httpUrl;
}, [mx, url, mimeType, encInfo, body])
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
const fileContent = encInfo
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
: await downloadMedia(mediaUrl);
const fileURL = URL.createObjectURL(fileContent);
FileSaver.saveAs(fileURL, body);
return fileURL;
}, [mx, url, useAuthentication, mimeType, encInfo, body])
);
return downloadState.status === AsyncStatus.Error ? (

View file

@ -22,11 +22,12 @@ import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
import { IImageInfo, MATRIX_BLUR_HASH_PROPERTY_NAME } from '../../../../types/matrix/common';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getFileSrcUrl } from './util';
import * as css from './style.css';
import { bytesToSize } from '../../../utils/common';
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';
import { stopPropagation } from '../../../utils/keyboard';
import { decryptFile, downloadEncryptedMedia, mxcUrlToHttp } from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
type RenderViewerProps = {
src: string;
@ -69,6 +70,7 @@ export const ImageContent = as<'div', ImageContentProps>(
ref
) => {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const blurHash = info?.[MATRIX_BLUR_HASH_PROPERTY_NAME];
const [load, setLoad] = useState(false);
@ -76,10 +78,16 @@ export const ImageContent = as<'div', ImageContentProps>(
const [viewer, setViewer] = useState(false);
const [srcState, loadSrc] = useAsyncCallback(
useCallback(
() => getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType || FALLBACK_MIMETYPE, encInfo),
[mx, url, mimeType, encInfo]
)
useCallback(async () => {
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
if (encInfo) {
const fileContent = await downloadEncryptedMedia(mediaUrl, (encBuf) =>
decryptFile(encBuf, mimeType ?? FALLBACK_MIMETYPE, encInfo)
);
return URL.createObjectURL(fileContent);
}
return mediaUrl;
}, [mx, url, useAuthentication, mimeType, encInfo])
);
const handleLoad = () => {

View file

@ -2,7 +2,9 @@ import { ReactNode, useCallback, useEffect } from 'react';
import { IThumbnailContent } from '../../../../types/matrix/common';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { getFileSrcUrl } from './util';
import { decryptFile, downloadEncryptedMedia, mxcUrlToHttp } from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';
export type ThumbnailContentProps = {
info: IThumbnailContent;
@ -10,20 +12,27 @@ export type ThumbnailContentProps = {
};
export function ThumbnailContent({ info, renderImage }: ThumbnailContentProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [thumbSrcState, loadThumbSrc] = useAsyncCallback(
useCallback(() => {
useCallback(async () => {
const thumbInfo = info.thumbnail_info;
const thumbMxcUrl = info.thumbnail_file?.url ?? info.thumbnail_url;
const encInfo = info.thumbnail_file;
if (typeof thumbMxcUrl !== 'string' || typeof thumbInfo?.mimetype !== 'string') {
throw new Error('Failed to load thumbnail');
}
return getFileSrcUrl(
mx.mxcUrlToHttp(thumbMxcUrl) ?? '',
thumbInfo.mimetype,
info.thumbnail_file
const mediaUrl = mxcUrlToHttp(mx, thumbMxcUrl, useAuthentication) ?? thumbMxcUrl;
if (encInfo) {
const fileContent = await downloadEncryptedMedia(mediaUrl, (encBuf) =>
decryptFile(encBuf, thumbInfo.mimetype ?? FALLBACK_MIMETYPE, encInfo)
);
}, [mx, info])
return URL.createObjectURL(fileContent);
}
return mediaUrl;
}, [mx, info, useAuthentication])
);
useEffect(() => {

View file

@ -22,9 +22,15 @@ import {
import * as css from './style.css';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { getFileSrcUrl } from './util';
import { bytesToSize } from '../../../../util/common';
import { millisecondsToMinutesAndSeconds } from '../../../utils/common';
import {
decryptFile,
downloadEncryptedMedia,
downloadMedia,
mxcUrlToHttp,
} from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
type RenderVideoProps = {
title: string;
@ -61,16 +67,22 @@ export const VideoContent = as<'div', VideoContentProps>(
ref
) => {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const blurHash = info.thumbnail_info?.[MATRIX_BLUR_HASH_PROPERTY_NAME];
const [load, setLoad] = useState(false);
const [error, setError] = useState(false);
const [srcState, loadSrc] = useAsyncCallback(
useCallback(
() => getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo),
[mx, url, mimeType, encInfo]
useCallback(async () => {
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
const fileContent = encInfo
? await downloadEncryptedMedia(mediaUrl, (encBuf) =>
decryptFile(encBuf, mimeType, encInfo)
)
: await downloadMedia(mediaUrl);
return URL.createObjectURL(fileContent);
}, [mx, url, useAuthentication, mimeType, encInfo])
);
const handleLoad = () => {

View file

@ -1,23 +0,0 @@
import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
import { decryptFile } from '../../../utils/matrix';
export const getFileSrcUrl = async (
httpUrl: string,
mimeType: string,
encInfo?: EncryptedAttachmentInfo
): Promise<string> => {
if (encInfo) {
if (typeof httpUrl !== 'string') throw new Error('Malformed event');
const encRes = await fetch(httpUrl, { method: 'GET' });
const encData = await encRes.arrayBuffer();
const decryptedBlob = await decryptFile(encData, mimeType, encInfo);
return URL.createObjectURL(decryptedBlob);
}
return httpUrl;
};
export const getSrcFile = async (src: string): Promise<Blob> => {
const res = await fetch(src, { method: 'GET' });
const blob = await res.blob();
return blob;
};

View file

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

View file

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

View file

@ -10,12 +10,15 @@ import {
} from '../../hooks/useIntersectionObserver';
import * as css from './UrlPreviewCard.css';
import { tryDecodeURIComponent } from '../../utils/dom';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
const linkStyles = { color: color.Success.Main };
export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
({ url, ts, ...props }, ref) => {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [previewStatus, loadPreview] = useAsyncCallback(
useCallback(() => mx.getUrlPreview(url, ts), [url, ts, mx])
);
@ -27,7 +30,7 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
if (previewStatus.status === AsyncStatus.Error) return null;
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 (
<>

View file

@ -33,6 +33,8 @@ import { LeaveSpacePrompt } from '../../components/leave-space-prompt';
import { stopPropagation } from '../../utils/keyboard';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { BackRouteHandler } from '../../components/BackRouteHandler';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
type LobbyMenuProps = {
roomId: string;
@ -122,6 +124,7 @@ type LobbyHeaderProps = {
};
export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const space = useSpace();
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
@ -129,7 +132,7 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
const name = useRoomName(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) => {
setMenuAnchor(evt.currentTarget.getBoundingClientRect());

View file

@ -11,15 +11,18 @@ import { RoomTopicViewer } from '../../components/room-topic-viewer';
import * as css from './LobbyHero.css';
import { PageHero } from '../../components/page';
import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
export function LobbyHero() {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const space = useSpace();
const name = useRoomName(space);
const topic = useRoomTopic(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 (
<PageHero

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,7 +13,6 @@ import { useKeyDown } from '../../hooks/useKeyDown';
import { markAsRead } from '../../../client/action/notifications';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoomMembers } from '../../hooks/useRoomMembers';
import { editableActiveElement } from '../../utils/dom';
export function Room() {
const { eventId } = useParams();
@ -29,7 +28,7 @@ export function Room() {
window,
useCallback(
(evt) => {
if (isKeyHotkey('escape', evt) && !editableActiveElement()) {
if (isKeyHotkey('escape', evt)) {
markAsRead(mx, room.roomId);
}
},

View file

@ -56,7 +56,7 @@ import {
} from '../../components/editor';
import { EmojiBoard, EmojiBoardTab } from '../../components/emoji-board';
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 { useFilePicker } from '../../hooks/useFilePicker';
import { useFilePasteHandler } from '../../hooks/useFilePasteHandler';
@ -108,6 +108,7 @@ import { mobileOrTablet } from '../../utils/user-agent';
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
import { ReplyLayout, ThreadIndicator } from '../../components/message';
import { roomToParentsAtom } from '../../state/room/roomToParents';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
interface RoomInputProps {
editor: Editor;
@ -118,6 +119,7 @@ interface RoomInputProps {
export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
({ editor, fileDropContainerRef, roomId, room }, ref) => {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline');
const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown');
const commands = useCommands(mx, room);
@ -368,7 +370,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
};
const handleStickerSelect = async (mxc: string, shortcode: string, label: string) => {
const stickerUrl = mx.mxcUrlToHttp(mxc);
const stickerUrl = mxcUrlToHttp(mx, mxc, useAuthentication);
if (!stickerUrl) return;
const info = await getImageInfo(

View file

@ -17,7 +17,6 @@ import {
EventTimelineSet,
EventTimelineSetHandlerMap,
IContent,
IEncryptedFile,
MatrixClient,
MatrixEvent,
Room,
@ -48,12 +47,7 @@ import {
import { isKeyHotkey } from 'is-hotkey';
import { Opts as LinkifyOpts } from 'linkifyjs';
import { useTranslation } from 'react-i18next';
import {
decryptFile,
eventWithShortcode,
factoryEventSentBy,
getMxIdLocalPart,
} from '../../utils/matrix';
import { eventWithShortcode, factoryEventSentBy, getMxIdLocalPart } from '../../utils/matrix';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useVirtualPaginator, ItemRange } from '../../hooks/useVirtualPaginator';
import { useAlive } from '../../hooks/useAlive';
@ -122,6 +116,7 @@ import { roomToUnreadAtom } from '../../state/room/roomToUnread';
import { useMentionClickHandler } from '../../hooks/useMentionClickHandler';
import { useSpoilerClickHandler } from '../../hooks/useSpoilerClickHandler';
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
const TimelineFloat = as<'div', css.TimelineFloatVariants>(
({ position, className, ...props }, ref) => (
@ -219,18 +214,6 @@ export const getEventIdAbsoluteIndex = (
return baseIndex + eventIndex;
};
export const factoryGetFileSrcUrl =
(httpUrl: string, mimeType: string, encFile?: IEncryptedFile) => async (): Promise<string> => {
if (encFile) {
if (typeof httpUrl !== 'string') throw new Error('Malformed event');
const encRes = await fetch(httpUrl, { method: 'GET' });
const encData = await encRes.arrayBuffer();
const decryptedBlob = await decryptFile(encData, mimeType, encFile);
return URL.createObjectURL(decryptedBlob);
}
return httpUrl;
};
type RoomTimelineProps = {
room: Room;
eventId?: string;
@ -437,6 +420,7 @@ const getRoomUnreadInfo = (room: Room, scrollTo = false) => {
export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimelineProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const encryptedRoom = mx.isRoomEncrypted(room.roomId);
const [messageLayout] = useSetting(settingsAtom, 'messageLayout');
const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing');
@ -511,10 +495,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
() =>
getReactCustomHtmlParser(mx, room.roomId, {
linkifyOpts,
useAuthentication,
handleSpoilerClick: spoilerClickHandler,
handleMentionClick: mentionClickHandler,
}),
[mx, room, linkifyOpts, spoilerClickHandler, mentionClickHandler]
[mx, room, linkifyOpts, spoilerClickHandler, mentionClickHandler, useAuthentication]
);
const parseMemberEvent = useMemberEventParser();
@ -726,6 +711,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const editableEvtId = editableEvt?.getId();
if (!editableEvtId) return;
setEditId(editableEvtId);
evt.preventDefault();
}
},
[mx, room, editor]

View file

@ -36,7 +36,7 @@ import { useSetSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
import { useSpaceOptionally } from '../../hooks/useSpace';
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 * as css from './RoomViewHeader.css';
import { useRoomUnread } from '../../state/hooks/unread';
@ -53,6 +53,7 @@ import { stopPropagation } from '../../utils/keyboard';
import { getMatrixToRoom } from '../../plugins/matrix-to';
import { getViaServers } from '../../plugins/via-servers';
import { BackRouteHandler } from '../../components/BackRouteHandler';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
type RoomMenuProps = {
room: Room;
@ -174,6 +175,7 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
export function RoomViewHeader() {
const navigate = useNavigate();
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const screenSize = useScreenSizeContext();
const room = useRoom();
const space = useSpaceOptionally();
@ -185,7 +187,7 @@ export function RoomViewHeader() {
const avatarMxc = useRoomAvatar(room, mDirects.has(room.roomId));
const name = useRoomName(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');

View file

@ -51,7 +51,7 @@ import {
getMemberAvatarMxc,
getMemberDisplayName,
} 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 { useMatrixClient } from '../../../hooks/useMatrixClient';
import { useRecentEmoji } from '../../../hooks/useRecentEmoji';
@ -67,6 +67,7 @@ import { copyToClipboard } from '../../../utils/dom';
import { stopPropagation } from '../../../utils/keyboard';
import { getMatrixToRoomEvent } from '../../../plugins/matrix-to';
import { getViaServers } from '../../../plugins/via-servers';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;
@ -650,6 +651,7 @@ export const Message = as<'div', MessageProps>(
ref
) => {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const senderId = mEvent.getSender() ?? '';
const [hover, setHover] = useState(false);
const { hoverProps } = useHover({ onHoverChange: setHover });
@ -709,7 +711,7 @@ export const Message = as<'div', MessageProps>(
userId={senderId}
src={
senderAvatarMxc
? mx.mxcUrlToHttp(senderAvatarMxc, 48, 48, 'crop') ?? undefined
? mxcUrlToHttp(mx, senderAvatarMxc, useAuthentication, 48, 48, 'crop') ?? undefined
: undefined
}
alt={senderDisplayName}

View file

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

View file

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

View file

@ -0,0 +1,11 @@
import { useSpecVersions } from './useSpecVersions';
export const useMediaAuthentication = (): boolean => {
const { versions, unstable_features: unstableFeatures } = useSpecVersions();
// Media authentication is introduced in spec version 1.11
const authenticatedMedia =
unstableFeatures?.['org.matrix.msc3916.stable'] || versions.includes('v1.11');
return authenticatedMedia;
};

View file

@ -14,6 +14,7 @@ export const useMentionClickHandler = (roomId: string): ReactEventHandler<HTMLEl
const handleClick: ReactEventHandler<HTMLElement> = useCallback(
(evt) => {
evt.stopPropagation();
evt.preventDefault();
const target = evt.currentTarget;
const mentionId = target.getAttribute('data-mention-id');

View file

@ -1,16 +1,10 @@
import { useMemo } from 'react';
import { ILoginFlow, IPasswordFlow, ISSOFlow, LoginFlow } from 'matrix-js-sdk/lib/@types/auth';
import { WithRequiredProp } from '../../types/utils';
export type Required_SSOFlow = WithRequiredProp<ISSOFlow, 'identity_providers'>;
export const getSSOFlow = (loginFlows: LoginFlow[]): Required_SSOFlow | undefined =>
loginFlows.find(
(flow) =>
(flow.type === 'm.login.sso' || flow.type === 'm.login.cas') &&
'identity_providers' in flow &&
Array.isArray(flow.identity_providers) &&
flow.identity_providers.length > 0
) as Required_SSOFlow | undefined;
export const getSSOFlow = (loginFlows: LoginFlow[]): ISSOFlow | undefined =>
loginFlows.find((flow) => flow.type === 'm.login.sso' || flow.type === 'm.login.cas') as
| ISSOFlow
| undefined;
export const getPasswordFlow = (loginFlows: LoginFlow[]): IPasswordFlow | undefined =>
loginFlows.find((flow) => flow.type === 'm.login.password') as IPasswordFlow;
@ -22,7 +16,7 @@ export const getTokenFlow = (loginFlows: LoginFlow[]): LoginFlow | undefined =>
export type ParsedLoginFlows = {
password?: LoginFlow;
token?: LoginFlow;
sso?: Required_SSOFlow;
sso?: ISSOFlow;
};
export const useParsedLoginFlows = (loginFlows: LoginFlow[]) => {
const parsedFlow: ParsedLoginFlows = useMemo<ParsedLoginFlows>(

View file

@ -1,6 +1,4 @@
import React, {
useState, useMemo, useReducer, useEffect,
} from 'react';
import React, { useState, useMemo, useReducer, useEffect } from 'react';
import PropTypes from 'prop-types';
import './ImagePack.scss';
@ -19,12 +17,16 @@ import ImagePackProfile from './ImagePackProfile';
import ImagePackItem from './ImagePackItem';
import ImagePackUpload from './ImagePackUpload';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
const renameImagePackItem = (shortcode) => new Promise((resolve) => {
const renameImagePackItem = (shortcode) =>
new Promise((resolve) => {
let isCompleted = false;
openReusableDialog(
<Text variant="s1" weight="medium">Rename</Text>,
<Text variant="s1" weight="medium">
Rename
</Text>,
(requestClose) => (
<div style={{ padding: 'var(--sp-normal)' }}>
<form
@ -37,23 +39,19 @@ const renameImagePackItem = (shortcode) => new Promise((resolve) => {
requestClose();
}}
>
<Input
value={shortcode}
name="shortcode"
label="Shortcode"
autoFocus
required
/>
<Input value={shortcode} name="shortcode" label="Shortcode" autoFocus required />
<div style={{ height: 'var(--sp-normal)' }} />
<Button variant="primary" type="submit">Rename</Button>
<Button variant="primary" type="submit">
Rename
</Button>
</form>
</div>
),
() => {
if (!isCompleted) resolve(null);
},
}
);
});
});
function getUsage(usage) {
if (usage.includes('emoticon') && usage.includes('sticker')) return 'both';
@ -79,7 +77,7 @@ function useRoomImagePack(roomId, stateKey) {
const pack = useMemo(() => {
const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
return ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent())
return ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent());
}, [room, stateKey]);
const sendPackContent = (content) => {
@ -96,10 +94,13 @@ function useUserImagePack() {
const mx = useMatrixClient();
const pack = useMemo(() => {
const packEvent = mx.getAccountData('im.ponies.user_emotes');
return ImagePackBuilder.parsePack(mx.getUserId(), packEvent?.getContent() ?? {
return ImagePackBuilder.parsePack(
mx.getUserId(),
packEvent?.getContent() ?? {
pack: { display_name: 'Personal' },
images: {},
})
}
);
}, [mx]);
const sendPackContent = (content) => {
@ -119,10 +120,7 @@ function useImagePackHandles(pack, sendPackContent) {
if (typeof key !== 'string') return undefined;
let newKey = key?.replace(/\s/g, '_');
if (pack.getImages().get(newKey)) {
newKey = suffixRename(
newKey,
(suffixedKey) => pack.getImages().get(suffixedKey),
);
newKey = suffixRename(newKey, (suffixedKey) => pack.getImages().get(suffixedKey));
}
return newKey;
};
@ -163,7 +161,7 @@ function useImagePackHandles(pack, sendPackContent) {
'Delete',
`Are you sure that you want to delete "${key}"?`,
'Delete',
'danger',
'danger'
);
if (!isConfirmed) return;
pack.removeImage(key);
@ -226,6 +224,7 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
const room = mx.getRoom(roomId);
const [viewMore, setViewMore] = useState(false);
const [isGlobal, setIsGlobal] = useState(isGlobalPack(mx, roomId, stateKey));
const useAuthentication = useMediaAuthentication();
const { pack, sendPackContent } = useRoomImagePack(roomId, stateKey);
@ -253,7 +252,7 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
'Delete Pack',
`Are you sure that you want to delete "${pack.displayName}"?`,
'Delete',
'danger',
'danger'
);
if (!isConfirmed) return;
handlePackDelete(stateKey);
@ -264,7 +263,19 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
return (
<div className="image-pack">
<ImagePackProfile
avatarUrl={pack.avatarUrl ? mx.mxcUrlToHttp(pack.avatarUrl, 42, 42, 'crop') : null}
avatarUrl={
pack.avatarUrl
? mx.mxcUrlToHttp(
pack.avatarUrl,
42,
42,
'crop',
undefined,
undefined,
useAuthentication
)
: null
}
displayName={pack.displayName ?? 'Unknown'}
attribution={pack.attribution}
usage={getUsage(pack.usage)}
@ -272,10 +283,8 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
onAvatarChange={canChange ? handleAvatarChange : null}
onEditProfile={canChange ? handleEditProfile : null}
/>
{ canChange && (
<ImagePackUpload onUpload={handleAddItem} />
)}
{ images.length === 0 ? null : (
{canChange && <ImagePackUpload onUpload={handleAddItem} />}
{images.length === 0 ? null : (
<div>
<div className="image-pack__header">
<Text variant="b3">Image</Text>
@ -285,7 +294,15 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
{images.map(([shortcode, image]) => (
<ImagePackItem
key={shortcode}
url={mx.mxcUrlToHttp(image.mxc)}
url={mx.mxcUrlToHttp(
image.mxc,
undefined,
undefined,
undefined,
undefined,
undefined,
useAuthentication
)}
shortcode={shortcode}
usage={getUsage(image.usage)}
onUsageChange={canChange ? handleUsageItem : undefined}
@ -299,14 +316,14 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
<div className="image-pack__footer">
{pack.images.size > 2 && (
<Button onClick={() => setViewMore(!viewMore)}>
{
viewMore
? 'View less'
: `View ${pack.images.size - 2} more`
}
{viewMore ? 'View less' : `View ${pack.images.size - 2} more`}
</Button>
)}
{handlePackDelete && (
<Button variant="danger" onClick={handleDeletePack}>
Delete Pack
</Button>
)}
{ handlePackDelete && <Button variant="danger" onClick={handleDeletePack}>Delete Pack</Button>}
</div>
)}
<div className="image-pack__global">
@ -332,6 +349,7 @@ ImagePack.propTypes = {
function ImagePackUser() {
const mx = useMatrixClient();
const [viewMore, setViewMore] = useState(false);
const useAuthentication = useMediaAuthentication();
const { pack, sendPackContent } = useUserImagePack();
@ -350,7 +368,19 @@ function ImagePackUser() {
return (
<div className="image-pack">
<ImagePackProfile
avatarUrl={pack.avatarUrl ? mx.mxcUrlToHttp(pack.avatarUrl, 42, 42, 'crop') : null}
avatarUrl={
pack.avatarUrl
? mx.mxcUrlToHttp(
pack.avatarUrl,
42,
42,
'crop',
undefined,
undefined,
useAuthentication
)
: null
}
displayName={pack.displayName ?? 'Personal'}
attribution={pack.attribution}
usage={getUsage(pack.usage)}
@ -359,7 +389,7 @@ function ImagePackUser() {
onEditProfile={handleEditProfile}
/>
<ImagePackUpload onUpload={handleAddItem} />
{ images.length === 0 ? null : (
{images.length === 0 ? null : (
<div>
<div className="image-pack__header">
<Text variant="b3">Image</Text>
@ -369,7 +399,15 @@ function ImagePackUser() {
{images.map(([shortcode, image]) => (
<ImagePackItem
key={shortcode}
url={mx.mxcUrlToHttp(image.mxc)}
url={mx.mxcUrlToHttp(
image.mxc,
undefined,
undefined,
undefined,
undefined,
undefined,
useAuthentication
)}
shortcode={shortcode}
usage={getUsage(image.usage)}
onUsageChange={handleUsageItem}
@ -379,14 +417,10 @@ function ImagePackUser() {
))}
</div>
)}
{(pack.images.size > 2) && (
{pack.images.size > 2 && (
<div className="image-pack__footer">
<Button onClick={() => setViewMore(!viewMore)}>
{
viewMore
? 'View less'
: `View ${pack.images.size - 2} more`
}
{viewMore ? 'View less' : `View ${pack.images.size - 2} more`}
</Button>
</div>
)}
@ -435,29 +469,33 @@ function ImagePackGlobal() {
<div className="image-pack-global">
<MenuHeader>Global packs</MenuHeader>
<div>
{
roomIdToStateKeys.size > 0
? [...roomIdToStateKeys].map(([roomId, stateKeys]) => {
{roomIdToStateKeys.size > 0 ? (
[...roomIdToStateKeys].map(([roomId, stateKeys]) => {
const room = mx.getRoom(roomId);
return (
stateKeys.map((stateKey) => {
return stateKeys.map((stateKey) => {
const data = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
const pack = ImagePackBuilder.parsePack(data?.getId(), data?.getContent());
if (!pack) return null;
return (
<div className="image-pack__global" key={pack.id}>
<Checkbox variant="positive" onToggle={() => handleChange(roomId, stateKey)} isActive />
<Checkbox
variant="positive"
onToggle={() => handleChange(roomId, stateKey)}
isActive
/>
<div>
<Text variant="b2">{pack.displayName ?? 'Unknown'}</Text>
<Text variant="b3">{room.name}</Text>
</div>
</div>
);
});
})
);
})
: <div className="image-pack-global__empty"><Text>No global packs</Text></div>
}
) : (
<div className="image-pack-global__empty">
<Text>No global packs</Text>
</div>
)}
</div>
</div>
);

View file

@ -18,11 +18,13 @@ import UserIC from '../../../../public/res/ic/outlined/user.svg';
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { getDMRoomFor } from '../../utils/matrix';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
const [isSearching, updateIsSearching] = useState(false);
const [searchQuery, updateSearchQuery] = useState({});
const [users, updateUsers] = useState([]);
const useAuthentication = useMediaAuthentication();
const [procUsers, updateProcUsers] = useState(new Set()); // proc stands for processing.
const [procUserError, updateUserProcError] = useState(new Map());
@ -222,7 +224,15 @@ function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
key={userId}
avatarSrc={
typeof user.avatar_url === 'string'
? mx.mxcUrlToHttp(user.avatar_url, 42, 42, 'crop')
? mx.mxcUrlToHttp(
user.avatar_url,
42,
42,
'crop',
undefined,
undefined,
useAuthentication
)
: null
}
name={name}

View file

@ -14,15 +14,19 @@ import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
import './ProfileEditor.scss';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
function ProfileEditor({ userId }) {
const [isEditing, setIsEditing] = useState(false);
const mx = useMatrixClient();
const user = mx.getUser(mx.getUserId());
const useAuthentication = useMediaAuthentication();
const displayNameRef = useRef(null);
const [avatarSrc, setAvatarSrc] = useState(
user.avatarUrl ? mx.mxcUrlToHttp(user.avatarUrl, 80, 80, 'crop') : null
user.avatarUrl
? mx.mxcUrlToHttp(user.avatarUrl, 80, 80, 'crop', undefined, undefined, useAuthentication)
: null
);
const [username, setUsername] = useState(user.displayName);
const [disabled, setDisabled] = useState(true);
@ -31,13 +35,25 @@ function ProfileEditor({ userId }) {
let isMounted = true;
mx.getProfileInfo(mx.getUserId()).then((info) => {
if (!isMounted) return;
setAvatarSrc(info.avatar_url ? mx.mxcUrlToHttp(info.avatar_url, 80, 80, 'crop') : null);
setAvatarSrc(
info.avatar_url
? mx.mxcUrlToHttp(
info.avatar_url,
80,
80,
'crop',
undefined,
undefined,
useAuthentication
)
: null
);
setUsername(info.displayname);
});
return () => {
isMounted = false;
};
}, [mx, userId]);
}, [mx, userId, useAuthentication]);
const handleAvatarUpload = async (url) => {
if (url === null) {
@ -54,7 +70,7 @@ function ProfileEditor({ userId }) {
return;
}
mx.setAvatarUrl(url);
setAvatarSrc(mx.mxcUrlToHttp(url, 80, 80, 'crop'));
setAvatarSrc(mx.mxcUrlToHttp(url, 80, 80, 'crop', undefined, undefined, useAuthentication));
};
const saveDisplayName = () => {

View file

@ -36,6 +36,7 @@ import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { getDMRoomFor } from '../../utils/matrix';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
function ModerationTools({ roomId, userId }) {
const mx = useMatrixClient();
@ -329,6 +330,7 @@ function useRerenderOnProfileChange(roomId, userId) {
function ProfileViewer() {
const [isOpen, roomId, userId, closeDialog, handleAfterClose] = useToggleDialog();
useRerenderOnProfileChange(roomId, userId);
const useAuthentication = useMediaAuthentication();
const mx = useMatrixClient();
const room = mx.getRoom(roomId);
@ -338,7 +340,9 @@ function ProfileViewer() {
const username = roomMember ? getUsernameOfRoomMember(roomMember) : getUsername(mx, userId);
const avatarMxc = roomMember?.getMxcAvatarUrl?.() || mx.getUser(userId)?.avatarUrl;
const avatarUrl =
avatarMxc && avatarMxc !== 'null' ? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop') : null;
avatarMxc && avatarMxc !== 'null'
? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop', undefined, undefined, useAuthentication)
: null;
const powerLevel = roomMember?.powerLevel || 0;
const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel || 0;

View file

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

View file

@ -4,30 +4,35 @@ import React, { useMemo } from 'react';
import { useAutoDiscoveryInfo } from '../../hooks/useAutoDiscoveryInfo';
type SSOLoginProps = {
providers: IIdentityProvider[];
asIcons?: boolean;
providers?: IIdentityProvider[];
redirectUrl: string;
saveScreenSpace?: boolean;
};
export function SSOLogin({ providers, redirectUrl, asIcons }: SSOLoginProps) {
export function SSOLogin({ providers, redirectUrl, saveScreenSpace }: SSOLoginProps) {
const discovery = useAutoDiscoveryInfo();
const baseUrl = discovery['m.homeserver'].base_url;
const mx = useMemo(() => createClient({ baseUrl }), [baseUrl]);
const getSSOIdUrl = (ssoId: string): string => mx.getSsoLoginUrl(redirectUrl, 'sso', ssoId);
const getSSOIdUrl = (ssoId?: string): string => mx.getSsoLoginUrl(redirectUrl, 'sso', ssoId);
const anyAsBtn = providers.find(
const withoutIcon = providers
? providers.find(
(provider) => !provider.icon || !mx.mxcUrlToHttp(provider.icon, 96, 96, 'crop', false)
);
)
: true;
const renderAsIcons = withoutIcon ? false : saveScreenSpace && providers && providers.length > 2;
return (
<Box justifyContent="Center" gap="600" wrap="Wrap">
{providers.map((provider) => {
{providers ? (
providers.map((provider) => {
const { id, name, icon } = provider;
const iconUrl = icon && mx.mxcUrlToHttp(icon, 96, 96, 'crop', false);
const buttonTitle = `Continue with ${name}`;
if (!anyAsBtn && iconUrl && asIcons) {
if (renderAsIcons) {
return (
<Avatar
style={{ cursor: 'pointer' }}
@ -38,7 +43,7 @@ export function SSOLogin({ providers, redirectUrl, asIcons }: SSOLoginProps) {
size="300"
radii="300"
>
<AvatarImage src={iconUrl} alt={name} title={buttonTitle} />
<AvatarImage src={iconUrl!} alt={name} title={buttonTitle} />
</Avatar>
);
}
@ -66,7 +71,22 @@ export function SSOLogin({ providers, redirectUrl, asIcons }: SSOLoginProps) {
</Text>
</Button>
);
})}
})
) : (
<Button
style={{ width: '100%' }}
as="a"
href={getSSOIdUrl()}
size="500"
variant="Secondary"
fill="Soft"
outlined
>
<Text align="Center" size="B500" truncate>
Continue with SSO
</Text>
</Button>
)}
</Box>
);
}

View file

@ -76,9 +76,7 @@ export function Login() {
<SSOLogin
providers={parsedFlows.sso.identity_providers}
redirectUrl={ssoRedirectUrl}
asIcons={
parsedFlows.password !== undefined && parsedFlows.sso.identity_providers.length > 2
}
saveScreenSpace={parsedFlows.password !== undefined}
/>
<span data-spacing-node />
</>

View file

@ -83,10 +83,7 @@ export function Register() {
<SSOLogin
providers={sso.identity_providers}
redirectUrl={ssoRedirectUrl}
asIcons={
registerFlows.status === RegisterFlowStatus.FlowRequired &&
sso.identity_providers.length > 2
}
saveScreenSpace={registerFlows.status === RegisterFlowStatus.FlowRequired}
/>
<span data-spacing-node />
</>

View file

@ -22,9 +22,10 @@ import {
isNotificationEvent,
} from '../../utils/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 { useInboxNotificationsSelected } from '../../hooks/router/useInbox';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
function SystemEmojiFeature() {
const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
@ -132,6 +133,7 @@ function MessageNotifications() {
const notifRef = useRef<Notification>();
const unreadCacheRef = useRef<Map<string, UnreadInfo>>(new Map());
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [showNotifications] = useSetting(settingsAtom, 'showNotifications');
const [notificationSound] = useSetting(settingsAtom, 'isNotificationSounds');
@ -216,7 +218,7 @@ function MessageNotifications() {
notify({
roomName: room.name ?? 'Unknown',
roomAvatar: avatarMxc
? mx.mxcUrlToHttp(avatarMxc, 96, 96, 'crop') ?? undefined
? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined
: undefined,
username: getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender) ?? sender,
roomId: room.roomId,
@ -240,6 +242,7 @@ function MessageNotifications() {
playSound,
notify,
selectedRoomId,
useAuthentication,
]);
return (

View file

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

View file

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

View file

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

View file

@ -86,6 +86,8 @@ import { openInviteUser, openSpaceSettings } from '../../../../client/action/nav
import { stopPropagation } from '../../../utils/keyboard';
import { getMatrixToRoom } from '../../../plugins/matrix-to';
import { getViaServers } from '../../../plugins/via-servers';
import { getRoomAvatarUrl } from '../../../utils/room';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
type SpaceMenuProps = {
room: Room;
@ -379,6 +381,7 @@ function SpaceTab({
onUnpin,
}: SpaceTabProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const targetRef = useRef<HTMLDivElement>(null);
const spaceDraggable: SidebarDraggable = useMemo(
@ -431,7 +434,7 @@ function SpaceTab({
>
<RoomAvatar
roomId={space.roomId}
src={space.getAvatarUrl(mx.baseUrl, 96, 96, 'crop') ?? undefined}
src={getRoomAvatarUrl(mx, space, 96, useAuthentication) ?? undefined}
alt={space.name}
renderFallback={() => (
<Text size={folder ? 'H6' : 'H4'}>{nameInitials(space.name, 2)}</Text>
@ -524,6 +527,7 @@ function ClosedSpaceFolder({
disabled,
}: ClosedSpaceFolderProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const handlerRef = useRef<HTMLDivElement>(null);
const spaceDraggable: FolderDraggable = useMemo(() => ({ folder }), [folder]);
@ -556,7 +560,7 @@ function ClosedSpaceFolder({
<SidebarAvatar key={sId} size="200" radii="300">
<RoomAvatar
roomId={space.roomId}
src={space.getAvatarUrl(mx.baseUrl, 96, 96, 'crop') ?? undefined}
src={getRoomAvatarUrl(mx, space, 96, useAuthentication) ?? undefined}
alt={space.name}
renderFallback={() => (
<Text size="Inherit">

View file

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

View file

@ -14,7 +14,12 @@ import { IntermediateRepresentation, Opts as LinkifyOpts, OptFn } from 'linkifyj
import Linkify from 'linkify-react';
import { ErrorBoundary } from 'react-error-boundary';
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 { EMOJI_PATTERN, URL_NEG_LB } from '../utils/regex';
import { getHexcodeForEmoji, getShortcodeFor, getEmojiUrl, isUsingTwemoji } from './emoji';
@ -44,7 +49,8 @@ export const LINKIFY_OPTS: LinkifyOpts = {
};
export const makeMentionCustomProps = (
handleMentionClick?: ReactEventHandler<HTMLElement>
handleMentionClick?: ReactEventHandler<HTMLElement>,
content?: string
): ComponentPropsWithoutRef<'a'> => ({
style: { cursor: 'pointer' },
target: '_blank',
@ -53,6 +59,7 @@ export const makeMentionCustomProps = (
tabIndex: handleMentionClick ? 0 : -1,
onKeyDown: handleMentionClick ? onEnterOrSpace(handleMentionClick) : undefined,
onClick: handleMentionClick,
children: content,
});
export const renderMatrixMention = (
@ -86,6 +93,8 @@ export const renderMatrixMention = (
isRoomAlias(roomIdOrAlias) ? getCanonicalAliasRoomId(mx, roomIdOrAlias) : roomIdOrAlias
);
const fallbackContent = mentionRoom ? `#${mentionRoom.name}` : roomIdOrAlias;
return (
<a
href={href}
@ -96,7 +105,7 @@ export const renderMatrixMention = (
data-mention-id={mentionRoom?.roomId ?? roomIdOrAlias}
data-mention-via={viaServers?.join(',')}
>
{mentionRoom ? `#${mentionRoom.name}` : roomIdOrAlias}
{customProps.children ? customProps.children : fallbackContent}
</a>
);
}
@ -119,7 +128,9 @@ export const renderMatrixMention = (
data-mention-event-id={eventId}
data-mention-via={viaServers?.join(',')}
>
Message: {mentionRoom ? `#${mentionRoom.name}` : roomIdOrAlias}
{customProps.children
? customProps.children
: `Message: ${mentionRoom ? `#${mentionRoom.name}` : roomIdOrAlias}`}
</a>
);
}
@ -201,6 +212,7 @@ export const getReactCustomHtmlParser = (
highlightRegex?: RegExp;
handleSpoilerClick?: ReactEventHandler<HTMLElement>;
handleMentionClick?: ReactEventHandler<HTMLElement>;
useAuthentication?: boolean;
}
): HTMLReactParserOptions => {
const opts: HTMLReactParserOptions = {
@ -336,12 +348,17 @@ export const getReactCustomHtmlParser = (
}
if (name === 'a' && testMatrixTo(tryDecodeURIComponent(props.href))) {
const content = children.find((child) => !(child instanceof DOMText))
? undefined
: children.map((c) => (c instanceof DOMText ? c.data : '')).join();
const mention = renderMatrixMention(
mx,
roomId,
tryDecodeURIComponent(props.href),
makeMentionCustomProps(params.handleMentionClick)
makeMentionCustomProps(params.handleMentionClick, content)
);
if (mention) return mention;
}
@ -363,7 +380,7 @@ export const getReactCustomHtmlParser = (
}
if (name === 'img') {
const htmlSrc = mx.mxcUrlToHttp(props.src);
const htmlSrc = mxcUrlToHttp(mx, props.src, params.useAuthentication);
if (htmlSrc && props.src.startsWith('mxc://') === false) {
return (
<a href={htmlSrc} target="_blank" rel="noreferrer noopener">

View file

@ -253,3 +253,40 @@ export const removeRoomIdFromMDirect = async (mx: MatrixClient, roomId: string):
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
);
export const downloadMedia = async (src: string): Promise<Blob> => {
// this request is authenticated by service worker
const res = await fetch(src, { method: 'GET' });
const blob = await res.blob();
return blob;
};
export const downloadEncryptedMedia = async (
src: string,
decryptContent: (buf: ArrayBuffer) => Promise<Blob>
): Promise<Blob> => {
const encryptedContent = await downloadMedia(src);
const decryptedContent = await decryptContent(await encryptedContent.arrayBuffer());
return decryptedContent;
};

View file

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

View file

@ -244,11 +244,7 @@ async function unignore(mx, userIds) {
}
async function setPowerLevel(mx, roomId, userId, powerLevel) {
const room = mx.getRoom(roomId);
const powerlevelEvent = room.currentState.getStateEvents('m.room.power_levels')[0];
const result = await mx.setPowerLevel(roomId, userId, powerLevel, powerlevelEvent);
const result = await mx.setPowerLevel(roomId, userId, powerLevel);
return result;
}

View file

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

View file

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

View file

@ -5,7 +5,7 @@ export const onLightFontWeight = createTheme(config.fontWeight, {
W100: '100',
W200: '200',
W300: '300',
W400: '420',
W400: '400',
W500: '500',
W600: '600',
W700: '700',
@ -17,10 +17,10 @@ export const onDarkFontWeight = createTheme(config.fontWeight, {
W100: '100',
W200: '200',
W300: '300',
W400: '350',
W500: '450',
W600: '550',
W700: '650',
W800: '750',
W900: '850',
W400: '400',
W500: '500',
W600: '600',
W700: '700',
W800: '800',
W900: '900',
});

View file

@ -12,6 +12,7 @@ import './index.scss';
import settings from './client/state/settings';
import { trimTrailingSlash } from './app/utils/common';
import App from './app/pages/App';
// import i18n (needs to be bundled ;))
@ -20,6 +21,26 @@ import './app/i18n';
document.body.classList.add(configClass, varsClass);
settings.applyTheme();
// Register Service Worker
if ('serviceWorker' in navigator) {
const swUrl =
import.meta.env.MODE === 'production'
? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js`
: `/dev-sw.js?dev-sw`;
navigator.serviceWorker.register(swUrl);
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') ?? undefined;
event.source!.postMessage({
responseKey: event.data.responseKey,
token,
});
}
});
}
const mountApp = () => {
const rootContainer = document.getElementById('root');

52
src/sw.ts Normal file
View file

@ -0,0 +1,52 @@
/// <reference lib="WebWorker" />
export type {};
declare const self: ServiceWorkerGlobalScope;
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}`,
},
cache: 'default',
};
}
self.addEventListener('activate', (event: ExtendableEvent) => {
event.waitUntil(clients.claim());
});
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 self.clients.get(event.clientId);
let token: string | undefined;
if (client) token = await askForAccessToken(client);
return fetch(url, fetchConfig(token));
})()
);
});

View file

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

View file

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