Files
Genarrative/src/services/puzzleRuntimeUrlState.ts
T

156 lines
4.3 KiB
TypeScript

export const PUZZLE_RUNTIME_WORK_QUERY_KEY = 'work';
export const PUZZLE_RUNTIME_PROFILE_QUERY_KEY = 'runtimeProfileId';
export const PUZZLE_RUNTIME_SESSION_QUERY_KEY = 'runtimeSessionId';
export const PUZZLE_RUNTIME_LEVEL_QUERY_KEY = 'runtimeLevelId';
export const PUZZLE_RUNTIME_MODE_QUERY_KEY = 'mode';
export type PuzzleRuntimeUrlMode = 'draft' | 'published';
export type PuzzleRuntimeUrlState = {
runtimeProfileId?: string | null;
runtimeSessionId?: string | null;
runtimeLevelId?: string | null;
publicWorkCode?: string | null;
mode?: PuzzleRuntimeUrlMode | null;
};
type PuzzleRuntimeUrlEnvironment = {
location?: {
pathname: string;
search: string;
} | null;
history?: {
replaceState: (
data: unknown,
unused: string,
url?: string | URL | null,
) => void;
} | null;
};
type WritePuzzleRuntimeUrlOptions = {
pathname?: string;
};
function resolveEnvironment(
env?: PuzzleRuntimeUrlEnvironment,
): Required<PuzzleRuntimeUrlEnvironment> {
if (env) {
return {
location: env.location ?? null,
history: env.history ?? null,
};
}
if (typeof window === 'undefined') {
return {
location: null,
history: null,
};
}
return {
location: window.location,
history: window.history,
};
}
function normalizeValue(value: unknown) {
return typeof value === 'string' && value.trim() ? value.trim() : null;
}
function normalizeRuntimeMode(value: unknown): PuzzleRuntimeUrlMode | null {
const normalized = normalizeValue(value);
return normalized === 'draft' || normalized === 'published'
? normalized
: null;
}
function isPuzzleRuntimePath(pathname: string | undefined) {
return pathname?.trim().toLowerCase().replace(/\/+$/u, '') === '/runtime/puzzle';
}
export function readPuzzleRuntimeUrlState(
env?: PuzzleRuntimeUrlEnvironment,
): PuzzleRuntimeUrlState {
const resolved = resolveEnvironment(env);
const params = new URLSearchParams(resolved.location?.search ?? '');
return {
runtimeProfileId: normalizeValue(
params.get(PUZZLE_RUNTIME_PROFILE_QUERY_KEY),
),
runtimeSessionId: normalizeValue(
params.get(PUZZLE_RUNTIME_SESSION_QUERY_KEY),
),
runtimeLevelId: normalizeValue(params.get(PUZZLE_RUNTIME_LEVEL_QUERY_KEY)),
publicWorkCode: normalizeValue(params.get(PUZZLE_RUNTIME_WORK_QUERY_KEY)),
mode: normalizeRuntimeMode(params.get(PUZZLE_RUNTIME_MODE_QUERY_KEY)),
};
}
export function writePuzzleRuntimeUrlState(
state: PuzzleRuntimeUrlState,
env?: PuzzleRuntimeUrlEnvironment,
options: WritePuzzleRuntimeUrlOptions = {},
) {
const resolved = resolveEnvironment(env);
const pathname = options.pathname ?? resolved.location?.pathname;
if (
!resolved.location ||
!resolved.history?.replaceState ||
!isPuzzleRuntimePath(pathname)
) {
return;
}
const params = new URLSearchParams(resolved.location.search);
const entries = [
[PUZZLE_RUNTIME_PROFILE_QUERY_KEY, state.runtimeProfileId],
[PUZZLE_RUNTIME_SESSION_QUERY_KEY, state.runtimeSessionId],
[PUZZLE_RUNTIME_LEVEL_QUERY_KEY, state.runtimeLevelId],
[PUZZLE_RUNTIME_WORK_QUERY_KEY, state.publicWorkCode],
[PUZZLE_RUNTIME_MODE_QUERY_KEY, state.mode],
] as const;
entries.forEach(([key, rawValue]) => {
const value = normalizeValue(rawValue);
if (value) {
params.set(key, value);
} else {
params.delete(key);
}
});
const search = params.toString();
const nextPathname = pathname ?? resolved.location.pathname;
const nextUrl = search ? `${nextPathname}?${search}` : nextPathname;
resolved.history.replaceState(null, '', nextUrl);
}
export function clearPuzzleRuntimeUrlState(env?: PuzzleRuntimeUrlEnvironment) {
const resolved = resolveEnvironment(env);
if (
!resolved.location ||
!resolved.history?.replaceState ||
!isPuzzleRuntimePath(resolved.location.pathname)
) {
return;
}
const params = new URLSearchParams(resolved.location.search);
[
PUZZLE_RUNTIME_PROFILE_QUERY_KEY,
PUZZLE_RUNTIME_SESSION_QUERY_KEY,
PUZZLE_RUNTIME_LEVEL_QUERY_KEY,
PUZZLE_RUNTIME_WORK_QUERY_KEY,
PUZZLE_RUNTIME_MODE_QUERY_KEY,
].forEach((key) => params.delete(key));
const search = params.toString();
const nextUrl = search
? `${resolved.location.pathname}?${search}`
: resolved.location.pathname;
resolved.history.replaceState(null, '', nextUrl);
}