diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index 450e4e29..b7646e58 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -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 diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml index 398785ab..38f6a4fe 100644 --- a/.github/workflows/docker-pr.yml +++ b/.github/workflows/docker-pr.yml @@ -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 diff --git a/.github/workflows/lockfile.yml b/.github/workflows/lockfile.yml index be52eb50..8a57a3b5 100644 --- a/.github/workflows/lockfile.yml +++ b/.github/workflows/lockfile.yml @@ -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: diff --git a/.github/workflows/netlify-dev.yml b/.github/workflows/netlify-dev.yml index 66cd5ad5..06e0abda 100644 --- a/.github/workflows/netlify-dev.yml +++ b/.github/workflows/netlify-dev.yml @@ -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 diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml index fe8a722f..8c3dcc56 100644 --- a/.github/workflows/prod-deploy.yml +++ b/.github/workflows/prod-deploy.yml @@ -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 diff --git a/Dockerfile b/Dockerfile index 6489cb86..0e89e4e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/README.md b/README.md index 427898f9..f1c36082 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/package-lock.json b/package-lock.json index 339368ff..65f0e8c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 67f7f684..5c942325 100644 --- a/package.json +++ b/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" } -} \ No newline at end of file +} diff --git a/src/app/components/create-room/utils.ts b/src/app/components/create-room/utils.ts index 3d702f2a..e757683a 100644 --- a/src/app/components/create-room/utils.ts +++ b/src/app/components/create-room/utils.ts @@ -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) { diff --git a/src/app/components/uia-stages/SSOStage.tsx b/src/app/components/uia-stages/SSOStage.tsx index f85bcb30..e3d6126a 100644 --- a/src/app/components/uia-stages/SSOStage.tsx +++ b/src/app/components/uia-stages/SSOStage.tsx @@ -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 ( diff --git a/src/app/components/url-preview/UrlPreviewCard.tsx b/src/app/components/url-preview/UrlPreviewCard.tsx index 2a51969a..cbe85df2 100644 --- a/src/app/components/url-preview/UrlPreviewCard.tsx +++ b/src/app/components/url-preview/UrlPreviewCard.tsx @@ -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" > diff --git a/src/app/features/room-settings/permissions/usePermissionItems.ts b/src/app/features/room-settings/permissions/usePermissionItems.ts index cf77a277..ad92eacf 100644 --- a/src/app/features/room-settings/permissions/usePermissionItems.ts +++ b/src/app/features/room-settings/permissions/usePermissionItems.ts @@ -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, diff --git a/src/app/features/settings/about/About.tsx b/src/app/features/settings/about/About.tsx index 7cf121d5..f7af2d6a 100644 --- a/src/app/features/settings/about/About.tsx +++ b/src/app/features/settings/about/About.tsx @@ -46,7 +46,7 @@ export function About({ requestClose }: AboutProps) { Cinny - v4.10.3 + v4.10.5 Yet another matrix client. diff --git a/src/app/features/space-settings/permissions/usePermissionItems.ts b/src/app/features/space-settings/permissions/usePermissionItems.ts index 8192e7bc..a02cf420 100644 --- a/src/app/features/space-settings/permissions/usePermissionItems.ts +++ b/src/app/features/space-settings/permissions/usePermissionItems.ts @@ -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, diff --git a/src/app/hooks/router/useSelectedSpace.ts b/src/app/hooks/router/useSelectedSpace.ts index 2e8e73a0..b891fde7 100644 --- a/src/app/hooks/router/useSelectedSpace.ts +++ b/src/app/hooks/router/useSelectedSpace.ts @@ -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, }); diff --git a/src/app/pages/Router.tsx b/src/app/pages/Router.tsx index 25744d27..16ab0a38 100644 --- a/src/app/pages/Router.tsx +++ b/src/app/pages/Router.tsx @@ -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={ diff --git a/src/app/pages/auth/AuthFooter.tsx b/src/app/pages/auth/AuthFooter.tsx index 124b0384..146f039b 100644 --- a/src/app/pages/auth/AuthFooter.tsx +++ b/src/app/pages/auth/AuthFooter.tsx @@ -15,7 +15,7 @@ export function AuthFooter() { target="_blank" rel="noreferrer" > - v4.10.3 + v4.10.5 Twitter diff --git a/src/app/pages/client/WelcomePage.tsx b/src/app/pages/client/WelcomePage.tsx index 969a5cd4..79630990 100644 --- a/src/app/pages/client/WelcomePage.tsx +++ b/src/app/pages/client/WelcomePage.tsx @@ -24,7 +24,7 @@ export function WelcomePage() { target="_blank" rel="noreferrer noopener" > - v4.10.3 + v4.10.5 } diff --git a/src/index.tsx b/src/index.tsx index 71e723ab..6cc0ca38 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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 = () => { diff --git a/src/sw.ts b/src/sw.ts index d4eebc02..69293b1d 100644 --- a/src/sw.ts +++ b/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(); +const clientToResolve = new Map void>(); +const clientToSessionPromise = new Map>(); + 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 { + 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 { + const client = await self.clients.get(clientId); + if (!client) return undefined; + + const sessionPromise = requestSession(client); + + const timeout = new Promise((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); + }) + ); });