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 | 534x 187x 534x 534x 3809x 3809x 1024x 2785x 2785x 1x 1x 2784x 2784x 3x 3x 3x 3x 2781x 534x 498x 498x 498x 498x 534x 60x 60x 60x 28x 60x 25x 534x 534x 555x 534x 3x 534x 534x 534x 531x 531x 531x 531x | 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 = (): string => {
const serializedValue = localStorage.getItem(languageKey);
const result = LanguageValueSchema.safeParse(serializedValue);
Iif (!result.success) {
localStorage.removeItem(languageKey);
location.reload();
return Locale.EN;
}
return result.data;
};
|