Merge pull request 'preview demo: drop a stored login for another server before the app reads it' (#80) from void/demo-homeserver-unreachable into dev
Some checks failed
Rat dev preview / Build dev preview (push) Has been cancelled
Some checks failed
Rat dev preview / Build dev preview (push) Has been cancelled
This commit is contained in:
commit
84770a2d2c
4 changed files with 103 additions and 35 deletions
|
|
@ -40,17 +40,17 @@ export function setFallbackSession(
|
|||
localStorage.setItem('cinny_user_id', userId);
|
||||
localStorage.setItem('cinny_hs_base_url', baseUrl);
|
||||
}
|
||||
export const removeFallbackSession = () => {
|
||||
localStorage.removeItem('cinny_hs_base_url');
|
||||
localStorage.removeItem('cinny_user_id');
|
||||
localStorage.removeItem('cinny_device_id');
|
||||
localStorage.removeItem('cinny_access_token');
|
||||
export const removeFallbackSession = (storage: Storage = localStorage) => {
|
||||
storage.removeItem('cinny_hs_base_url');
|
||||
storage.removeItem('cinny_user_id');
|
||||
storage.removeItem('cinny_device_id');
|
||||
storage.removeItem('cinny_access_token');
|
||||
};
|
||||
export const getFallbackSession = (): Session | undefined => {
|
||||
const baseUrl = localStorage.getItem('cinny_hs_base_url');
|
||||
const userId = localStorage.getItem('cinny_user_id');
|
||||
const deviceId = localStorage.getItem('cinny_device_id');
|
||||
const accessToken = localStorage.getItem('cinny_access_token');
|
||||
export const getFallbackSession = (storage: Storage = localStorage): Session | undefined => {
|
||||
const baseUrl = storage.getItem('cinny_hs_base_url');
|
||||
const userId = storage.getItem('cinny_user_id');
|
||||
const deviceId = storage.getItem('cinny_device_id');
|
||||
const accessToken = storage.getItem('cinny_access_token');
|
||||
|
||||
if (baseUrl && userId && deviceId && accessToken) {
|
||||
const session: Session = {
|
||||
|
|
|
|||
|
|
@ -30,29 +30,32 @@ import { getFallbackSession } from './app/state/sessions';
|
|||
|
||||
document.body.classList.add(configClass, varsClass);
|
||||
|
||||
// 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`;
|
||||
// Register Service Worker. After the preview demo has settled the stored session: the worker is
|
||||
// handed that session, and the demo may drop one it cannot serve.
|
||||
const registerServiceWorker = () => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
const swUrl =
|
||||
import.meta.env.MODE === 'production'
|
||||
? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js`
|
||||
: `/dev-sw.js?dev-sw`;
|
||||
|
||||
const sendSessionToSW = () => {
|
||||
const session = getFallbackSession();
|
||||
pushSessionToSW(session?.baseUrl, session?.accessToken);
|
||||
};
|
||||
const sendSessionToSW = () => {
|
||||
const session = getFallbackSession();
|
||||
pushSessionToSW(session?.baseUrl, session?.accessToken);
|
||||
};
|
||||
|
||||
navigator.serviceWorker.register(swUrl).then(sendSessionToSW);
|
||||
navigator.serviceWorker.ready.then(sendSessionToSW);
|
||||
navigator.serviceWorker.register(swUrl).then(sendSessionToSW);
|
||||
navigator.serviceWorker.ready.then(sendSessionToSW);
|
||||
|
||||
navigator.serviceWorker.addEventListener('message', (ev) => {
|
||||
const { type } = ev.data ?? {};
|
||||
navigator.serviceWorker.addEventListener('message', (ev) => {
|
||||
const { type } = ev.data ?? {};
|
||||
|
||||
if (type === 'requestSession') {
|
||||
sendSessionToSW();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (type === 'requestSession') {
|
||||
sendSessionToSW();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// The pretend homeserver of the dev preview: built in only with VITE_PREVIEW_DEMO=1 (the dev
|
||||
// workflow) and switched on by `previewDemo: true` in config.json. Any other build drops it.
|
||||
|
|
@ -76,4 +79,7 @@ const mountApp = () => {
|
|||
root.render(<App />);
|
||||
};
|
||||
|
||||
installPreviewDemo().finally(mountApp);
|
||||
installPreviewDemo().finally(() => {
|
||||
registerServiceWorker();
|
||||
mountApp();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
// only by a build with VITE_PREVIEW_DEMO=1 whose config.json says previewDemo: true (src/index.tsx).
|
||||
// Own-origin requests (the app's static files) pass; the demo origin is answered in-page; any
|
||||
// other origin is refused, so no real homeserver can be contacted while this is on.
|
||||
import { removeFallbackSession } from '../app/state/sessions';
|
||||
import { DEMO_ORIGIN } from './fixtures';
|
||||
import { createDemoServer, DemoServer } from './server';
|
||||
|
||||
|
|
@ -27,8 +28,6 @@ const refusal = (what: string, url: string) =>
|
|||
new Error(`preview: pretend server only; ${what} to ${url} refused`);
|
||||
|
||||
const BANNER_TEXT = 'preview: pretend server. Any password works.';
|
||||
const FOREIGN_SESSION_TEXT =
|
||||
'preview: pretend server. A login for another server is stored here and cannot be used.';
|
||||
|
||||
// In flow, above the app: body becomes a column and the app root takes the rest, so the banner
|
||||
// never covers the room header or the connection strip.
|
||||
|
|
@ -78,7 +77,16 @@ export const guardedFetch =
|
|||
});
|
||||
};
|
||||
|
||||
// Every preview of this site shares one origin, so one localStorage: a login another preview left
|
||||
// (a real homeserver) would make the app connect to that server, which the guard refuses. The
|
||||
// demo has one server, so a stored login for any other is dropped before the app reads it.
|
||||
export const dropForeignSession = (storage: Storage) => {
|
||||
const base = storage.getItem('cinny_hs_base_url');
|
||||
if (base !== null && base !== DEMO_ORIGIN) removeFallbackSession(storage);
|
||||
};
|
||||
|
||||
export const installDemo = (win: Window & typeof globalThis = window) => {
|
||||
dropForeignSession(win.localStorage);
|
||||
const server = createDemoServer();
|
||||
const { origin } = win.location;
|
||||
const realFetch = win.fetch.bind(win);
|
||||
|
|
@ -111,7 +119,6 @@ export const installDemo = (win: Window & typeof globalThis = window) => {
|
|||
routeOf(String(url), origin) === 'own' ? realBeacon(url, data) : false;
|
||||
}
|
||||
|
||||
const stored = win.localStorage.getItem('cinny_hs_base_url');
|
||||
showBanner(stored !== null && stored !== DEMO_ORIGIN ? FOREIGN_SESSION_TEXT : BANNER_TEXT);
|
||||
showBanner(BANNER_TEXT);
|
||||
return server;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ClientEvent, MatrixClient, SyncState, createClient } from 'matrix-js-sdk';
|
||||
import { DEMO_ORIGIN, ME, ROOMS, GENERAL, COMMONS } from './fixtures';
|
||||
import { guardedFetch, installDemo, routeOf } from './install';
|
||||
import { dropForeignSession, guardedFetch, installDemo, routeOf } from './install';
|
||||
import { createDemoServer, DemoServer } from './server';
|
||||
import { seedDemoSettings } from './seed';
|
||||
import { getFallbackSession } from '../app/state/sessions';
|
||||
import { mindOfSender, POSE_KEY, resolvePose } from '../app/components/message/pose/poses';
|
||||
import { parseRoomNotice, roomTrainingLine } from '../app/utils/trainingNotice';
|
||||
|
||||
|
|
@ -233,6 +234,7 @@ describe('the guard', () => {
|
|||
localStorage: {
|
||||
getItem: (k: string) => store.get(k) ?? null,
|
||||
setItem: (k: string, v: string) => void store.set(k, v),
|
||||
removeItem: (k: string) => void store.delete(k),
|
||||
},
|
||||
} as unknown as Window & typeof globalThis;
|
||||
vi.stubGlobal('document', {
|
||||
|
|
@ -254,6 +256,59 @@ describe('the guard', () => {
|
|||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('drops a stored login for a real homeserver before the app reads it, keeps the demo one', () => {
|
||||
const SESSION = {
|
||||
cinny_access_token: 'tok',
|
||||
cinny_device_id: 'DEV',
|
||||
cinny_user_id: '@a:b',
|
||||
cinny_hs_base_url: 'https://chat.gamecult.org',
|
||||
};
|
||||
const store = new Map<string, string>(Object.entries({ ...SESSION, settings: '{}' }));
|
||||
const storage = {
|
||||
getItem: (k: string) => store.get(k) ?? null,
|
||||
setItem: (k: string, v: string) => void store.set(k, v),
|
||||
removeItem: (k: string) => void store.delete(k),
|
||||
} as Storage;
|
||||
dropForeignSession(storage);
|
||||
expect(getFallbackSession(storage)).toBeUndefined();
|
||||
expect([...store.keys()]).toEqual(['settings']);
|
||||
|
||||
Object.entries({ ...SESSION, cinny_hs_base_url: DEMO_ORIGIN }).forEach(([k, v]) =>
|
||||
store.set(k, v)
|
||||
);
|
||||
dropForeignSession(storage);
|
||||
expect(getFallbackSession(storage)?.baseUrl).toBe(DEMO_ORIGIN);
|
||||
});
|
||||
|
||||
it('installDemo clears a foreign login so the app starts at the login screen', () => {
|
||||
const store = new Map<string, string>([
|
||||
['cinny_hs_base_url', 'https://chat.gamecult.org'],
|
||||
['cinny_access_token', 'tok'],
|
||||
['cinny_device_id', 'DEV'],
|
||||
['cinny_user_id', '@a:b'],
|
||||
]);
|
||||
const win = {
|
||||
location: { origin: PAGE },
|
||||
fetch: never,
|
||||
WebSocket: class {},
|
||||
XMLHttpRequest: { prototype: { open: vi.fn() } },
|
||||
navigator: {},
|
||||
localStorage: {
|
||||
getItem: (k: string) => store.get(k) ?? null,
|
||||
removeItem: (k: string) => void store.delete(k),
|
||||
},
|
||||
} as unknown as Window & typeof globalThis;
|
||||
vi.stubGlobal('document', {
|
||||
createElement: () => ({ style: {}, setAttribute: vi.fn() }),
|
||||
getElementById: () => null,
|
||||
body: { style: {}, insertBefore: vi.fn() },
|
||||
});
|
||||
const server = installDemo(win);
|
||||
live.push({ server, mx: clientFor(server) });
|
||||
expect(store.size).toBe(0);
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('seeds pose portraits on for a first visit and keeps what a visitor already chose', () => {
|
||||
const fresh = new Map<string, string>();
|
||||
seedDemoSettings({
|
||||
|
|
|
|||
Loading…
Reference in a new issue