The Technical Journey of the Orbit Browser Extension
You may have heard by now that we’ve recently released the Orbit Browser Extension. This is an open-source widget that brings Orbit data directly to Github, Twitter, LinkedIn, and Gmail. To support our public building of it and help folks eager to contribute in the future, we wanted to put this blog post together to share our thoughts, feelings, and decisions we made during development.
Just between us ( 🤐), there was a version of the extension that existed before we started our revamp. We weren’t exactly starting from square one, but we wanted to rebuild the extension from the ground up with a focus on clean, extensible architecture to promote future contributions.
What did we start with?
The main drive for our work to extend the extensions capabilities was to add support for other sites. The previous version of the extension was fully functional but tightly coupled to GitHub & not too pretty (sorry Nico 😖). It really did make sense to start from scratch…
So, in direct contradiction with the general wisdom, we did.
Setting the Stage for Development
The latter of these set up hot reloading, which significantly enhanced our development workflow—before you’d need to rebuild & re-import the extension for every single change. With these cleared, we were in a much better position to move forward with the new widget.
Structuring the new widget
So, preamble aside: we wanted to build this extension to be easily extensible. Which begs the question, “how?”. Here’s some of the data models & ideas we ended up with 👀
A page stores the logic for interacting with various elements of the DOM. When planning, we realised we would face a number of challenges as part of injecting the widget across different websites, and it made sense to group that logic together. For example:
- Where can we insert the widget on this page?
- How do we find a username on this page?
- How should we style the widget button to match the surrounding page?
All logic regarding the DOM of a specific page, for example a “TwitterProfilePage”, is kept within these classes.
Each website requires unique event handling for widget rendering, which is all defined at the entrypoint.
We needed a variety of these because different websites require us to listen to different events in order to decide when to render the widget. For example GitHub, the simplest, just needs to listen to
turbo:render events – for when the page loads, and for when the user navigates from page to page, respectively.
Once these events are hooked up, the most important responsibility of the entrypoint is to boot up the WidgetOrchestrator.
The Widget Orchestrator
The orchestrator detects which page the user is on, and inserts the widget into the DOM; the elements it inserts are an Orbit button, additional data slot, and the widget itself.
Sidenote: the orchestrator is stateless, which is ideal for reducing coupling & supports writing automated tests – it’s a series of functions with a specific input & do a single thing, which makes them easier to test in isolation.
Once we've inserted the widget elements into the DOM, we need to actually populate it with useful content, which is where the widget itself takes over.
The widget handles user interactions, fetching data from the Orbit API and managing display states. It offers a quick, at-a-glance overview of a member's data. This is how it looks:
It shows practical and helpful information about a member so you can understand them at a glance. We are always open to suggestions if you think there’s something else we could show!
The Background Runner
As part of rebuilding the application, we moved API requests out of the main widget and into a
background.js runner on a separate thread. The primary benefit of this is that the API requests are non-blocking; we can make requests without slowing down the actual user interface, which helps deliver a more streamlined and performative experience. It also makes CORS rules easier, as all requests now come from a single place, rather than multiple websites.
Bringing it all together
So, with these puzzle pieces all together we end up with a fairly comprehensive code architecture as follows:
It’s something we’re really excited to work on in the future, and can’t wait to bring even more support and functionality to the browser extension.
Getting the extension to this point has been a genuinely fun programming experience, with all kinds of challenges and puzzles to solve. We’re planning to do a few more blog posts in this vein because there is SO MUCH we want to talk about. At this stage we’re thinking a few key topics could be
- a deeper dive into the tech stack we used (along with a template repo to quickly scaffold a browser extension?),
- a look into the accessibility choices and decisions we made, and
- a discussion of the OAuth flow and how we switched over to it – this is a topic that, no matter how much I read, I never understand any clearer 😵💫 perhaps writing one myself will help…
Give us a poke if any of these interest you. Any issues with the extension, or ideas for how we could improve it? Get in touch!