156 lines
4.3 KiB
TypeScript
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);
|
|
}
|