1import type { ScopedThreadRef } from "@t3tools/contracts";
2import {
3 isAtomCommandInterrupted,
4 squashAtomCommandFailure,
5} from "@t3tools/client-runtime/state/runtime";
6import { recordVisitForThread } from "~/browserHistoryStore";
7import { readLocalApi } from "~/localApi";
8import { previewEnvironment } from "~/state/preview";
9import { useAtomCommand } from "~/state/use-atom-command";
10
11import {
12 canOpenLinksInApp,
13 resolveBrowserLinkTargetPreference,
14 resolveLinkTarget,
15} from "./browserLinkTarget";
16import { isBrowserSettingsReadError, openUrlInPreview } from "./openFileInPreview";
17
18const NO_MODIFIER = { metaKey: false, ctrlKey: false } as const;
19
20/** Rejects on settings read failure without opening a browser, or if the system-browser fallback fails. */
21export function useOpenLink(threadRef: ScopedThreadRef | null | undefined): (
22 url: string,
23 options?: {
24 readonly event?: {
25 readonly metaKey: boolean;
26 readonly ctrlKey: boolean;
27 readonly button?: number | undefined;
28 };
29 readonly threadRef?: ScopedThreadRef | undefined;
30 },
31) => Promise<void> {
32 const openPreview = useAtomCommand(previewEnvironment.open, { reportFailure: false });
33 return async (url, options = {}) => {
34 const targetThreadRef = options.threadRef ?? threadRef;
35 const target = resolveLinkTarget({
36 url,
37 event: options.event ?? NO_MODIFIER,
38 preference: await resolveBrowserLinkTargetPreference(),
39 canOpenInApp: canOpenLinksInApp(targetThreadRef),
40 });
41 if (target === "app" && targetThreadRef) {
42 const result = await openUrlInPreview({ threadRef: targetThreadRef, url, openPreview });
43 if (isAtomCommandInterrupted(result)) return;
44 if (result._tag === "Success") {
45 recordVisitForThread(targetThreadRef, url);
46 return;
47 }
48 const failure = squashAtomCommandFailure(result);
49 if (isBrowserSettingsReadError(failure)) throw failure;
50 console.error(result.cause);
51 }
52 const api = readLocalApi();
53 if (!api) throw new Error("Link opening is unavailable.");
54 await api.shell.openExternal(url);
55 };
56}