krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.10.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 const pathSegment = path ? `/${path}` : "";
72 return `hutch://${service}${pathSegment}${url.search}${url.hash}`;
73}
74
75function showUnsupportedMessage(tabId) {
76 if (!tabId) {
77 return;
78 }
79
80 const message = "Open in Hutch supports git, hg, todo, builds, lists, meta, and sr.ht pages.";
81
82 if (browserAPI?.scripting?.executeScript) {
83 browserAPI.scripting.executeScript({
84 target: { tabId },
85 func: (text) => {
86 window.alert(text);
87 },
88 args: [message],
89 });
90 return;
91 }
92
93 browserAPI?.tabs?.sendMessage?.(tabId, { type: "hutchUnsupportedPage", message });
94}
95
96function openURL(tabId, url) {
97 if (browserAPI?.tabs?.update && tabId) {
98 browserAPI.tabs.update(tabId, { url });
99 return;
100 }
101
102 if (browserAPI?.tabs?.create) {
103 browserAPI.tabs.create({ url });
104 }
105}
106
107browserAPI.action.onClicked.addListener((tab) => {
108 const hutchURL = tab?.url ? hutchDeepLinkFor(tab.url) : null;
109 if (!hutchURL) {
110 showUnsupportedMessage(tab?.id);
111 return;
112 }
113
114 console.debug("[Hutch] Opening deep link", { sourceURL: tab.url, hutchURL });
115 openURL(tab.id, hutchURL);
116});