feat(react): typescriptify everything

This commit is contained in:
neoapps-dev
2026-05-23 18:31:49 +03:00
parent 043d8445f2
commit 736376dc04
33 changed files with 223 additions and 150 deletions
+7 -7
View File
@@ -71,7 +71,7 @@ export const ArcEditorView: React.FC = () => {
}
setSelectedEntryIdx(null);
showNotification(`Loaded ${parsed.name}`);
} catch (err: any) {
} catch (err: unknown) {
if (err !== "CANCELED") {
console.error("Failed to parse ARC", err);
showNotification("Failed to parse ARC", "error");
@@ -96,7 +96,7 @@ export const ArcEditorView: React.FC = () => {
setOpenedPath(targetPath);
showNotification("ARC Saved Successfully");
}
} catch (err: any) {
} catch (err: unknown) {
if (err !== "CANCELED") showNotification("Save failed", "error");
}
};
@@ -109,7 +109,7 @@ export const ArcEditorView: React.FC = () => {
playPressSound();
await TauriService.writeBinaryFile(path, entry.data);
showNotification(`Extracted: ${entry.filename}`);
} catch (err: any) {
} catch (err: unknown) {
if (err !== "CANCELED") showNotification("Extraction failed", "error");
}
};
@@ -212,7 +212,7 @@ export const ArcEditorView: React.FC = () => {
};
const treeData = useMemo(() => {
const root: any = { name: "<root>", children: {}, isFolder: true };
const root: Record<string, any> = { name: "<root>", children: {}, isFolder: true };
filteredEntries.forEach((entry) => {
const parts = entry.filename.split(/\//);
let current = root;
@@ -237,7 +237,7 @@ export const ArcEditorView: React.FC = () => {
setExpandedNodes(newExpanded);
};
const renderTree = (node: any, path: string = "") => {
const renderTree = (node: Record<string, any>, path: string = "") => {
const nodePath = path ? `${path}/${node.name}` : node.name;
const isExpanded = expandedNodes.has(nodePath);
@@ -285,7 +285,7 @@ export const ArcEditorView: React.FC = () => {
exit={{ height: 0, opacity: 0 }}
className="ml-4 border-l border-white/10 overflow-hidden"
>
{Object.values(node.children).map((child: any) => renderTree(child, nodePath))}
{Object.values(node.children as Record<string, any>).map((child: Record<string, any>) => renderTree(child, nodePath))}
</motion.div>
)}
</AnimatePresence>
@@ -306,7 +306,7 @@ export const ArcEditorView: React.FC = () => {
await TauriService.writeBinaryFile(`${baseFolder}/${fileName}`, entry.data);
}
showNotification("All Entries Exported");
} catch (err: any) {
} catch (err: unknown) {
if (err !== "CANCELED") showNotification("Export failed", "error");
}
};
+4 -4
View File
@@ -47,9 +47,9 @@ export default function ColEditorView() {
const parsedCol = ColService.readCOL(buffer);
setCol(parsedCol);
showNotification(`Loaded ${file.name}`);
} catch (err: any) {
} catch (err: unknown) {
console.error("Failed to parse COL", err);
showNotification(err.message || "Failed to parse COL", "error");
showNotification(err instanceof Error ? err.message : "Failed to parse COL", "error");
}
e.target.value = "";
};
@@ -67,9 +67,9 @@ export default function ColEditorView() {
a.click();
URL.revokeObjectURL(url);
showNotification("COL Saved Successfully");
} catch (err: any) {
} catch (err: unknown) {
console.error("Failed to save COL", err);
showNotification(err.message || "Failed to save COL", "error");
showNotification(err instanceof Error ? err.message : "Failed to save COL", "error");
}
};
+5 -5
View File
@@ -28,9 +28,9 @@ export default function GrfEditorView() {
setGrf(parsedGrf);
setFilename(file.name);
showNotification(`Loaded ${file.name}`);
} catch (err: any) {
} catch (err: unknown) {
console.error("Failed to parse GRF", err);
showNotification(err.message || "Failed to parse GRF", "error");
showNotification(err instanceof Error ? err.message : "Failed to parse GRF", "error");
}
e.target.value = "";
};
@@ -94,7 +94,7 @@ export default function GrfEditorView() {
playPressSound();
try {
const buffer = GrfService.serializeGRF(grf);
const blob = new Blob([buffer as any]);
const blob = new Blob([buffer]);
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
@@ -102,9 +102,9 @@ export default function GrfEditorView() {
a.click();
URL.revokeObjectURL(url);
showNotification("GRF Saved Successfully");
} catch (err: any) {
} catch (err: unknown) {
console.error("Failed to save GRF", err);
showNotification(err.message || "Failed to save GRF", "error");
showNotification(err instanceof Error ? err.message : "Failed to save GRF", "error");
}
};
+3 -2
View File
@@ -6,6 +6,7 @@ import {
useAudio,
useGame,
} from "../../context/LauncherContext";
import type { Edition } from "../../types/edition";
const HomeView = memo(function HomeView() {
const { setActiveView, setShowCredits, focusSection, onNavigateToSkin } =
@@ -24,7 +25,7 @@ const HomeView = memo(function HomeView() {
} = useGame();
const isFocusedSection = focusSection === "menu";
const selectedEdition = editions.find((e: any) => e.id === profile);
const selectedEdition = editions.find((e: Edition) => e.id === profile);
const selectedVersionName = selectedEdition?.name || "Game";
const isInstalled = installs.includes(profile);
const isDownloading = downloadingId === profile;
@@ -128,7 +129,7 @@ const HomeView = memo(function HomeView() {
transition={{ duration: useConfig().animationsEnabled ? 0.3 : 0 }}
className="relative w-full max-w-[540px] flex flex-col space-y-3 outline-none"
>
{buttons.map((btn: any, i: number) => (
{buttons.map((btn: { label: string; action: () => void; isDanger?: boolean; disabled: boolean; id?: string }, i: number) => (
<div key={i} className="relative w-full group">
<button
onMouseEnter={() =>
+25 -23
View File
@@ -11,6 +11,7 @@ import {
LceLiveAccount,
FriendRequest,
GameInvite,
DeviceLinkStartResponse,
} from "../../services/LceLiveService";
import { TauriService } from "../../services/TauriService";
import ChooseInstanceModal from "../modals/ChooseInstanceModal";
@@ -31,7 +32,7 @@ const LceLiveView = memo(function LceLiveView() {
const [incomingReqs, setIncomingReqs] = useState<FriendRequest[]>([]);
const [outgoingReqs, setOutgoingReqs] = useState<FriendRequest[]>([]);
const [invites, setInvites] = useState<GameInvite[]>([]);
const [linkData, setLinkData] = useState<any>(null);
const [linkData, setLinkData] = useState<DeviceLinkStartResponse | null>(null);
const [linkError, setLinkError] = useState<string | null>(null);
const [isHosting, setIsHosting] = useState(false);
const [hostStatus, setHostStatus] = useState("");
@@ -59,7 +60,7 @@ const LceLiveView = memo(function LceLiveView() {
setIncomingReqs(reqs.incoming);
setOutgoingReqs(reqs.outgoing);
setInvites(invs.filter((i: GameInvite) => i.status === "pending"));
} catch (e: any) {
} catch (e: unknown) {
console.error(e);
}
};
@@ -86,15 +87,15 @@ const LceLiveView = memo(function LceLiveView() {
useEffect(() => {
if (currentTab !== "device_link") return;
let mounted = true;
let pollInterval: any = null;
let pollInterval: ReturnType<typeof setInterval> | null = null;
const startLink = async () => {
try {
if (!linkData) {
const data = await lceLiveService.startDeviceLink();
if (mounted) setLinkData(data);
}
} catch (e: any) {
if (mounted) setLinkError(e.message);
} catch (e: unknown) {
if (mounted) setLinkError(e instanceof Error ? e.message : String(e));
}
};
@@ -109,9 +110,9 @@ const LceLiveView = memo(function LceLiveView() {
if (res.isLinked && mounted) {
setIsSignedIn(true);
setLinkData(null);
clearInterval(pollInterval);
if (pollInterval !== null) clearInterval(pollInterval);
}
} catch (e: any) {
} catch (e: unknown) {
console.warn("Poll failed", e);
}
},
@@ -121,7 +122,7 @@ const LceLiveView = memo(function LceLiveView() {
return () => {
mounted = false;
if (pollInterval) clearInterval(pollInterval);
if (pollInterval !== null) clearInterval(pollInterval);
};
}, [currentTab, linkData]);
@@ -151,8 +152,8 @@ const LceLiveView = memo(function LceLiveView() {
try {
await action();
fetchSocialData();
} catch (e: any) {
setErrorModal(e.message || "An error occurred");
} catch (e: unknown) {
setErrorModal(e instanceof Error ? e.message : "An error occurred");
}
};
@@ -174,8 +175,8 @@ const LceLiveView = memo(function LceLiveView() {
setIsHosting(true);
setHostStatus(`Hosting at ${endpoint.ip}:25565`);
setInvitedFriends(new Set());
} catch (e: any) {
const msg = typeof e === "string" ? e : e?.message || "Unknown error";
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error";
setErrorModal("STUN discovery failed: " + msg);
setHostStatus("");
} finally {
@@ -207,7 +208,7 @@ const LceLiveView = memo(function LceLiveView() {
try {
await TauriService.stopAllProxies();
await lceLiveService.deactivateGameInvites();
} catch (e: any) {
} catch (e: unknown) {
console.warn("Stop hosting failed", e);
}
setIsHosting(false);
@@ -237,16 +238,17 @@ const LceLiveView = memo(function LceLiveView() {
25565,
)
.then(() => setHostStatus("Relay active"))
.catch((relayErr: any) => {
.catch((relayErr: unknown) => {
const relayMsg =
typeof relayErr === "string"
? relayErr
: relayErr?.message || "Unknown error";
relayErr instanceof Error ? relayErr.message
: typeof relayErr === "string"
? relayErr
: "Unknown error";
console.warn("Relay failed:", relayMsg);
setHostStatus("Relay disconnected");
});
} catch (e: any) {
const msg = typeof e === "string" ? e : e?.message || "Unknown error";
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error";
setErrorModal("Failed to send invite: " + msg);
}
};
@@ -377,7 +379,7 @@ const LceLiveView = memo(function LceLiveView() {
showHostMethodPicker,
]);
const tabs = ["friends", "requests", "invites"];
const tabs: ("friends" | "requests" | "invites")[] = ["friends", "requests", "invites"];
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (errorModal) {
@@ -433,14 +435,14 @@ const LceLiveView = memo(function LceLiveView() {
const curIdx = tabs.indexOf(currentTab);
if (e.key === "q" || e.key === "Q" || e.key === "ArrowLeft") {
const next = curIdx > 0 ? tabs[curIdx - 1] : tabs[tabs.length - 1];
setCurrentTab(next as any);
setCurrentTab(next);
setFocusIndex(0);
playPressSound();
return;
}
if (e.key === "e" || e.key === "E" || e.key === "ArrowRight") {
const next = curIdx < tabs.length - 1 ? tabs[curIdx + 1] : tabs[0];
setCurrentTab(next as any);
setCurrentTab(next);
setFocusIndex(0);
playPressSound();
return;
@@ -774,7 +776,7 @@ const LceLiveView = memo(function LceLiveView() {
imageRendering: "pixelated",
}}
onClick={() => {
setCurrentTab(t as any);
setCurrentTab(t);
setFocusIndex(0);
playPressSound();
}}
+1 -1
View File
@@ -50,7 +50,7 @@ export default function LocEditorView() {
if (!loc) return;
playPressSound();
const buffer = ArcService.serializeLOC(loc);
const blob = new Blob([buffer as any]);
const blob = new Blob([buffer]);
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
+4 -4
View File
@@ -32,9 +32,9 @@ export default function OptionsEditorView() {
const parsed = OptionsService.readOptions(buffer);
setOpt(parsed);
showNotification(`Loaded options.dat`);
} catch (err: any) {
} catch (err: unknown) {
console.error("Failed to parse Options", err);
showNotification(err.message || "Failed to parse Options", "error");
showNotification(err instanceof Error ? err.message : "Failed to parse Options", "error");
}
e.target.value = "";
};
@@ -52,12 +52,12 @@ export default function OptionsEditorView() {
a.click();
URL.revokeObjectURL(url);
showNotification("Options Saved");
} catch (err: any) {
} catch (err: unknown) {
showNotification("Failed to save", "error");
}
};
const updateSetting = (field: keyof OptionsFile, value: any) => {
const updateSetting = (field: keyof OptionsFile, value: string | number | boolean | number[]) => {
if (!opt) return;
setOpt({ ...opt, [field]: value });
};
+13 -11
View File
@@ -68,7 +68,7 @@ export default function PckEditorView() {
});
});
const convert = (nodes: Record<string, TempNode>): any[] => {
const convert = (nodes: Record<string, TempNode>): TreeNode[] => {
return Object.values(nodes)
.sort((a, b) => {
if (a.isFolder && !b.isFolder) return -1;
@@ -107,7 +107,7 @@ export default function PckEditorView() {
return;
}
const blob = new Blob([selectedAsset.data as any], { type: "image/png" });
const blob = new Blob([selectedAsset.data], { type: "image/png" });
const url = URL.createObjectURL(blob);
setAssetPreview({ id: selectedAsset.id, url });
@@ -128,17 +128,19 @@ export default function PckEditorView() {
setExpandedFolders(next);
};
const renderTree = (nodes: any[], depth = 0) => {
type TreeNode = { name: string; path: string; isFolder: boolean; children: TreeNode[]; asset?: PCKAsset };
const renderTree = (nodes: TreeNode[], depth = 0) => {
return nodes.map((node) => {
const isExpanded = expandedFolders.has(node.path) || !!searchTerm;
const isSelected = selectedAssetId === node.asset?.id;
const isSelected = node.asset ? selectedAssetId === node.asset.id : false;
return (
<div key={node.path} className="flex flex-col">
<div
onClick={() => {
if (node.isFolder) {
toggleFolder(node.path);
} else {
} else if (node.asset) {
playPressSound();
setSelectedAssetId(node.asset.id);
}
@@ -175,7 +177,7 @@ export default function PckEditorView() {
<span className="truncate mc-text-shadow text-base">
{node.name}
</span>
{!node.isFolder && (
{!node.isFolder && node.asset && (
<span className="ml-auto text-[10px] opacity-40 uppercase">
{(node.asset.size / 1024).toFixed(1)} KB
</span>
@@ -202,7 +204,7 @@ export default function PckEditorView() {
setOpenedPath(path);
setSelectedAssetId(parsed.files[0]?.id || null);
setExpandedFolders(new Set());
} catch (err: any) {
} catch (err: unknown) {
if (err !== "CANCELED") {
console.error("Failed to parse PCK", err);
showNotification("Failed to parse PCK", "error");
@@ -246,7 +248,7 @@ export default function PckEditorView() {
playPressSound();
await TauriService.writeBinaryFile(path, asset.data);
showNotification(`Exported: ${fileName}`);
} catch (err: any) {
} catch (err: unknown) {
if (err !== "CANCELED") showNotification("Export failed", "error");
}
};
@@ -413,7 +415,7 @@ export default function PckEditorView() {
);
}
showNotification("All Assets Exported");
} catch (err: any) {
} catch (err: unknown) {
if (err !== "CANCELED") showNotification("Export failed", "error");
}
};
@@ -438,8 +440,8 @@ export default function PckEditorView() {
setOpenedPath(targetPath);
showNotification("PCK Saved Successfully");
}
} catch (err: any) {
if (err !== "CANCELED") showNotification("Save failed", "error");
} catch (err: unknown) {
if (err !== "CANCELED") showNotification("Export failed", "error");
}
};
+2 -1
View File
@@ -6,6 +6,7 @@ import {
useGame,
useConfig,
} from "../../context/LauncherContext";
import type { Edition } from "../../types/edition";
import {
ScreenshotService,
ScreenshotInfo,
@@ -147,7 +148,7 @@ const ScreenshotsView = memo(function ScreenshotsView() {
}, [gridFocusIndex, selectedScreenshot]);
const getEditionLogo = (instanceId: string) => {
const edition = editions.find((e: any) => e.id === instanceId);
const edition = editions.find((e: Edition) => e.id === instanceId);
return edition?.logo || edition?.titleImage;
};
+7 -6
View File
@@ -227,7 +227,7 @@ const SettingsView = memo(function SettingsView() {
label: string;
type: "slider";
value: number;
onChange: (val: any) => void;
onChange: (val: number) => void;
}
| {
id: string;
@@ -474,7 +474,8 @@ const SettingsView = memo(function SettingsView() {
const item = settingsItems[focusIndex];
if (item.type === "slider") {
const delta = e.key === "ArrowRight" ? 5 : -5;
item.onChange((v: number) => Math.max(0, Math.min(100, v + delta)));
const newVal = Math.max(0, Math.min(100, item.value + delta));
item.onChange(newVal);
}
} else if (e.key === "Enter" && focusIndex !== null) {
const item = settingsItems[focusIndex];
@@ -583,8 +584,8 @@ const SettingsView = memo(function SettingsView() {
);
}
const isRed = (item as any).color === "red";
const isSmall = (item as any).small;
const isRed = ("color" in item && (item as { color: string }).color === "red");
const isSmall = "small" in item && (item as { small: boolean }).small;
return (
<button
@@ -651,8 +652,8 @@ const SettingsView = memo(function SettingsView() {
);
}
const isRed = (item as any).color === "red";
const isSmall = (item as any).small;
const isRed = item.type === "button" && item.color === "red";
const isSmall = item.type === "button" && !!item.small;
const isToggle = isToggleOption(item.label);
const toggleState = isToggle ? getToggleState(item.label) : false;
+2 -2
View File
@@ -165,9 +165,9 @@ const SkinsView = memo(function SkinsView() {
const skinBase64 = `data:image/png;base64,${base64Raw}`;
processSkinImage(skinBase64, exactName.substring(0, 16));
}
} catch (e: any) {
} catch (e: unknown) {
setImportError(
typeof e === "string" ? e : e.message || "Failed to fetch",
e instanceof Error ? e.message : typeof e === "string" ? e : "Failed to fetch",
);
} finally {
setIsImporting(false);
+6 -6
View File
@@ -40,7 +40,7 @@ export default function SwfView() {
if (imageUrls[img.id]) return imageUrls[img.id];
let url = "";
if (img.type === "jpeg") {
const blob = new Blob([img.data as any], { type: "image/jpeg" });
const blob = new Blob([img.data], { type: "image/jpeg" });
url = URL.createObjectURL(blob);
} else if (img.type === "lossless") {
const rgba = await SwfService.decodeLosslessToRGBA(img);
@@ -80,7 +80,7 @@ export default function SwfView() {
setSelectedImageId(extracted[0].id);
}
showNotification(`Loaded ${file.name}`);
} catch (e: any) {
} catch (e: unknown) {
console.error(e);
showNotification("Failed to process SWF", "error");
setImages([]);
@@ -99,7 +99,7 @@ export default function SwfView() {
let blob: Blob;
let ext = "png";
if (img.type === "jpeg") {
blob = new Blob([img.data as any], { type: "image/jpeg" });
blob = new Blob([img.data], { type: "image/jpeg" });
ext = "jpg";
} else if (img.type === "lossless") {
const rgba = await SwfService.decodeLosslessToRGBA(img);
@@ -114,11 +114,11 @@ export default function SwfView() {
const res = await fetch(dataUrl);
blob = await res.blob();
} else {
blob = new Blob([rgba as any], { type: "application/octet-stream" });
blob = new Blob([rgba], { type: "application/octet-stream" });
ext = "bin";
}
} else {
blob = new Blob([img.data as any], { type: "application/octet-stream" });
blob = new Blob([img.data], { type: "application/octet-stream" });
ext = "bin";
}
@@ -162,7 +162,7 @@ export default function SwfView() {
if (!swfData) return;
playPressSound();
const result = SwfService.serialize(swfData.version, swfData.compressed, swfData.frameHeader, swfData.tags);
const blob = new Blob([result as any], { type: "application/x-shockwave-flash" });
const blob = new Blob([result], { type: "application/x-shockwave-flash" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
+7 -6
View File
@@ -10,6 +10,7 @@ import {
useGame,
} from "../../context/LauncherContext";
import { ScreenshotImage } from "../common/ScreenshotImage";
import type { Edition } from "../../types/edition";
interface DeleteConfirmButtonProps {
label: string;
onClick: () => void;
@@ -73,14 +74,14 @@ const VersionsView = memo(function VersionsView() {
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
const [isSetUidModalOpen, setIsSetUidModalOpen] = useState(false);
const [setUidTargetId, setSetUidTargetId] = useState("");
const [editingEdition, setEditingEdition] = useState<any>(null);
const [editingEdition, setEditingEdition] = useState<Edition | null>(null);
const [initialPath, setInitialPath] = useState<string>("");
const [hoveredBtn, setHoveredBtn] = useState<{
row: number;
btn: string;
} | null>(null);
const [openMenuId, setOpenMenuId] = useState<string | null>(null);
const [deleteConfirmEdition, setDeleteConfirmEdition] = useState<any>(null);
const [deleteConfirmEdition, setDeleteConfirmEdition] = useState<Edition | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
const listRef = useRef<HTMLDivElement>(null);
const ITEM_COUNT = editions.length + 3;
@@ -185,7 +186,7 @@ const VersionsView = memo(function VersionsView() {
}
}, [focusIndex]);
const handleEditionClick = (edition: any, index: number) => {
const handleEditionClick = (edition: Edition, index: number) => {
const isInstalled = installedVersions.includes(edition.instanceId);
if (isInstalled) {
playPressSound();
@@ -233,7 +234,7 @@ const VersionsView = memo(function VersionsView() {
className="w-full max-h-[45vh] overflow-y-auto py-2 custom-scrollbar"
>
<div className="flex flex-col gap-1">
{editions.map((edition: any, i: number) => {
{editions.map((edition: Edition, i: number) => {
const isInstalled = installedVersions.includes(
edition.instanceId,
);
@@ -514,7 +515,7 @@ const VersionsView = memo(function VersionsView() {
addToSteam(
edition.instanceId,
edition.name,
edition.titleImage,
edition.titleImage ?? "",
panoramaUrl,
);
setOpenMenuId(null);
@@ -702,7 +703,7 @@ const VersionsView = memo(function VersionsView() {
setEditingEdition(null);
setInitialPath("");
}}
onImport={(ed: any) => {
onImport={(ed: { name: string; desc: string; url: string; path?: string }) => {
if (editingEdition) {
onUpdateEdition(editingEdition.id, ed);
} else {
+7 -6
View File
@@ -20,6 +20,7 @@ import {
import {
TauriService,
InstalledWorkshopPackage,
type CustomEdition,
} from "../../services/TauriService";
const REGISTRY_URL =
"https://raw.githubusercontent.com/LCE-Hub/LCE-Workshop/refs/heads/main/registry.json";
@@ -224,7 +225,7 @@ const WorkshopView = memo(function WorkshopView() {
(pkgId: string) => {
if (activeTab === "Versions") {
const isAdded = config.customEditions?.some(
(e: any) =>
(e: CustomEdition) =>
e.id === pkgId ||
e.url === versionPackages.find((p) => p.id === pkgId)?.url,
);
@@ -252,7 +253,7 @@ const WorkshopView = memo(function WorkshopView() {
if (activeTab === "Versions") {
return (
config.customEditions?.some(
(e: any) =>
(e: CustomEdition) =>
e.id === pkgId ||
e.url === versionPackages.find((p) => p.id === pkgId)?.url,
) ?? false
@@ -1471,10 +1472,10 @@ function InstallModal({
pkg.version,
);
setStatus("success");
} catch (e: any) {
} catch (e: unknown) {
console.error(e);
setStatus("error");
setErrorMsg(typeof e === "string" ? e : e.message);
setErrorMsg(e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error");
}
};
@@ -1646,10 +1647,10 @@ function UninstallModal({
await TauriService.workshopUninstall(instanceId, pkg.id);
}
setStatus("success");
} catch (e: any) {
} catch (e: unknown) {
console.error(e);
setStatus("error");
setErrorMsg(typeof e === "string" ? e : e.message);
setErrorMsg(e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error");
}
};