Compare commits
21 commits
a28e93b0f3
...
52db5599c4
| Author | SHA1 | Date | |
|---|---|---|---|
| 52db5599c4 | |||
|
|
6347640a35 | ||
|
|
f2d8ad0b6b | ||
|
|
739786d9ab | ||
|
|
f642809939 | ||
|
|
02106a99b9 | ||
|
|
df3a3ba789 | ||
|
|
cd80d4c9e8 | ||
|
|
dab44edef2 | ||
|
|
ed0ad61bc4 | ||
|
|
b2cb717178 | ||
|
|
7a9f6d2223 | ||
|
|
a9022184fc | ||
|
|
826b3c2997 | ||
|
|
2e6c5f7c04 | ||
|
|
2d6730de56 | ||
|
|
b6cc0e3077 | ||
|
|
91c8731940 | ||
|
|
1f03891b25 | ||
|
|
9ff15b8b03 | ||
|
|
170f5cd473 |
21 changed files with 195 additions and 88 deletions
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}}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v6.0.2
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v6.2.0
|
||||
with:
|
||||
node-version: 20.12.2
|
||||
node-version: 24.13.1
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||
run: npm run build
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
uses: actions/upload-artifact@v6.0.0
|
||||
with:
|
||||
name: preview
|
||||
path: dist
|
||||
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
- name: Save pr number
|
||||
run: echo ${PR_NUMBER} > ./pr.txt
|
||||
- name: Upload pr number
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
uses: actions/upload-artifact@v6.0.0
|
||||
with:
|
||||
name: pr
|
||||
path: ./pr.txt
|
||||
|
|
|
|||
5
.github/workflows/docker-pr.yml
vendored
5
.github/workflows/docker-pr.yml
vendored
|
|
@ -5,15 +5,16 @@ on:
|
|||
paths:
|
||||
- 'Dockerfile'
|
||||
- '.github/workflows/docker-pr.yml'
|
||||
- '.github/workflows/prod-deploy.yml'
|
||||
|
||||
jobs:
|
||||
docker-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v6.0.2
|
||||
- name: Build Docker image
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
uses: docker/build-push-action@v6.19.2
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
|
|
|
|||
2
.github/workflows/lockfile.yml
vendored
2
.github/workflows/lockfile.yml
vendored
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v6.0.2
|
||||
- name: NPM Lockfile Changes
|
||||
uses: codepunkt/npm-lockfile-changes@b40543471c36394409466fdb277a73a0856d7891
|
||||
with:
|
||||
|
|
|
|||
6
.github/workflows/netlify-dev.yml
vendored
6
.github/workflows/netlify-dev.yml
vendored
|
|
@ -11,11 +11,11 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v6.0.2
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v6.2.0
|
||||
with:
|
||||
node-version: 20.12.2
|
||||
node-version: 24.13.1
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
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
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v6.0.2
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v6.2.0
|
||||
with:
|
||||
node-version: 20.12.2
|
||||
node-version: 24.13.1
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
|
@ -66,31 +66,31 @@ jobs:
|
|||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
uses: actions/checkout@v6.0.2
|
||||
- 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
|
||||
uses: docker/setup-buildx-action@v3.11.1
|
||||
uses: docker/setup-buildx-action@v3.12.0
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3.6.0
|
||||
uses: docker/login-action@v3.7.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to the Container registry
|
||||
uses: docker/login-action@v3.6.0
|
||||
uses: docker/login-action@v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5.8.0
|
||||
uses: docker/metadata-action@v5.10.0
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_USERNAME }}/cinny
|
||||
ghcr.io/${{ github.repository }}
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
uses: docker/build-push-action@v6.19.2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
## Builder
|
||||
FROM node:20.12.2-alpine3.18 as builder
|
||||
FROM node:24.13.1-alpine AS builder
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ RUN npm run build
|
|||
|
||||
|
||||
## App
|
||||
FROM nginx:1.29.3-alpine
|
||||
FROM nginx:1.29.5-alpine
|
||||
|
||||
COPY --from=builder /src/dist /app
|
||||
COPY --from=builder /src/docker-nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ mxFo+ioe/ABCufSmyqFye0psX3Sp
|
|||
|
||||
## Local development
|
||||
> [!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:
|
||||
```sh
|
||||
|
|
|
|||
30
package-lock.json
generated
30
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "cinny",
|
||||
"version": "4.10.3",
|
||||
"version": "4.10.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cinny",
|
||||
"version": "4.10.3",
|
||||
"version": "4.10.5",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
"emojibase-data": "15.3.2",
|
||||
"file-saver": "2.0.5",
|
||||
"focus-trap-react": "10.0.2",
|
||||
"folds": "2.5.0",
|
||||
"folds": "2.6.1",
|
||||
"html-dom-parser": "4.0.0",
|
||||
"html-react-parser": "4.2.0",
|
||||
"i18next": "23.12.2",
|
||||
|
|
@ -43,8 +43,8 @@
|
|||
"immer": "9.0.16",
|
||||
"is-hotkey": "0.2.0",
|
||||
"jotai": "2.6.0",
|
||||
"linkify-react": "4.1.3",
|
||||
"linkifyjs": "4.1.3",
|
||||
"linkify-react": "4.3.2",
|
||||
"linkifyjs": "4.3.2",
|
||||
"matrix-js-sdk": "38.2.0",
|
||||
"matrix-widget-api": "1.11.0",
|
||||
"millify": "6.1.0",
|
||||
|
|
@ -7197,9 +7197,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/folds": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/folds/-/folds-2.5.0.tgz",
|
||||
"integrity": "sha512-UJhvXAQ1XnZ9w10KJwSW+frvzzWE/zcF0dH3fDVCD70RFHAxwEi0UkkVS8CaZGxZF2Wvt3qTJyTS5LW3LwwUAw==",
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/folds/-/folds-2.6.1.tgz",
|
||||
"integrity": "sha512-0L1ZSqwjFSg2fesa//C4DgP47Vp/KqDuzjAaOEYN21AvoptyVI+6OEXWrtIdE8DPQCZYr0bV+tqbrLyA6uAhaw==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@vanilla-extract/css": "1.9.2",
|
||||
|
|
@ -8531,18 +8531,20 @@
|
|||
}
|
||||
},
|
||||
"node_modules/linkify-react": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/linkify-react/-/linkify-react-4.1.3.tgz",
|
||||
"integrity": "sha512-rhI3zM/fxn5BfRPHfi4r9N7zgac4vOIxub1wHIWXLA5ENTMs+BGaIaFO1D1PhmxgwhIKmJz3H7uCP0Dg5JwSlA==",
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/linkify-react/-/linkify-react-4.3.2.tgz",
|
||||
"integrity": "sha512-mi744h1hf+WDsr+paJgSBBgYNLMWNSHyM9V9LVUo03RidNGdw1VpI7Twnt+K3pEh3nIzB4xiiAgZxpd61ItKpQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"linkifyjs": "^4.0.0",
|
||||
"react": ">= 15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/linkifyjs": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.3.tgz",
|
||||
"integrity": "sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg=="
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz",
|
||||
"integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cinny",
|
||||
"version": "4.10.3",
|
||||
"version": "4.10.5",
|
||||
"description": "Yet another matrix client",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
"emojibase-data": "15.3.2",
|
||||
"file-saver": "2.0.5",
|
||||
"focus-trap-react": "10.0.2",
|
||||
"folds": "2.5.0",
|
||||
"folds": "2.6.1",
|
||||
"html-dom-parser": "4.0.0",
|
||||
"html-react-parser": "4.2.0",
|
||||
"i18next": "23.12.2",
|
||||
|
|
@ -55,9 +55,9 @@
|
|||
"immer": "9.0.16",
|
||||
"is-hotkey": "0.2.0",
|
||||
"jotai": "2.6.0",
|
||||
"linkify-react": "4.1.3",
|
||||
"linkifyjs": "4.1.3",
|
||||
"matrix-widget-api": "1.11.0",
|
||||
"linkify-react": "4.3.2",
|
||||
"linkifyjs": "4.3.2",
|
||||
"matrix-js-sdk": "38.2.0",
|
||||
"millify": "6.1.0",
|
||||
"pdfjs-dist": "4.2.67",
|
||||
|
|
@ -113,4 +113,4 @@
|
|||
"vite-plugin-static-copy": "1.0.4",
|
||||
"vite-plugin-top-level-await": "1.4.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,10 @@ export const createRoomParentState = (parent: Room) => ({
|
|||
},
|
||||
});
|
||||
|
||||
const createSpacePowerLevelsOverride = () => ({
|
||||
events_default: 50,
|
||||
});
|
||||
|
||||
export const createRoomEncryptionState = () => ({
|
||||
type: 'm.room.encryption',
|
||||
state_key: '',
|
||||
|
|
@ -165,6 +169,10 @@ export const createRoom = async (mx: MatrixClient, data: CreateRoomData): Promis
|
|||
initial_state: initialState,
|
||||
};
|
||||
|
||||
if (data.type === RoomType.Space) {
|
||||
options.power_level_content_override = createSpacePowerLevelsOverride();
|
||||
}
|
||||
|
||||
const result = await mx.createRoom(options);
|
||||
|
||||
if (data.parent) {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,12 @@ export function SSOStage({
|
|||
|
||||
useEffect(() => {
|
||||
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();
|
||||
setSSOWindow(undefined);
|
||||
handleSubmit();
|
||||
|
|
@ -37,7 +42,7 @@ export function SSOStage({
|
|||
return () => {
|
||||
window.removeEventListener('message', handleMessage);
|
||||
};
|
||||
}, [ssoWindow, handleSubmit]);
|
||||
}, [ssoWindow, handleSubmit, ssoRedirectURL]);
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,15 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
|
|||
if (previewStatus.status === AsyncStatus.Error) return null;
|
||||
|
||||
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 (
|
||||
<>
|
||||
|
|
@ -42,7 +50,7 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
|
|||
as="a"
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="no-referrer"
|
||||
rel="noreferrer"
|
||||
size="T200"
|
||||
priority="300"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -190,6 +190,13 @@ export const usePermissionGroups = (): PermissionGroup[] => {
|
|||
const otherSettingsGroup: PermissionGroup = {
|
||||
name: 'Other',
|
||||
items: [
|
||||
{
|
||||
location: {
|
||||
state: true,
|
||||
key: StateEvent.PoniesRoomEmotes,
|
||||
},
|
||||
name: 'Manage Emojis & Stickers',
|
||||
},
|
||||
{
|
||||
location: {
|
||||
state: true,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export function About({ requestClose }: AboutProps) {
|
|||
<Box direction="Column" gap="100">
|
||||
<Box gap="100" alignItems="End">
|
||||
<Text size="H3">Cinny</Text>
|
||||
<Text size="T200">v4.10.3</Text>
|
||||
<Text size="T200">v4.10.5</Text>
|
||||
</Box>
|
||||
<Text>Yet another matrix client.</Text>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -125,6 +125,13 @@ export const usePermissionGroups = (): PermissionGroup[] => {
|
|||
const otherSettingsGroup: PermissionGroup = {
|
||||
name: 'Other',
|
||||
items: [
|
||||
{
|
||||
location: {
|
||||
state: true,
|
||||
key: StateEvent.PoniesRoomEmotes,
|
||||
},
|
||||
name: 'Manage Emojis & Stickers',
|
||||
},
|
||||
{
|
||||
location: {
|
||||
state: true,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const useSelectedSpace = (): string | undefined => {
|
|||
|
||||
export const useSpaceLobbySelected = (spaceIdOrAlias: string): boolean => {
|
||||
const match = useMatch({
|
||||
path: getSpaceLobbyPath(spaceIdOrAlias),
|
||||
path: decodeURIComponent(getSpaceLobbyPath(spaceIdOrAlias)),
|
||||
caseSensitive: true,
|
||||
end: false,
|
||||
});
|
||||
|
|
@ -28,7 +28,7 @@ export const useSpaceLobbySelected = (spaceIdOrAlias: string): boolean => {
|
|||
|
||||
export const useSpaceSearchSelected = (spaceIdOrAlias: string): boolean => {
|
||||
const match = useMatch({
|
||||
path: getSpaceSearchPath(spaceIdOrAlias),
|
||||
path: decodeURIComponent(getSpaceSearchPath(spaceIdOrAlias)),
|
||||
caseSensitive: true,
|
||||
end: false,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ import { pushSessionToSW } from '../../sw-session';
|
|||
import { CallProvider } from './client/call/CallProvider';
|
||||
import { PersistentCallContainer } from './client/call/PersistentCallContainer';
|
||||
|
||||
|
||||
export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => {
|
||||
const { hashRouter } = clientConfig;
|
||||
const mobile = screenSize === ScreenSize.Mobile;
|
||||
|
|
@ -119,7 +118,6 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
|||
if (afterLoginPath) setAfterLoginRedirectPath(afterLoginPath);
|
||||
return redirect(getLoginPath());
|
||||
}
|
||||
pushSessionToSW(session.baseUrl, session.accessToken);
|
||||
return null;
|
||||
}}
|
||||
element={
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export function AuthFooter() {
|
|||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
v4.10.3
|
||||
v4.10.5
|
||||
</Text>
|
||||
<Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer">
|
||||
Twitter
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export function WelcomePage() {
|
|||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
v4.10.3
|
||||
v4.10.5
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,17 +34,14 @@ if ('serviceWorker' in navigator) {
|
|||
|
||||
navigator.serviceWorker.register(swUrl).then(sendSessionToSW);
|
||||
navigator.serviceWorker.ready.then(sendSessionToSW);
|
||||
window.addEventListener('load', sendSessionToSW);
|
||||
|
||||
// When returning from background
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
navigator.serviceWorker.addEventListener('message', (ev) => {
|
||||
const { type } = ev.data ?? {};
|
||||
|
||||
if (type === 'requestSession') {
|
||||
sendSessionToSW();
|
||||
}
|
||||
});
|
||||
|
||||
// When restored from bfcache (important on iOS)
|
||||
window.addEventListener('pageshow', sendSessionToSW);
|
||||
}
|
||||
|
||||
const mountApp = () => {
|
||||
|
|
|
|||
128
src/sw.ts
128
src/sw.ts
|
|
@ -3,14 +3,6 @@
|
|||
export type {};
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
self.addEventListener('install', () => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event: ExtendableEvent) => {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
type SessionInfo = {
|
||||
accessToken: string;
|
||||
baseUrl: string;
|
||||
|
|
@ -21,6 +13,9 @@ type 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() {
|
||||
const activeClients = await self.clients.matchAll();
|
||||
const activeIds = new Set(activeClients.map((c) => c.id));
|
||||
|
|
@ -28,10 +23,72 @@ async function cleanupDeadClients() {
|
|||
Array.from(sessions.keys()).forEach((id) => {
|
||||
if (!activeIds.has(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
|
||||
*/
|
||||
|
|
@ -41,23 +98,28 @@ self.addEventListener('message', (event: ExtendableMessageEvent) => {
|
|||
|
||||
const { type, accessToken, baseUrl } = event.data || {};
|
||||
|
||||
if (type !== 'setSession') return;
|
||||
|
||||
cleanupDeadClients();
|
||||
|
||||
if (typeof accessToken === 'string' && typeof baseUrl === 'string') {
|
||||
sessions.set(client.id, { accessToken, baseUrl });
|
||||
} else {
|
||||
// Logout or invalid session
|
||||
sessions.delete(client.id);
|
||||
if (type === 'setSession') {
|
||||
setSession(client.id, accessToken, baseUrl);
|
||||
cleanupDeadClients();
|
||||
}
|
||||
});
|
||||
|
||||
function validMediaRequest(url: string, baseUrl: string): boolean {
|
||||
const downloadUrl = new URL('/_matrix/client/v1/media/download', baseUrl);
|
||||
const thumbnailUrl = new URL('/_matrix/client/v1/media/thumbnail', baseUrl);
|
||||
const MEDIA_PATHS = ['/_matrix/client/v1/media/download', '/_matrix/client/v1/media/thumbnail'];
|
||||
|
||||
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 {
|
||||
|
|
@ -72,13 +134,25 @@ function fetchConfig(token: string): RequestInit {
|
|||
self.addEventListener('fetch', (event: FetchEvent) => {
|
||||
const { url, method } = event.request;
|
||||
|
||||
if (method !== 'GET') return;
|
||||
if (!event.clientId) return;
|
||||
if (method !== 'GET' || !mediaPath(url)) return;
|
||||
|
||||
const session = sessions.get(event.clientId);
|
||||
if (!session) return;
|
||||
const { clientId } = event;
|
||||
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)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
event.respondWith(fetch(url, fetchConfig(session.accessToken)));
|
||||
event.respondWith(
|
||||
requestSessionWithTimeout(clientId).then((s) => {
|
||||
if (s && validMediaRequest(url, s.baseUrl)) {
|
||||
return fetch(url, fetchConfig(s.accessToken));
|
||||
}
|
||||
return fetch(event.request);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue