Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 636x 208x 636x 636x 4510x 4510x 1135x 3375x 3375x 1x 1x 3374x 3374x 3x 3x 3x 3x 3371x 636x 583x 583x 583x 583x 636x 62x 62x 62x 29x 62x 26x 636x 636x 645x 636x 3x 636x 636x 636x 633x 633x 633x 633x | import { captureException } from "@sentry/react";
import { merge } from "remeda";
import { z } from "zod";
import { ActiveProgramType } from "shared/config/clientConfigTypes";
import {
appUpdateReloadedBuildTimeKey,
browserStorageEventPrefix,
kompassiLoginStateKey,
localStorageStateKey,
} from "shared/constants/browserStorage";
import { Locale } from "shared/types/locale";
import { ProgramType } from "shared/types/models/programItem";
import { LocalStorageState } from "client/types/reduxTypes";
import { getProgramTypeSelectOptions } from "client/utils/getProgramTypeSelectOptions";
import { StringToJsonSchema } from "client/utils/zodUtils";
const isActive = (programType: ActiveProgramType): boolean =>
getProgramTypeSelectOptions().includes(programType);
const SessionSchema = z
.object({
login: z.object({ jwt: z.string() }).optional(),
admin: z
.object({
activeProgramTypes: z.array(z.enum(ProgramType).refine(isActive)),
})
.optional(),
})
.strict();
type LocalStorage = z.infer<typeof SessionSchema>;
export const loadSession = (): LocalStorage | undefined => {
const serializedValue = localStorage.getItem(localStorageStateKey);
if (!serializedValue) {
return undefined;
}
const parseJsonResult = StringToJsonSchema.safeParse(serializedValue);
if (!parseJsonResult.success) {
clearSession();
return undefined;
}
const result = SessionSchema.safeParse(parseJsonResult.data);
if (!result.success) {
// Clearing the session logs the user out, so make the reason visible: a
// widespread parse failure would mean a persisted-shape change mid-event
captureException(
new Error("Invalid localStorage session, clearing session"),
{
extra: { zodError: result.error.message },
},
);
// eslint-disable-next-line no-console
console.error(
"Invalid localStorage session, clearing session:",
result.error,
);
clearSession();
return undefined;
}
return result.data;
};
export const saveSession = (state: Partial<LocalStorageState>): void => {
const previousSession = loadSession();
const newSession = previousSession ? merge(previousSession, state) : state;
const serializedState = JSON.stringify(newSession);
localStorage.setItem(localStorageStateKey, serializedState);
};
export const clearSession = (): void => {
localStorage.removeItem(localStorageStateKey);
// Only this event's session keys, not the whole store: the update reload
// guard has to outlive a logout (which is itself a navigation, so dropping
// it would let a pending update reload the page a second time), an
// in-progress Kompassi login's state has to outlive one too (a logout
// between the button and the callback would otherwise reject the login), and
// a blanket wipe would also take keys other origins' code owns
const preservedKeys = new Set<string>([
appUpdateReloadedBuildTimeKey,
kompassiLoginStateKey,
]);
const sessionKeys = Object.keys(sessionStorage).filter(
(key) =>
key.startsWith(`${browserStorageEventPrefix}-`) &&
!preservedKeys.has(key),
);
for (const sessionKey of sessionKeys) {
sessionStorage.removeItem(sessionKey);
}
};
// Dismissed admin message is stored separately from the zod-strict 'state' object so a public
// (logged-out) visitor can remember their dismissal without a session. We store the dismissed
// message text itself, so a new or edited admin message no longer matches and shows again.
const dismissedAdminMessageKey = `${browserStorageEventPrefix}-dismissedAdminMessage`;
export const getDismissedAdminMessage = (): string => {
return localStorage.getItem(dismissedAdminMessageKey) ?? "";
};
export const saveDismissedAdminMessage = (adminMessage: string): void => {
localStorage.setItem(dismissedAdminMessageKey, adminMessage);
};
// Locale uses same 'languageKey' as i18next but i18next has separate logic for handling localStorage
const languageKey = "i18nextLng";
const LanguageValueSchema = z.enum(Locale);
export const getLocalStorageLocale = (): Locale => {
const serializedValue = localStorage.getItem(languageKey);
const result = LanguageValueSchema.safeParse(serializedValue);
Iif (!result.success) {
localStorage.removeItem(languageKey);
location.reload();
return Locale.EN;
}
return result.data;
};
|