Local Golinks
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!
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.
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 };
}
.webRequest.onBeforeRequest.addListener(redirect, { urls: ['http://go/*']}, ['blocking']); browser
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.
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() {
.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)
http}
The /all
route renders a HTML page with all the existing
golinks. Here’s mine at time of writing:
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
- Go to chrome://extensions
- Toggle the “Developer Mode” setting in the top right
- Click the “Load Unpacked” button on the top left
- Select the “extension” folder
- Pin the extension to your address bar for ease of use
To install the webserver
- Make sure Go is installed on your machine
cd
into theserver
directory- Run
go build
which will compile the server into an executable calledserver
- 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
- The golink system at Google
- Utopian Software: https://ratfactor.com/cards/utopian-software
- Ian Fisher’s golinks: https://iafisher.com/blog/2020/10/golinks