Local Golinks

A picture of the Dallas GoLink van. A white van with the words
My own smart bookmark tool

Golinks are a kind of shared bookmark system commonly used in big tech companies. They allow you to assign some easy-to-remember link (golink) to a URL.

For example, you might have go/ragman-notes point to some long URL like https://www.docs.google.com/d/document/3G1e….

Then anybody at the company could type go/ragman-notes into their browser, and they’d get redirected to the URL. They were used all the time when I worked at Google.

I’ve been getting increasingly interested in building little fun utility tools to make my personal computing experience better. Just little personal programs that I can whip up without having to think too much about UI or security. Or as Dave Gauer of ratfactor.com puts it: utopian software.

So when I was reminded of golinks the other day I thought: I should build it for myself!

Now, I don’t think golinks are nearly as useful for an individual as they are in a big organization. One of the main benefits is making it easy to share URLs between people verbally, which isn’t an issue when it’s just yourself! Additionally, Chrome is pretty good at guessing the website I want to go from just a few letters typed into the address bar, so I really don’t know how much I’ll use this.

But that’s the best thing about utopian software: it doesn’t have to be “a good idea”, it can just be fun!

A video showing the working Chrome extension. It redirects from go/blog to ragman.net.

Go to the Host It Yourself section below if you want to run this yourself!


How It’s Made

The first thing I did was to look around to see if there was anybody that I could crib off of. Fortunately, there was!

Ian Fisher wrote a great blog post detailing his own personal golink system. I highly recommend reading it if you’re interested in building one. He goes into good detail, and also talks about the history behind the golinks concept.

Ian’s system consists of two parts. First, a Chrome extension that redirects requests from “go/whatever” to “localhost:5000/go/whatever”. Then a Python webserver on “localhost:5000” that looks up the passed golink and redirects to the mapped URL if it exists.

His webserver has an in-memory map of golinks to URLs – to add a new one you just update the map.

A visual representation of the explanation above.

This design seemed good, but I decided to add a few extra features:

  • A clickable Chrome extension popup that would let me add a new golink for the current URL
  • A way to see all golinks for the current URL
  • A way to see all golinks (go/all)

So with Ian’s design as a base, I started coding!


Chrome Extension Redirect

I started by copying Ian’s Chrome extension code. It’s a wonderfully simple six lines of code that handle the redirection to localhost.

Ian Fisher’s extension

function redirect(request) {
  const prefix = 'http://go/';
  const path = request.url.slice(prefix.length);
  return { redirectUrl: 'http://localhost:5000/go/' + path };
}

browser.webRequest.onBeforeRequest.addListener(redirect, { urls: ['http://go/*']}, ['blocking']);

Unfortunately for me, this code was written for Chrome Extension “Manifest V2” which is actively being phased out for V3. In V3, the browser.webRequest API is only allowed for spyware extensions installed in a corporate settings (i.e. “policy installed extensions”).

Instead, I had to use the new declarativeNetRequest API, which is a custom config language that is poorly documented, fails silently, is hard to debug, and which uses its own custom regex-like language 🤮. Thirty minutes of swearing later (so much for utopian software!) I had it done:

manifest.json

{
    "manifest_version": 3,
    "name": "golinks",
    "version": "0.1",
    "description": "Redirects http://go/ links to localhost:7575",
    "declarative_net_request": {
      "rule_resources" : [{
        "id": "ruleset_1",
        "enabled": true,
        "path": "rules.json"
      }]
    },
    "host_permissions": [
      "http://go/*",
      "https://go/*",
      "http://localhost/*"
    ],
    "action": {
      "default_title": "golink",
      "default_popup": "popup.html",
      "default_icon": "icon.png"
    },
    "permissions": ["activeTab", "tabs", "declarativeNetRequest","declarativeNetRequestFeedback"]
  }

Note that the parts that handle redirection are the declarative_net_request, host_permissions, and permissions objects. The rest is for the future popup.

rules.json

[{
  "id": 1,
  "condition": {
    "resourceTypes": ["main_frame"],
    "urlFilter": "||go",
  },
  "action": {
    "type": "redirect",
    "redirect": {
      "transform": {
        "host": "localhost",
        "port": "7575"
      }
    }
  }
}]

