forked from github/cinny
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6347640a35 | ||
|
|
f2d8ad0b6b | ||
|
|
739786d9ab | ||
|
|
f642809939 | ||
|
|
02106a99b9 | ||
|
|
df3a3ba789 | ||
|
|
cd80d4c9e8 | ||
|
|
dab44edef2 | ||
|
|
ed0ad61bc4 | ||
|
|
b2cb717178 | ||
|
|
7a9f6d2223 | ||
|
|
a9022184fc | ||
|
|
826b3c2997 | ||
|
|
2e6c5f7c04 | ||
|
|
2d6730de56 | ||
|
|
b6cc0e3077 | ||
|
|
91c8731940 | ||
|
|
1f03891b25 | ||
|
|
9ff15b8b03 | ||
|
|
170f5cd473 |
10
.github/workflows/build-pull-request.yml
vendored
10
.github/workflows/build-pull-request.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
|||||||
PR_NUMBER: ${{github.event.number}}
|
PR_NUMBER: ${{github.event.number}}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.2.0
|
uses: actions/checkout@v6.0.2
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: 20.12.2
|
node-version: 24.13.1
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||||
run: npm run build
|
run: npm run build
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4.6.2
|
uses: actions/upload-artifact@v6.0.0
|
||||||
with:
|
with:
|
||||||
name: preview
|
name: preview
|
||||||
path: dist
|
path: dist
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
- name: Save pr number
|
- name: Save pr number
|
||||||
run: echo ${PR_NUMBER} > ./pr.txt
|
run: echo ${PR_NUMBER} > ./pr.txt
|
||||||
- name: Upload pr number
|
- name: Upload pr number
|
||||||
uses: actions/upload-artifact@v4.6.2
|
uses: actions/upload-artifact@v6.0.0
|
||||||
with:
|
with:
|
||||||
name: pr
|
name: pr
|
||||||
path: ./pr.txt
|
path: ./pr.txt
|
||||||
|
|||||||
5
.github/workflows/docker-pr.yml
vendored
5
.github/workflows/docker-pr.yml
vendored
@@ -5,15 +5,16 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'Dockerfile'
|
- 'Dockerfile'
|
||||||
- '.github/workflows/docker-pr.yml'
|
- '.github/workflows/docker-pr.yml'
|
||||||
|
- '.github/workflows/prod-deploy.yml'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker-build:
|
docker-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.2.0
|
uses: actions/checkout@v6.0.2
|
||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
uses: docker/build-push-action@v6.18.0
|
uses: docker/build-push-action@v6.19.2
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: false
|
push: false
|
||||||
|
|||||||
2
.github/workflows/lockfile.yml
vendored
2
.github/workflows/lockfile.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.2.0
|
uses: actions/checkout@v6.0.2
|
||||||
- name: NPM Lockfile Changes
|
- name: NPM Lockfile Changes
|
||||||
uses: codepunkt/npm-lockfile-changes@b40543471c36394409466fdb277a73a0856d7891
|
uses: codepunkt/npm-lockfile-changes@b40543471c36394409466fdb277a73a0856d7891
|
||||||
with:
|
with:
|
||||||
|
|||||||
6
.github/workflows/netlify-dev.yml
vendored
6
.github/workflows/netlify-dev.yml
vendored
@@ -11,11 +11,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.2.0
|
uses: actions/checkout@v6.0.2
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: 20.12.2
|
node-version: 24.13.1
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|||||||
20
.github/workflows/prod-deploy.yml
vendored
20
.github/workflows/prod-deploy.yml
vendored
@@ -10,11 +10,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.2.0
|
uses: actions/checkout@v6.0.2
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: 20.12.2
|
node-version: 24.13.1
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
@@ -66,31 +66,31 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.2.0
|
uses: actions/checkout@v6.0.2
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.6.0
|
uses: docker/setup-qemu-action@v3.7.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.11.1
|
uses: docker/setup-buildx-action@v3.12.0
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3.6.0
|
uses: docker/login-action@v3.7.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Login to the Container registry
|
- name: Login to the Container registry
|
||||||
uses: docker/login-action@v3.6.0
|
uses: docker/login-action@v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5.8.0
|
uses: docker/metadata-action@v5.10.0
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_USERNAME }}/cinny
|
${{ secrets.DOCKER_USERNAME }}/cinny
|
||||||
ghcr.io/${{ github.repository }}
|
ghcr.io/${{ github.repository }}
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v6.18.0
|
uses: docker/build-push-action@v6.19.2
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
## Builder
|
## Builder
|
||||||
FROM node:20.12.2-alpine3.18 as builder
|
FROM node:24.13.1-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ RUN npm run build
|
|||||||
|
|
||||||
|
|
||||||
## App
|
## App
|
||||||
FROM nginx:1.29.3-alpine
|
FROM nginx:1.29.5-alpine
|
||||||
|
|
||||||
COPY --from=builder /src/dist /app
|
COPY --from=builder /src/dist /app
|
||||||
COPY --from=builder /src/docker-nginx.conf /etc/nginx/conf.d/default.conf
|
COPY --from=builder /src/docker-nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ mxFo+ioe/ABCufSmyqFye0psX3Sp
|
|||||||
|
|
||||||
## Local development
|
## Local development
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> We recommend using a version manager as versions change very quickly. You will likely need to switch between multiple Node.js versions based on the needs of different projects you're working on. [NVM on windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) on Windows and [nvm](https://github.com/nvm-sh/nvm) on Linux/macOS are pretty good choices. Recommended nodejs version is Iron LTS (v20).
|
> We recommend using a version manager as versions change very quickly. You will likely need to switch between multiple Node.js versions based on the needs of different projects you're working on. [NVM on windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) on Windows and [nvm](https://github.com/nvm-sh/nvm) on Linux/macOS are pretty good choices. Recommended nodejs version is Krypton LTS (v24.13.1).
|
||||||
|
|
||||||
Execute the following commands to start a development server:
|
Execute the following commands to start a development server:
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
30
package-lock.json
generated
30
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "cinny",
|
"name": "cinny",
|
||||||
"version": "4.10.3",
|
"version": "4.10.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cinny",
|
"name": "cinny",
|
||||||
"version": "4.10.3",
|
"version": "4.10.5",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
|
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"emojibase-data": "15.3.2",
|
"emojibase-data": "15.3.2",
|
||||||
"file-saver": "2.0.5",
|
"file-saver": "2.0.5",
|
||||||
"focus-trap-react": "10.0.2",
|
"focus-trap-react": "10.0.2",
|
||||||
"folds": "2.5.0",
|
"folds": "2.6.1",
|
||||||
"html-dom-parser": "4.0.0",
|
"html-dom-parser": "4.0.0",
|
||||||
"html-react-parser": "4.2.0",
|
"html-react-parser": "4.2.0",
|
||||||
"i18next": "23.12.2",
|
"i18next": "23.12.2",
|
||||||
@@ -41,8 +41,8 @@
|
|||||||
"immer": "9.0.16",
|
"immer": "9.0.16",
|
||||||
"is-hotkey": "0.2.0",
|
"is-hotkey": "0.2.0",
|
||||||
"jotai": "2.6.0",
|
"jotai": "2.6.0",
|
||||||
"linkify-react": "4.1.3",
|
"linkify-react": "4.3.2",
|
||||||
"linkifyjs": "4.1.3",
|
"linkifyjs": "4.3.2",
|
||||||
"matrix-js-sdk": "38.2.0",
|
"matrix-js-sdk": "38.2.0",
|
||||||
"millify": "6.1.0",
|
"millify": "6.1.0",
|
||||||
"pdfjs-dist": "4.2.67",
|
"pdfjs-dist": "4.2.67",
|
||||||
@@ -7158,9 +7158,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/folds": {
|
"node_modules/folds": {
|
||||||
"version": "2.5.0",
|
"version": "2.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/folds/-/folds-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/folds/-/folds-2.6.1.tgz",
|
||||||
"integrity": "sha512-UJhvXAQ1XnZ9w10KJwSW+frvzzWE/zcF0dH3fDVCD70RFHAxwEi0UkkVS8CaZGxZF2Wvt3qTJyTS5LW3LwwUAw==",
|
"integrity": "sha512-0L1ZSqwjFSg2fesa//C4DgP47Vp/KqDuzjAaOEYN21AvoptyVI+6OEXWrtIdE8DPQCZYr0bV+tqbrLyA6uAhaw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@vanilla-extract/css": "1.9.2",
|
"@vanilla-extract/css": "1.9.2",
|
||||||
@@ -8492,18 +8492,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/linkify-react": {
|
"node_modules/linkify-react": {
|
||||||
"version": "4.1.3",
|
"version": "4.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/linkify-react/-/linkify-react-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/linkify-react/-/linkify-react-4.3.2.tgz",
|
||||||
"integrity": "sha512-rhI3zM/fxn5BfRPHfi4r9N7zgac4vOIxub1wHIWXLA5ENTMs+BGaIaFO1D1PhmxgwhIKmJz3H7uCP0Dg5JwSlA==",
|
"integrity": "sha512-mi744h1hf+WDsr+paJgSBBgYNLMWNSHyM9V9LVUo03RidNGdw1VpI7Twnt+K3pEh3nIzB4xiiAgZxpd61ItKpQ==",
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"linkifyjs": "^4.0.0",
|
"linkifyjs": "^4.0.0",
|
||||||
"react": ">= 15.0.0"
|
"react": ">= 15.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/linkifyjs": {
|
"node_modules/linkifyjs": {
|
||||||
"version": "4.1.3",
|
"version": "4.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz",
|
||||||
"integrity": "sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg=="
|
"integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cinny",
|
"name": "cinny",
|
||||||
"version": "4.10.3",
|
"version": "4.10.5",
|
||||||
"description": "Yet another matrix client",
|
"description": "Yet another matrix client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"emojibase-data": "15.3.2",
|
"emojibase-data": "15.3.2",
|
||||||
"file-saver": "2.0.5",
|
"file-saver": "2.0.5",
|
||||||
"focus-trap-react": "10.0.2",
|
"focus-trap-react": "10.0.2",
|
||||||
"folds": "2.5.0",
|
"folds": "2.6.1",
|
||||||
"html-dom-parser": "4.0.0",
|
"html-dom-parser": "4.0.0",
|
||||||
"html-react-parser": "4.2.0",
|
"html-react-parser": "4.2.0",
|
||||||
"i18next": "23.12.2",
|
"i18next": "23.12.2",
|
||||||
@@ -52,8 +52,8 @@
|
|||||||
"immer": "9.0.16",
|
"immer": "9.0.16",
|
||||||
"is-hotkey": "0.2.0",
|
"is-hotkey": "0.2.0",
|
||||||
"jotai": "2.6.0",
|
"jotai": "2.6.0",
|
||||||
"linkify-react": "4.1.3",
|
"linkify-react": "4.3.2",
|
||||||
"linkifyjs": "4.1.3",
|
"linkifyjs": "4.3.2",
|
||||||
"matrix-js-sdk": "38.2.0",
|
"matrix-js-sdk": "38.2.0",
|
||||||
"millify": "6.1.0",
|
"millify": "6.1.0",
|
||||||
"pdfjs-dist": "4.2.67",
|
"pdfjs-dist": "4.2.67",
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ export const createRoomParentState = (parent: Room) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const createSpacePowerLevelsOverride = () => ({
|
||||||
|
events_default: 50,
|
||||||
|
});
|
||||||
|
|
||||||
export const createRoomEncryptionState = () => ({
|
export const createRoomEncryptionState = () => ({
|
||||||
type: 'm.room.encryption',
|
type: 'm.room.encryption',
|
||||||
state_key: '',
|
state_key: '',
|
||||||
@@ -121,6 +125,10 @@ export const createRoom = async (mx: MatrixClient, data: CreateRoomData): Promis
|
|||||||
initial_state: initialState,
|
initial_state: initialState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (data.type === RoomType.Space) {
|
||||||
|
options.power_level_content_override = createSpacePowerLevelsOverride();
|
||||||
|
}
|
||||||
|
|
||||||
const result = await mx.createRoom(options);
|
const result = await mx.createRoom(options);
|
||||||
|
|
||||||
if (data.parent) {
|
if (data.parent) {
|
||||||
|
|||||||
@@ -26,7 +26,12 @@ export function SSOStage({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleMessage = (evt: MessageEvent) => {
|
const handleMessage = (evt: MessageEvent) => {
|
||||||
if (ssoWindow && evt.data === 'authDone' && evt.source === ssoWindow) {
|
if (
|
||||||
|
evt.origin === new URL(ssoRedirectURL).origin &&
|
||||||
|
ssoWindow &&
|
||||||
|
evt.data === 'authDone' &&
|
||||||
|
evt.source === ssoWindow
|
||||||
|
) {
|
||||||
ssoWindow.close();
|
ssoWindow.close();
|
||||||
setSSOWindow(undefined);
|
setSSOWindow(undefined);
|
||||||
handleSubmit();
|
handleSubmit();
|
||||||
@@ -37,7 +42,7 @@ export function SSOStage({
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('message', handleMessage);
|
window.removeEventListener('message', handleMessage);
|
||||||
};
|
};
|
||||||
}, [ssoWindow, handleSubmit]);
|
}, [ssoWindow, handleSubmit, ssoRedirectURL]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
|
|||||||
@@ -30,7 +30,15 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
|
|||||||
if (previewStatus.status === AsyncStatus.Error) return null;
|
if (previewStatus.status === AsyncStatus.Error) return null;
|
||||||
|
|
||||||
const renderContent = (prev: IPreviewUrlResponse) => {
|
const renderContent = (prev: IPreviewUrlResponse) => {
|
||||||
const imgUrl = mxcUrlToHttp(mx, prev['og:image'] || '', useAuthentication, 256, 256, 'scale', false);
|
const imgUrl = mxcUrlToHttp(
|
||||||
|
mx,
|
||||||
|
prev['og:image'] || '',
|
||||||
|
useAuthentication,
|
||||||
|
256,
|
||||||
|
256,
|
||||||
|
'scale',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -42,7 +50,7 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
|
|||||||
as="a"
|
as="a"
|
||||||
href={url}
|
href={url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="no-referrer"
|
rel="noreferrer"
|
||||||
size="T200"
|
size="T200"
|
||||||
priority="300"
|
priority="300"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -177,6 +177,13 @@ export const usePermissionGroups = (): PermissionGroup[] => {
|
|||||||
const otherSettingsGroup: PermissionGroup = {
|
const otherSettingsGroup: PermissionGroup = {
|
||||||
name: 'Other',
|
name: 'Other',
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
location: {
|
||||||
|
state: true,
|
||||||
|
key: StateEvent.PoniesRoomEmotes,
|
||||||
|
},
|
||||||
|
name: 'Manage Emojis & Stickers',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
location: {
|
location: {
|
||||||
state: true,
|
state: true,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export function About({ requestClose }: AboutProps) {
|
|||||||
<Box direction="Column" gap="100">
|
<Box direction="Column" gap="100">
|
||||||
<Box gap="100" alignItems="End">
|
<Box gap="100" alignItems="End">
|
||||||
<Text size="H3">Cinny</Text>
|
<Text size="H3">Cinny</Text>
|
||||||
<Text size="T200">v4.10.3</Text>
|
<Text size="T200">v4.10.5</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Text>Yet another matrix client.</Text>
|
<Text>Yet another matrix client.</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -125,6 +125,13 @@ export const usePermissionGroups = (): PermissionGroup[] => {
|
|||||||
const otherSettingsGroup: PermissionGroup = {
|
const otherSettingsGroup: PermissionGroup = {
|
||||||
name: 'Other',
|
name: 'Other',
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
location: {
|
||||||
|
state: true,
|
||||||
|
key: StateEvent.PoniesRoomEmotes,
|
||||||
|
},
|
||||||
|
name: 'Manage Emojis & Stickers',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
location: {
|
location: {
|
||||||
state: true,
|
state: true,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const useSelectedSpace = (): string | undefined => {
|
|||||||
|
|
||||||
export const useSpaceLobbySelected = (spaceIdOrAlias: string): boolean => {
|
export const useSpaceLobbySelected = (spaceIdOrAlias: string): boolean => {
|
||||||
const match = useMatch({
|
const match = useMatch({
|
||||||
path: getSpaceLobbyPath(spaceIdOrAlias),
|
path: decodeURIComponent(getSpaceLobbyPath(spaceIdOrAlias)),
|
||||||
caseSensitive: true,
|
caseSensitive: true,
|
||||||
end: false,
|
end: false,
|
||||||
});
|
});
|
||||||
@@ -28,7 +28,7 @@ export const useSpaceLobbySelected = (spaceIdOrAlias: string): boolean => {
|
|||||||
|
|
||||||
export const useSpaceSearchSelected = (spaceIdOrAlias: string): boolean => {
|
export const useSpaceSearchSelected = (spaceIdOrAlias: string): boolean => {
|
||||||
const match = useMatch({
|
const match = useMatch({
|
||||||
path: getSpaceSearchPath(spaceIdOrAlias),
|
path: decodeURIComponent(getSpaceSearchPath(spaceIdOrAlias)),
|
||||||
caseSensitive: true,
|
caseSensitive: true,
|
||||||
end: false,
|
end: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ import { Create } from './client/create';
|
|||||||
import { CreateSpaceModalRenderer } from '../features/create-space';
|
import { CreateSpaceModalRenderer } from '../features/create-space';
|
||||||
import { SearchModalRenderer } from '../features/search';
|
import { SearchModalRenderer } from '../features/search';
|
||||||
import { getFallbackSession } from '../state/sessions';
|
import { getFallbackSession } from '../state/sessions';
|
||||||
import { pushSessionToSW } from '../../sw-session';
|
|
||||||
|
|
||||||
export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => {
|
export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => {
|
||||||
const { hashRouter } = clientConfig;
|
const { hashRouter } = clientConfig;
|
||||||
@@ -116,7 +115,6 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
|||||||
if (afterLoginPath) setAfterLoginRedirectPath(afterLoginPath);
|
if (afterLoginPath) setAfterLoginRedirectPath(afterLoginPath);
|
||||||
return redirect(getLoginPath());
|
return redirect(getLoginPath());
|
||||||
}
|
}
|
||||||
pushSessionToSW(session.baseUrl, session.accessToken);
|
|
||||||
return null;
|
return null;
|
||||||
}}
|
}}
|
||||||
element={
|
element={
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function AuthFooter() {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
v4.10.3
|
v4.10.5
|
||||||
</Text>
|
</Text>
|
||||||
<Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer">
|
<Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer">
|
||||||
Twitter
|
Twitter
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function WelcomePage() {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
>
|
>
|
||||||
v4.10.3
|
v4.10.5
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,17 +34,14 @@ if ('serviceWorker' in navigator) {
|
|||||||
|
|
||||||
navigator.serviceWorker.register(swUrl).then(sendSessionToSW);
|
navigator.serviceWorker.register(swUrl).then(sendSessionToSW);
|
||||||
navigator.serviceWorker.ready.then(sendSessionToSW);
|
navigator.serviceWorker.ready.then(sendSessionToSW);
|
||||||
window.addEventListener('load', sendSessionToSW);
|
|
||||||
|
|
||||||
// When returning from background
|
navigator.serviceWorker.addEventListener('message', (ev) => {
|
||||||
document.addEventListener('visibilitychange', () => {
|
const { type } = ev.data ?? {};
|
||||||
if (document.visibilityState === 'visible') {
|
|
||||||
|
if (type === 'requestSession') {
|
||||||
sendSessionToSW();
|
sendSessionToSW();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// When restored from bfcache (important on iOS)
|
|
||||||
window.addEventListener('pageshow', sendSessionToSW);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mountApp = () => {
|
const mountApp = () => {
|
||||||
|
|||||||
126
src/sw.ts
126
src/sw.ts
@@ -3,14 +3,6 @@
|
|||||||
export type {};
|
export type {};
|
||||||
declare const self: ServiceWorkerGlobalScope;
|
declare const self: ServiceWorkerGlobalScope;
|
||||||
|
|
||||||
self.addEventListener('install', () => {
|
|
||||||
self.skipWaiting();
|
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('activate', (event: ExtendableEvent) => {
|
|
||||||
event.waitUntil(self.clients.claim());
|
|
||||||
});
|
|
||||||
|
|
||||||
type SessionInfo = {
|
type SessionInfo = {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
@@ -21,6 +13,9 @@ type SessionInfo = {
|
|||||||
*/
|
*/
|
||||||
const sessions = new Map<string, SessionInfo>();
|
const sessions = new Map<string, SessionInfo>();
|
||||||
|
|
||||||
|
const clientToResolve = new Map<string, (value: SessionInfo | undefined) => void>();
|
||||||
|
const clientToSessionPromise = new Map<string, Promise<SessionInfo | undefined>>();
|
||||||
|
|
||||||
async function cleanupDeadClients() {
|
async function cleanupDeadClients() {
|
||||||
const activeClients = await self.clients.matchAll();
|
const activeClients = await self.clients.matchAll();
|
||||||
const activeIds = new Set(activeClients.map((c) => c.id));
|
const activeIds = new Set(activeClients.map((c) => c.id));
|
||||||
@@ -28,10 +23,72 @@ async function cleanupDeadClients() {
|
|||||||
Array.from(sessions.keys()).forEach((id) => {
|
Array.from(sessions.keys()).forEach((id) => {
|
||||||
if (!activeIds.has(id)) {
|
if (!activeIds.has(id)) {
|
||||||
sessions.delete(id);
|
sessions.delete(id);
|
||||||
|
clientToResolve.delete(id);
|
||||||
|
clientToSessionPromise.delete(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSession(clientId: string, accessToken: any, baseUrl: any) {
|
||||||
|
if (typeof accessToken === 'string' && typeof baseUrl === 'string') {
|
||||||
|
sessions.set(clientId, { accessToken, baseUrl });
|
||||||
|
} else {
|
||||||
|
// Logout or invalid session
|
||||||
|
sessions.delete(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveSession = clientToResolve.get(clientId);
|
||||||
|
if (resolveSession) {
|
||||||
|
resolveSession(sessions.get(clientId));
|
||||||
|
clientToResolve.delete(clientId);
|
||||||
|
clientToSessionPromise.delete(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestSession(client: Client): Promise<SessionInfo | undefined> {
|
||||||
|
const promise =
|
||||||
|
clientToSessionPromise.get(client.id) ??
|
||||||
|
new Promise((resolve) => {
|
||||||
|
clientToResolve.set(client.id, resolve);
|
||||||
|
client.postMessage({ type: 'requestSession' });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!clientToSessionPromise.has(client.id)) {
|
||||||
|
clientToSessionPromise.set(client.id, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestSessionWithTimeout(
|
||||||
|
clientId: string,
|
||||||
|
timeoutMs = 3000
|
||||||
|
): Promise<SessionInfo | undefined> {
|
||||||
|
const client = await self.clients.get(clientId);
|
||||||
|
if (!client) return undefined;
|
||||||
|
|
||||||
|
const sessionPromise = requestSession(client);
|
||||||
|
|
||||||
|
const timeout = new Promise<undefined>((resolve) => {
|
||||||
|
setTimeout(() => resolve(undefined), timeoutMs);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.race([sessionPromise, timeout]);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener('install', () => {
|
||||||
|
self.skipWaiting();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', (event: ExtendableEvent) => {
|
||||||
|
event.waitUntil(
|
||||||
|
(async () => {
|
||||||
|
await self.clients.claim();
|
||||||
|
await cleanupDeadClients();
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receive session updates from clients
|
* Receive session updates from clients
|
||||||
*/
|
*/
|
||||||
@@ -41,23 +98,28 @@ self.addEventListener('message', (event: ExtendableMessageEvent) => {
|
|||||||
|
|
||||||
const { type, accessToken, baseUrl } = event.data || {};
|
const { type, accessToken, baseUrl } = event.data || {};
|
||||||
|
|
||||||
if (type !== 'setSession') return;
|
if (type === 'setSession') {
|
||||||
|
setSession(client.id, accessToken, baseUrl);
|
||||||
cleanupDeadClients();
|
cleanupDeadClients();
|
||||||
|
|
||||||
if (typeof accessToken === 'string' && typeof baseUrl === 'string') {
|
|
||||||
sessions.set(client.id, { accessToken, baseUrl });
|
|
||||||
} else {
|
|
||||||
// Logout or invalid session
|
|
||||||
sessions.delete(client.id);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function validMediaRequest(url: string, baseUrl: string): boolean {
|
const MEDIA_PATHS = ['/_matrix/client/v1/media/download', '/_matrix/client/v1/media/thumbnail'];
|
||||||
const downloadUrl = new URL('/_matrix/client/v1/media/download', baseUrl);
|
|
||||||
const thumbnailUrl = new URL('/_matrix/client/v1/media/thumbnail', baseUrl);
|
|
||||||
|
|
||||||
return url.startsWith(downloadUrl.href) || url.startsWith(thumbnailUrl.href);
|
function mediaPath(url: string): boolean {
|
||||||
|
try {
|
||||||
|
const { pathname } = new URL(url);
|
||||||
|
return MEDIA_PATHS.some((p) => pathname.startsWith(p));
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validMediaRequest(url: string, baseUrl: string): boolean {
|
||||||
|
return MEDIA_PATHS.some((p) => {
|
||||||
|
const validUrl = new URL(p, baseUrl);
|
||||||
|
return url.startsWith(validUrl.href);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchConfig(token: string): RequestInit {
|
function fetchConfig(token: string): RequestInit {
|
||||||
@@ -72,13 +134,25 @@ function fetchConfig(token: string): RequestInit {
|
|||||||
self.addEventListener('fetch', (event: FetchEvent) => {
|
self.addEventListener('fetch', (event: FetchEvent) => {
|
||||||
const { url, method } = event.request;
|
const { url, method } = event.request;
|
||||||
|
|
||||||
if (method !== 'GET') return;
|
if (method !== 'GET' || !mediaPath(url)) return;
|
||||||
if (!event.clientId) return;
|
|
||||||
|
|
||||||
const session = sessions.get(event.clientId);
|
const { clientId } = event;
|
||||||
if (!session) return;
|
if (!clientId) return;
|
||||||
|
|
||||||
if (!validMediaRequest(url, session.baseUrl)) return;
|
|
||||||
|
|
||||||
|
const session = sessions.get(clientId);
|
||||||
|
if (session) {
|
||||||
|
if (validMediaRequest(url, session.baseUrl)) {
|
||||||
event.respondWith(fetch(url, fetchConfig(session.accessToken)));
|
event.respondWith(fetch(url, fetchConfig(session.accessToken)));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.respondWith(
|
||||||
|
requestSessionWithTimeout(clientId).then((s) => {
|
||||||
|
if (s && validMediaRequest(url, s.baseUrl)) {
|
||||||
|
return fetch(url, fetchConfig(s.accessToken));
|
||||||
|
}
|
||||||
|
return fetch(event.request);
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user