krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

main: 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});