summaryrefslogtreecommitdiff
path: root/HutchSafariExtension/Resources/background.js
blob: 81bf8b6a699ee59259b5e3683987216a91839079 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
const supportedHosts = new Set([
  "git.sr.ht",
  "hg.sr.ht",
  "todo.sr.ht",
  "builds.sr.ht",
  "lists.sr.ht",
  "meta.sr.ht",
  "sr.ht",
]);

const browserAPI = globalThis.browser ?? globalThis.chrome;

function isOwnerRootPath(path) {
  const components = path.split("/").filter(Boolean);
  return components.length === 1 && components[0].startsWith("~");
}

function normalizedPath(pathname) {
  return pathname.split("/").filter(Boolean).join("/");
}

function deepLinkPath(hostname, path) {
  const components = path.split("/").filter(Boolean);
  if (
    hostname === "sr.ht" &&
    components.length === 2 &&
    components[0] === "projects" &&
    components[1].startsWith("~")
  ) {
    return components[1];
  }

  return path;
}

function deepLinkService(hostname, path) {
  if (isOwnerRootPath(path)) {
    return "lookup";
  }

  switch (hostname) {
    case "git.sr.ht":
      return "git";
    case "hg.sr.ht":
      return "hg";
    case "todo.sr.ht":
      return "todo";
    case "builds.sr.ht":
      return "builds";
    case "lists.sr.ht":
      return "lists";
    default:
      return "lookup";
  }
}

function hutchDeepLinkFor(rawURL) {
  let url;
  try {
    url = new URL(rawURL);
  } catch {
    return null;
  }

  if (url.protocol !== "https:" || !supportedHosts.has(url.hostname)) {
    return null;
  }

  const path = deepLinkPath(url.hostname, normalizedPath(url.pathname));
  const service = deepLinkService(url.hostname, path);
  return `hutch://${service}${path ? `/${path}` : ""}${url.search}${url.hash}`;
}

function showUnsupportedMessage(tabId) {
  if (!tabId) {
    return;
  }

  const message = "Open in Hutch supports git, hg, todo, builds, lists, meta, and sr.ht pages.";

  if (browserAPI?.scripting?.executeScript) {
    browserAPI.scripting.executeScript({
      target: { tabId },
      func: (text) => {
        window.alert(text);
      },
      args: [message],
    });
    return;
  }

  browserAPI?.tabs?.sendMessage?.(tabId, { type: "hutchUnsupportedPage", message });
}

function openURL(tabId, url) {
  if (browserAPI?.tabs?.update && tabId) {
    browserAPI.tabs.update(tabId, { url });
    return;
  }

  if (browserAPI?.tabs?.create) {
    browserAPI.tabs.create({ url });
  }
}

browserAPI.action.onClicked.addListener((tab) => {
  const hutchURL = tab?.url ? hutchDeepLinkFor(tab.url) : null;
  if (!hutchURL) {
    showUnsupportedMessage(tab?.id);
    return;
  }

  console.debug("[Hutch] Opening deep link", { sourceURL: tab.url, hutchURL });
  openURL(tab.id, hutchURL);
});