0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

refactor(core,ui): fix cache issue when sign in to live preview (#3495)

This commit is contained in:
Gao Sun 2023-03-19 11:39:16 +08:00 committed by GitHub
parent c17fb35298
commit 5a2f3c3461
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 65 additions and 18 deletions

View file

@ -9,7 +9,6 @@ import i18next from 'i18next';
import Provider, { errors, ResourceServer } from 'oidc-provider'; import Provider, { errors, ResourceServer } from 'oidc-provider';
import snakecaseKeys from 'snakecase-keys'; import snakecaseKeys from 'snakecase-keys';
import { wellKnownCache } from '#src/caches/well-known.js';
import type { EnvSet } from '#src/env-set/index.js'; import type { EnvSet } from '#src/env-set/index.js';
import { addOidcEventListeners } from '#src/event-listeners/index.js'; import { addOidcEventListeners } from '#src/event-listeners/index.js';
import koaAuditLog from '#src/middleware/koa-audit-log.js'; import koaAuditLog from '#src/middleware/koa-audit-log.js';
@ -151,16 +150,11 @@ export default function initOidc(
const appendParameters = (path: string) => { const appendParameters = (path: string) => {
// `notification` is for showing a text banner on the homepage // `notification` is for showing a text banner on the homepage
return isDemoApp ? path + `?notification=demo_app.notification` : path; return isDemoApp ? path + `?notification=demo_app.notification&no_cache` : path;
}; };
switch (interaction.prompt.name) { switch (interaction.prompt.name) {
case 'login': { case 'login': {
// Always fetch the latest sign-in experience config for demo app (live preview)
if (isDemoApp) {
wellKnownCache.invalidate(tenantId, ['sie', 'sie-full']);
}
const isSignUp = const isSignUp =
ctx.oidc.params?.[OIDCExtraParametersKey.InteractionMode] === InteractionMode.signUp; ctx.oidc.params?.[OIDCExtraParametersKey.InteractionMode] === InteractionMode.signUp;

View file

@ -16,7 +16,7 @@ export default function koaInteractionSie<StateT, ContextT, ResponseT>(
tenantId: string tenantId: string
): MiddlewareType<StateT, WithInteractionSieContext<ContextT>, ResponseT> { ): MiddlewareType<StateT, WithInteractionSieContext<ContextT>, ResponseT> {
return async (ctx, next) => { return async (ctx, next) => {
if (noCache(ctx.headers)) { if (noCache(ctx.request)) {
wellKnownCache.invalidate(tenantId, ['sie']); wellKnownCache.invalidate(tenantId, ['sie']);
} }

View file

@ -39,8 +39,9 @@ export default function wellKnownRoutes<T extends AnonymousRouter>(
'/.well-known/sign-in-exp', '/.well-known/sign-in-exp',
koaGuard({ response: guardFullSignInExperience, status: 200 }), koaGuard({ response: guardFullSignInExperience, status: 200 }),
async (ctx, next) => { async (ctx, next) => {
if (noCache(ctx.headers)) { if (noCache(ctx.request)) {
wellKnownCache.invalidate(tenantId, ['sie', 'sie-full']); wellKnownCache.invalidate(tenantId, ['sie', 'sie-full']);
console.log('invalidated');
} }
ctx.body = await getFullSignInExperience(); ctx.body = await getFullSignInExperience();
@ -59,7 +60,7 @@ export default function wellKnownRoutes<T extends AnonymousRouter>(
status: 200, status: 200,
}), }),
async (ctx, next) => { async (ctx, next) => {
if (noCache(ctx.headers)) { if (noCache(ctx.request)) {
wellKnownCache.invalidate(tenantId, ['sie', 'phrases-lng-tags', 'phrases']); wellKnownCache.invalidate(tenantId, ['sie', 'phrases-lng-tags', 'phrases']);
} }

View file

@ -1,5 +1,8 @@
import type { IncomingHttpHeaders } from 'http'; import type { Request } from 'koa';
export const noCache = (headers: IncomingHttpHeaders): boolean => export const noCache = (request: Request): boolean =>
headers['cache-control']?.split(',').some((value) => value.trim().toLowerCase() === 'no-cache') ?? Boolean(
false; request.headers['cache-control']
?.split(',')
.some((value) => ['no-cache', 'no-store'].includes(value.trim().toLowerCase()))
) || request.URL.searchParams.get('no_cache') !== null;

View file

@ -23,10 +23,13 @@ import SocialLinkAccount from './pages/SocialLinkAccount';
import SocialSignIn from './pages/SocialSignInCallback'; import SocialSignIn from './pages/SocialSignInCallback';
import Springboard from './pages/Springboard'; import Springboard from './pages/Springboard';
import VerificationCode from './pages/VerificationCode'; import VerificationCode from './pages/VerificationCode';
import { handleSearchParametersData } from './utils/search-parameters';
import { getSignInExperienceSettings, setFavIcon } from './utils/sign-in-experience'; import { getSignInExperienceSettings, setFavIcon } from './utils/sign-in-experience';
import './scss/normalized.scss'; import './scss/normalized.scss';
handleSearchParametersData();
const App = () => { const App = () => {
const { context, Provider } = usePageContext(); const { context, Provider } = usePageContext();
const { experienceSettings, setLoading, setExperienceSettings } = context; const { experienceSettings, setLoading, setExperienceSettings } = context;

View file

@ -3,13 +3,29 @@
* The API will be deprecated in the future once SSR is implemented. * The API will be deprecated in the future once SSR is implemented.
*/ */
import { conditionalString } from '@silverhand/essentials'; import type { Nullable, Optional } from '@silverhand/essentials';
import { conditional } from '@silverhand/essentials';
import ky from 'ky'; import ky from 'ky';
import type { SignInExperienceResponse } from '@/types'; import type { SignInExperienceResponse } from '@/types';
import { searchKeys } from '@/utils/search-parameters';
const buildSearchParameters = (record: Record<string, Nullable<Optional<string>>>) => {
const entries = Object.entries(record).filter((entry): entry is [string, string] =>
Boolean(entry[0] && entry[1])
);
return conditional(entries.length > 0 && entries);
};
export const getSignInExperience = async <T extends SignInExperienceResponse>(): Promise<T> => { export const getSignInExperience = async <T extends SignInExperienceResponse>(): Promise<T> => {
return ky.get('/api/.well-known/sign-in-exp').json<T>(); return ky
.get('/api/.well-known/sign-in-exp', {
searchParams: buildSearchParameters({
[searchKeys.noCache]: sessionStorage.getItem(searchKeys.noCache),
}),
})
.json<T>();
}; };
export const getPhrases = async ({ export const getPhrases = async ({
@ -31,4 +47,9 @@ export const getPhrases = async ({
], ],
}, },
}) })
.get(`/api/.well-known/phrases${conditionalString(language && `?lng=${language}`)}`); .get('/api/.well-known/phrases', {
searchParams: buildSearchParameters({
[searchKeys.noCache]: sessionStorage.getItem(searchKeys.noCache),
lng: language,
}),
});

View file

@ -5,6 +5,7 @@ import { Trans, useTranslation } from 'react-i18next';
import { AppNotification as Notification } from '@/components/Notification'; import { AppNotification as Notification } from '@/components/Notification';
import usePlatform from '@/hooks/use-platform'; import usePlatform from '@/hooks/use-platform';
import { searchKeys } from '@/utils/search-parameters';
import * as styles from './index.module.scss'; import * as styles from './index.module.scss';
@ -26,7 +27,7 @@ const AppNotification = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
setNotification(new URLSearchParams(window.location.search).get('notification')); setNotification(sessionStorage.getItem(searchKeys.notification));
}, []); }, []);
useEffect(() => { useEffect(() => {

View file

@ -0,0 +1,24 @@
export const searchKeys = Object.freeze({
notification: 'notification',
noCache: 'no_cache',
});
export const handleSearchParametersData = () => {
const { search } = window.location;
if (!search) {
return;
}
const parameters = new URLSearchParams(search);
const notification = parameters.get(searchKeys.notification);
if (notification) {
sessionStorage.setItem(searchKeys.notification, notification);
}
if (parameters.get(searchKeys.noCache) !== null) {
sessionStorage.setItem(searchKeys.noCache, 'true');
}
};