import { createContext, createSignal, useContext, type Component, type JSX } from 'solid-js';
const APPVIEW_URL_KEY = 'untangled.settings.appviewUrl';
const ROUTE_KNOT_REQUESTS_THROUGH_APPVIEW_KEY = 'untangled.settings.routeKnotRequestsThroughAppview';
const FORCE_PDS_KEY = 'untangled.settings.forcePds';
export const DEFAULT_TANGLED_APPVIEW_SERVICE =
import.meta.env.VITE_TANGLED_APPVIEW_SERVICE || 'https://bobbin.klbr.net';
export const normalizeAppviewUrl = (input: string): string => {
const trimmed = input.trim();
const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
const url = new URL(withScheme);
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
throw new Error('Appview URL must use http or https');
}
url.hash = '';
url.search = '';
return url.toString().replace(/\/$/, '');
};
const readStoredAppviewUrl = (): string => {
if (typeof localStorage === 'undefined') {
return normalizeAppviewUrl(DEFAULT_TANGLED_APPVIEW_SERVICE);
}
const stored = localStorage.getItem(APPVIEW_URL_KEY);
if (!stored) {
return normalizeAppviewUrl(DEFAULT_TANGLED_APPVIEW_SERVICE);
}
try {
return normalizeAppviewUrl(stored);
} catch {
localStorage.removeItem(APPVIEW_URL_KEY);
return normalizeAppviewUrl(DEFAULT_TANGLED_APPVIEW_SERVICE);
}
};
const readStoredRouteKnotRequestsThroughAppview = (): boolean => {
if (typeof localStorage === 'undefined') {
return false;
}
return localStorage.getItem(ROUTE_KNOT_REQUESTS_THROUGH_APPVIEW_KEY) === 'true';
};
const readStoredForcePds = (): boolean => {
if (typeof localStorage === 'undefined') {
return false;
}
return localStorage.getItem(FORCE_PDS_KEY) === 'true';
};
let currentAppviewUrl = readStoredAppviewUrl();
let currentRouteKnotRequestsThroughAppview = readStoredRouteKnotRequestsThroughAppview();
let currentForcePds = readStoredForcePds();
export const getTangledAppviewService = (): string => currentAppviewUrl;
export const getRouteKnotRequestsThroughAppview = (): boolean => currentRouteKnotRequestsThroughAppview;
export const getForcePds = (): boolean => currentForcePds;
interface AppSettingsContextValue {
appviewUrl: () => string;
routeKnotRequestsThroughAppview: () => boolean;
forcePds: () => boolean;
defaultAppviewUrl: string;
setAppviewUrl: (url: string) => void;
setRouteKnotRequestsThroughAppview: (enabled: boolean) => void;
setForcePds: (enabled: boolean) => void;
resetAppviewUrl: () => void;
resetAppviewSettings: () => void;
}
const AppSettingsContext = createContext<AppSettingsContextValue>();
export const AppSettingsProvider: Component<{ children: JSX.Element }> = (props) => {
const [appviewUrl, setAppviewUrlSignal] = createSignal(currentAppviewUrl);
const [routeKnotRequestsThroughAppview, setRouteKnotRequestsThroughAppviewSignal] = createSignal(
currentRouteKnotRequestsThroughAppview,
);
const [forcePds, setForcePdsSignal] = createSignal(currentForcePds);
const defaultAppviewUrl = normalizeAppviewUrl(DEFAULT_TANGLED_APPVIEW_SERVICE);
const setAppviewUrl = (url: string) => {
const normalized = normalizeAppviewUrl(url);
currentAppviewUrl = normalized;
localStorage.setItem(APPVIEW_URL_KEY, normalized);
setAppviewUrlSignal(normalized);
};
const setRouteKnotRequestsThroughAppview = (enabled: boolean) => {
currentRouteKnotRequestsThroughAppview = enabled;
localStorage.setItem(ROUTE_KNOT_REQUESTS_THROUGH_APPVIEW_KEY, String(enabled));
setRouteKnotRequestsThroughAppviewSignal(enabled);
};
const setForcePds = (enabled: boolean) => {
currentForcePds = enabled;
localStorage.setItem(FORCE_PDS_KEY, String(enabled));
setForcePdsSignal(enabled);
};
const resetAppviewUrl = () => {
currentAppviewUrl = defaultAppviewUrl;
localStorage.removeItem(APPVIEW_URL_KEY);
setAppviewUrlSignal(defaultAppviewUrl);
};
const resetAppviewSettings = () => {
resetAppviewUrl();
currentRouteKnotRequestsThroughAppview = false;
localStorage.removeItem(ROUTE_KNOT_REQUESTS_THROUGH_APPVIEW_KEY);
setRouteKnotRequestsThroughAppviewSignal(false);
currentForcePds = false;
localStorage.removeItem(FORCE_PDS_KEY);
setForcePdsSignal(false);
};
return (
<AppSettingsContext.Provider
value={{
appviewUrl,
routeKnotRequestsThroughAppview,
forcePds,
defaultAppviewUrl,
setAppviewUrl,
setRouteKnotRequestsThroughAppview,
setForcePds,
resetAppviewUrl,
resetAppviewSettings,
}}
>
{props.children}
</AppSettingsContext.Provider>
);
};
export const useAppSettings = (): AppSettingsContextValue => {
const context = useContext(AppSettingsContext);
if (!context) {
throw new Error('useAppSettings must be used inside AppSettingsProvider');
}
return context;
};