krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.4.0: HutchSafariExtension/Resources/background.js · raw
1const supportedHosts = new Set([
2 "git.sr.ht",
3 "hg.sr.ht",
4 "todo.sr.ht",
5 "builds.sr.ht",
6 "lists.sr.ht",
7 "meta.sr.ht",
8 "sr.ht",
9]);
10
11const browserAPI = globalThis.browser ?? globalThis.chrome;
12
13function isOwnerRootPath(path) {
14 const components = path.split("/").filter(Boolean);
15 return components.length === 1 && components[0].startsWith("~");
16}
17
18function normalizedPath(pathname) {
19 return pathname.split("/").filter(Boolean).join("/");
20}
21
22function deepLinkPath(hostname, path) {
23 const components = path.split("/").filter(Boolean);
24 if (
25 hostname === "sr.ht" &&
26 components.length === 2 &&
27 components[0] === "projects" &&
28 components[1].startsWith("~")
29 ) {
30 return components[1];
31 }
32
33 return path;
34}
35
36function deepLinkService(hostname, path) {
37 if (isOwnerRootPath(path)) {
38 return "lookup";
39 }
40
41 switch (hostname) {
42 case "git.sr.ht":
43 return "git";
44 case "hg.sr.ht":
45 return "hg";
46 case "todo.sr.ht":
47 return "todo";
48 case "builds.sr.ht":
49 return "builds";
50 case "lists.sr.ht":
51 return "lists";
52 default:
53 return "lookup";
54 }
55}
56
57function hutchDeepLinkFor(rawURL) {
58 let url;
59 try {
60 url = new URL(rawURL);
61 } catch {
62 return null;
63 }
64
65 if (url.protocol !== "https:" || !supportedHosts.has(url.hostname)) {
66 return null;
67 }
68
69 const path = deepLinkPath(url.hostname, normalizedPath(url.pathname));
70 const service = deepLinkService(url.hostname, path);
71 return `hutch://${service}${path ? `/${path}` : ""}${url.search}${url.hash}`;
72}
73
74function showUnsupportedMessage(tabId) {
75 if (!tabId) {
76 return;
77 }
78
79 const message = "Open in Hutch supports git, hg, todo, builds, lists, meta, and sr.ht pages.";
80
81 if (browserAPI?.scripting?.executeScript) {
82 browserAPI.scripting.executeScript({
83 target: { tabId },
84 func: (text) => {
85 window.alert(text);
86 },
87 args: [message],
88 });
89 return;
90 }
91
92 browserAPI?.tabs?.sendMessage?.(tabId, { type: "hutchUnsupportedPage", message });
93}
94
95function openURL(tabId, url) {
96 if (browserAPI?.tabs?.update && tabId) {
97 browserAPI.tabs.update(tabId, { url });
98 return;
99 }
100
101 if (browserAPI?.tabs?.create) {
102 browserAPI.tabs.create({ url });
103 }
104}
105
106browserAPI.action.onClicked.addListener((tab) => {
107 const hutchURL = tab?.url ? hutchDeepLinkFor(tab.url) : null;
108 if (!hutchURL) {
109 showUnsupportedMessage(tab?.id);
110 return;
111 }
112
113 console.debug("[Hutch] Opening deep link", { sourceURL: tab.url, hutchURL });
114 openURL(tab.id, hutchURL);
115});