Compare commits
5 Commits
v4.10.2
...
improve-sw
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9dda2c553 | ||
|
|
073a9f5786 | ||
|
|
655c1c9aff | ||
|
|
17d4bceb42 | ||
|
|
0f61f2f328 |
4
.github/workflows/prod-deploy.yml
vendored
4
.github/workflows/prod-deploy.yml
vendored
@@ -72,12 +72,12 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.11.1
|
uses: docker/setup-buildx-action@v3.11.1
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3.5.0
|
uses: docker/login-action@v3.6.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.5.0
|
uses: docker/login-action@v3.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ RUN npm run build
|
|||||||
|
|
||||||
|
|
||||||
## App
|
## App
|
||||||
FROM nginx:1.29.1-alpine
|
FROM nginx:1.29.3-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
|
||||||
|
|||||||
@@ -212,9 +212,10 @@ export const getMentions = (mx: MatrixClient, roomId: string, editor: Editor): M
|
|||||||
if (node.type === BlockType.CodeBlock) return;
|
if (node.type === BlockType.CodeBlock) return;
|
||||||
|
|
||||||
if (node.type === BlockType.Mention) {
|
if (node.type === BlockType.Mention) {
|
||||||
if (node.id === getCanonicalAliasOrRoomId(mx, roomId)) {
|
if (node.name === '@room') {
|
||||||
mentionData.room = true;
|
mentionData.room = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUserId(node.id) && node.id !== mx.getUserId()) {
|
if (isUserId(node.id) && node.id !== mx.getUserId()) {
|
||||||
mentionData.users.add(node.id);
|
mentionData.users.add(node.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP
|
|||||||
onClick={() => setAdvance(!advance)}
|
onClick={() => setAdvance(!advance)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<Text size="T200">Advance Options</Text>
|
<Text size="T200">Advanced Options</Text>
|
||||||
</Chip>
|
</Chip>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ export function CreateSpaceForm({ defaultKind, space, onCreate }: CreateSpaceFor
|
|||||||
onClick={() => setAdvance(!advance)}
|
onClick={() => setAdvance(!advance)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<Text size="T200">Advance Options</Text>
|
<Text size="T200">Advanced Options</Text>
|
||||||
</Chip>
|
</Chip>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export function General({ requestClose }: GeneralProps) {
|
|||||||
<RoomLocalAddresses permissions={permissions} />
|
<RoomLocalAddresses permissions={permissions} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box direction="Column" gap="100">
|
<Box direction="Column" gap="100">
|
||||||
<Text size="L400">Advance Options</Text>
|
<Text size="L400">Advanced Options</Text>
|
||||||
<RoomUpgrade permissions={permissions} requestClose={requestClose} />
|
<RoomUpgrade permissions={permissions} requestClose={requestClose} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export function General({ requestClose }: GeneralProps) {
|
|||||||
<RoomLocalAddresses permissions={permissions} />
|
<RoomLocalAddresses permissions={permissions} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box direction="Column" gap="100">
|
<Box direction="Column" gap="100">
|
||||||
<Text size="L400">Advance Options</Text>
|
<Text size="L400">Advanced Options</Text>
|
||||||
<RoomUpgrade permissions={permissions} requestClose={requestClose} />
|
<RoomUpgrade permissions={permissions} requestClose={requestClose} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ 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;
|
||||||
@@ -106,7 +107,8 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
|||||||
|
|
||||||
<Route
|
<Route
|
||||||
loader={() => {
|
loader={() => {
|
||||||
if (!getFallbackSession()) {
|
const session = getFallbackSession();
|
||||||
|
if (!session) {
|
||||||
const afterLoginPath = getAppPathFromHref(
|
const afterLoginPath = getAppPathFromHref(
|
||||||
getOriginBaseUrl(hashRouter),
|
getOriginBaseUrl(hashRouter),
|
||||||
window.location.href
|
window.location.href
|
||||||
@@ -114,6 +116,7 @@ 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={
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createClient, MatrixClient, IndexedDBStore, IndexedDBCryptoStore } from
|
|||||||
|
|
||||||
import { cryptoCallbacks } from './secretStorageKeys';
|
import { cryptoCallbacks } from './secretStorageKeys';
|
||||||
import { clearNavToActivePathStore } from '../app/state/navToActivePath';
|
import { clearNavToActivePathStore } from '../app/state/navToActivePath';
|
||||||
|
import { pushSessionToSW } from '../sw-session';
|
||||||
|
|
||||||
type Session = {
|
type Session = {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
@@ -53,6 +54,7 @@ export const clearCacheAndReload = async (mx: MatrixClient) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const logoutClient = async (mx: MatrixClient) => {
|
export const logoutClient = async (mx: MatrixClient) => {
|
||||||
|
pushSessionToSW();
|
||||||
mx.stopClient();
|
mx.stopClient();
|
||||||
try {
|
try {
|
||||||
await mx.logout();
|
await mx.logout();
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import App from './app/pages/App';
|
|||||||
|
|
||||||
// import i18n (needs to be bundled ;))
|
// import i18n (needs to be bundled ;))
|
||||||
import './app/i18n';
|
import './app/i18n';
|
||||||
|
import { pushSessionToSW } from './sw-session';
|
||||||
|
import { getFallbackSession } from './app/state/sessions';
|
||||||
|
|
||||||
document.body.classList.add(configClass, varsClass);
|
document.body.classList.add(configClass, varsClass);
|
||||||
|
|
||||||
@@ -25,16 +27,9 @@ if ('serviceWorker' in navigator) {
|
|||||||
? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js`
|
? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js`
|
||||||
: `/dev-sw.js?dev-sw`;
|
: `/dev-sw.js?dev-sw`;
|
||||||
|
|
||||||
navigator.serviceWorker.register(swUrl);
|
navigator.serviceWorker.register(swUrl).then(() => {
|
||||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
const session = getFallbackSession();
|
||||||
if (event.data?.type === 'token' && event.data?.responseKey) {
|
pushSessionToSW(session?.baseUrl, session?.accessToken);
|
||||||
// Get the token for SW.
|
|
||||||
const token = localStorage.getItem('cinny_access_token') ?? undefined;
|
|
||||||
event.source!.postMessage({
|
|
||||||
responseKey: event.data.responseKey,
|
|
||||||
token,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
src/sw-session.ts
Normal file
10
src/sw-session.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export function pushSessionToSW(baseUrl?: string, accessToken?: string) {
|
||||||
|
if (!('serviceWorker' in navigator)) return;
|
||||||
|
if (!navigator.serviceWorker.controller) return;
|
||||||
|
|
||||||
|
navigator.serviceWorker.controller.postMessage({
|
||||||
|
type: 'setSession',
|
||||||
|
accessToken,
|
||||||
|
baseUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
92
src/sw.ts
92
src/sw.ts
@@ -3,22 +3,64 @@
|
|||||||
export type {};
|
export type {};
|
||||||
declare const self: ServiceWorkerGlobalScope;
|
declare const self: ServiceWorkerGlobalScope;
|
||||||
|
|
||||||
async function askForAccessToken(client: Client): Promise<string | undefined> {
|
self.addEventListener('install', () => {
|
||||||
return new Promise((resolve) => {
|
self.skipWaiting();
|
||||||
const responseKey = Math.random().toString(36);
|
});
|
||||||
const listener = (event: ExtendableMessageEvent) => {
|
|
||||||
if (event.data.responseKey !== responseKey) return;
|
self.addEventListener('activate', (event: ExtendableEvent) => {
|
||||||
resolve(event.data.token);
|
event.waitUntil(self.clients.claim());
|
||||||
self.removeEventListener('message', listener);
|
});
|
||||||
|
|
||||||
|
type SessionInfo = {
|
||||||
|
accessToken: string;
|
||||||
|
baseUrl: string;
|
||||||
};
|
};
|
||||||
self.addEventListener('message', listener);
|
|
||||||
client.postMessage({ responseKey, type: 'token' });
|
/**
|
||||||
|
* Store session per client (tab)
|
||||||
|
*/
|
||||||
|
const sessions = new Map<string, SessionInfo>();
|
||||||
|
|
||||||
|
async function cleanupDeadClients() {
|
||||||
|
const activeClients = await self.clients.matchAll();
|
||||||
|
const activeIds = new Set(activeClients.map((c) => c.id));
|
||||||
|
|
||||||
|
Array.from(sessions.keys()).forEach((id) => {
|
||||||
|
if (!activeIds.has(id)) {
|
||||||
|
sessions.delete(id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchConfig(token?: string): RequestInit | undefined {
|
/**
|
||||||
if (!token) return undefined;
|
* Receive session updates from clients
|
||||||
|
*/
|
||||||
|
self.addEventListener('message', (event: ExtendableMessageEvent) => {
|
||||||
|
const client = event.source as Client | null;
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return url.startsWith(downloadUrl.href) || url.startsWith(thumbnailUrl.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchConfig(token: string): RequestInit {
|
||||||
return {
|
return {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
@@ -27,26 +69,16 @@ function fetchConfig(token?: string): RequestInit | undefined {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addEventListener('activate', (event: ExtendableEvent) => {
|
|
||||||
event.waitUntil(clients.claim());
|
|
||||||
});
|
|
||||||
|
|
||||||
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 (
|
|
||||||
!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));
|
if (method !== 'GET') return;
|
||||||
})()
|
if (!event.clientId) return;
|
||||||
);
|
|
||||||
|
const session = sessions.get(event.clientId);
|
||||||
|
if (!session) return;
|
||||||
|
|
||||||
|
if (!validMediaRequest(url, session.baseUrl)) return;
|
||||||
|
|
||||||
|
event.respondWith(fetch(url, fetchConfig(session.accessToken)));
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user