The declarative_net_request object in the manifest points to the rules.json file, which has an action which does the redirect.

The host_permissions object in the manifest declares which domains the extension runs on as well as which it’s allowed to make requests to. For the redirect you only need https://go/*, but localhost is needed for the extension popup to be able to make requests to the webserver.

The urlFilter condition uses the regex-like-language to filter the redirect to only happen for domains that start with go. Without this, any requests to localhost will get redirected to localhost:7575.

It doesn’t look too bad written out here, but it took me ages to figure out the right combination of things I needed, and if anything was wrong (e.g. not having localhost in host_permissions object in rules.json), the extension would fail silently.

A picture of grumpy cat -- a grey and white looking cat with a scowl on its face.
Thanks Google!

Chrome Extension Popup

This enables the additional feature I wanted – the ability to quickly create a new golink for the current tab.

I kept it pretty simple. Chrome extensions have a popup action that lets you render an HTML file on click.

My HTML is just a form with a textbox to enter your new golink, and a <script> tag pointing to my JS.

popup.html

<html>
    <script src="popup.js"></script>
    <form id="form" style="margin-top: 1em;">
        <label for="golink"><b>New link:</b></label>
        <input autofocus type="text" id="golink" name="golink" style="margin-top: 3px"><br><br>
        <input type="submit" value="Submit">
    </form>
</html>

The popup.js file makes a GET request to the webserver (http://localhost:7575/list?url=${url}) for the list of existing golinks for the current tab. If any exist, it renders them above the form.

Other than that, clicking “Submit” on the form sends a request to the webserver to add the new link.

Errors from the webserver are displayed in red below the form.

Webserver

The webserver isn’t particularly interesting. I wrote it in Go, using a CSV file to store my mapping of golinks to URLs.

The http routing looks like this:

func main() {
    http.HandleFunc("GET /all", showAll)
    http.HandleFunc("GET /list", linksForURL)
    http.HandleFunc("GET /{golink}", redirectToLink)

    http.HandleFunc("POST /create", createLink)
    http.HandleFunc("GET /delete/{golink}", deleteLink)

    http.ListenAndServe(":7575", nil)
}

The /all route renders a HTML page with all the existing golinks. Here’s mine at time of writing:

A picture the /all page which shows all my golinks.
Hopefully I’ll add more with time!

The /delete/ route isn’t currently used – I might add a button on the extension popup to delete links later, but for now just deleting them from the CSV file works fine.

The code isn’t my best, but it gets the job done!

What’s next?

I dunno! I might add the delete feature I mention above, I might not.

Possibly I’ll see about hosting the webserver on some VPS and sharing links between friends, that might be fun.

I won’t be productionalizing this. golinks.io already exists and it would be way too much work!

Host it yourself!

You can download the code for both the extension and the server here: golinks.zip.

The code comes with a GPL license – the code is provided freely, as-is, no warranty. If you redistribute it, it’s gotta be open-source too.

I offer no assurances about the quality of the code, or that it will work in the future, but if you have any feedback feel free to email me at mailto:email@ragman.net.

Prequisites

You’ll need Google Chrome and Go installed on your machine. You might be able to get it to work on Firefox, but I don’t know what the state of the declarativeNetRequest API is there.

The Go webserver code has no external dependencies outside of the standard library. It should be compilable on any common OS.

Installing

First, unzip the golinks.zip file above. You should have a folder with server and extension sub-directories. Then:

To install the extension

  1. Go to chrome://extensions
  2. Toggle the “Developer Mode” setting in the top right
  3. Click the “Load Unpacked” button on the top left
  4. Select the “extension” folder
  5. Pin the extension to your address bar for ease of use

To install the webserver

  1. Make sure Go is installed on your machine
  2. cd into the server directory
  3. Run go build which will compile the server into an executable called server
  4. Run the new server executable

And that should do it! You’ll find a links.csv file created in the server directory, which is the source-of-truth for your golinks.

You may want to run your webserver as a background process. On my Mac I have an alias which does this and also logs output to a txt file:

alias golink='cd ~/dev/golinks/server && go build && nohup ./golink &>> logs.txt &'

If I make any changes to the system, I’ll update the download and leave a note with changes. Happy golinking!

Inspirations