Writing an xbar plugin for Plausible.io

How I wrote a JS-plugin for the xbar-app to see the current number of visitors

Writing an xbar-plugin in Javascript

As a weekend-project I wanted to write a simple plugin for xbar, an application I recently stumbled that. xbar itself is written in the programming language “Go” and uses “Wails” as its underlying service to enable developers write custom menu entries for the menu bar in macOS (yes, it’s currently limited to macOS only).

The nice thing about xbar is that it supports a wide variety of programming languages, which also includes Javascript. As a web dev myself, I therefore wanted to see how easy it is to write a custom plugin that connects to Plausible.io, my privacy-friendly analytics service. The goal is to render all current users that are currently on this web app. As of writing, the pull request is still in review.

Image 52d29c4414ce

Image 67e4f6e5107d

Basic xbar setup

xbar can simply be downloaded and installed on your Mac. Once you start it for the first time, a screen that contains all downloadable and published plugins can be used to obtain the plugin you want to have.

The installing process simply copies the plugin’s source code (a single file per plugin) into a special folder, from which xbar reads all currently installed plugins. To start writing your own plugin, you simply create a file in the directory and start hacking. Neat!

xbar file name convention

The file’s name consists of three parts, all separated by dot.

  • your unique plugin name
  • the time interval in which your code gets executed, similar to a CRON-job
  • the common file suffix
plausible.1m.js

Coding an xbar plugin in JS

Now you’re set up to start coding! First include the required shebang directive to tell xbar where the node instance should be found.

#!/usr/bin/env /usr/local/bin/node

The next part is then adding some meta data. Of course, for local development this can be omitted, but here’s what I’m using for the Plausible-plugin.

// Metadata allows your plugin to show up in the app, and website.
//
//  <xbar.title>Plausible Tracker</xbar.title>
//  <xbar.version>v1.0</xbar.version>
//  <xbar.author>Tom Schönmann</xbar.author>
//  <xbar.author.github>flaming-codes</xbar.author.github>
//  <xbar.desc>See who's on your site at-a-glance.</xbar.desc>
//  <xbar.dependencies>node</xbar.dependencies>
//  <xbar.abouturl>https://flaming.codes</xbar.abouturl>
//  <xbar.image>https://raw.githubusercontent.com/flaming-codes/xbar-plausible-stats/main/plausible-icon-36-36-144.png</xbar.image>

And that's all that’s required for your plugin to run. Now you have a canvas where you can code in pure Javascript, executed by the user’s Node.js-instance. This means we have access to all core Node-packages, such as “https”. For my use case, this is everything I need, as the tracking of users simply requires a fetch from the Plausible.io-API.

The following pieces of code show the most relevant part that I think are worth talking about. The full code is available in the related public repository, link in the addendum at the end of this page.

// Here you see how to make network
// requests with 'https'-only.
// This is a native node-lib that
// works w/ data streams, as you'll see.

const https = require("https");

// Those have to edited by the
// user directly in the file,
// after it has been downloaded
// (aka installed)!
const SITE_ID = "";
const API_KEY = "";

// ... other code ...

async function fetcher() {
  return new Promise((resolve, reject) => {
    let body = "";
    const request = {
      host: "plausible.io",
      path: `/api/v1/stats/realtime/visitors?site_id=${SITE_ID}`,
      method: "GET",
      headers: {
        Authorization: `Bearer ${API_KEY}`,
      },
    };

    try {
      const req = https.get(request, (res) => {
        res.on("data", (data) => {
          body += data;
        });
        res.on("end", () => {
          resolve(JSON.parse(body));
        });
      });

      req.on("error", (error) => {
        console.error(error);
      });

      req.end();
    } catch (error) {
      reject(error);
    }
  });
}
// I've implemented an array of tuples
// that hold an icon + count threshold
// when to active it.

// The first entry doesn't render a 
// string (which can be direclty used),
// but rather a mapping that results in
// the following code:
//
// `image="..." font=Menlo color=white`
//
// This is a special syntax that tells
// xbar to render a base64 image w/
// a custom font and color.
//
// 'plausibleIconWhite' is just the string
// for the base64-image.
const stepIcons = [
  [0, `${plausibleIconWhite} Menlo white`, "image font color"],
  [5, "💫"],
  [10, "⭐️"],
  [50, "🌟"],
  [100, "⚡️"],
  [500, "💥"],
];
// Actually rendering stuff in xbar
// is super simple - just output it
// with console.log(...).
//
// As you'll see, '---' define 
// separator. The first log-call
// gets rendered as acutal menu item.

const linksMenu = [
  "---",
  `🔮 Open dashboard | href=https://plausible.io/${SITE_ID}`,
  `🔥 Made by flaming.codes | href=https://flaming.codes`,
];

function renderError(props) {
  const { error } = props;
  const output = [
    "❔",
    "---",
    "No data accessible",
    "Please check your user data",
    ...linksMenu,
  ];

  console.log(output.join("\n"));
}

// Finally, I defined a single function
// where everything starts. This function
// just gets called to kick everyting off.
// Plain JS, it's that simple.

async function render() {
  const { data, error } = await fetcher()
    .then((data) => ({ data }))
    .catch((error) => ({ error }));

  if (data >= 0) return renderData({ data });
  if (error) return renderError({ error });
}

render();

Coding for xbar is easy

The whole process took me a few hours to complete, mostly checking the API from xbar as well as playing around with different configurations for styling. All in all, writing a simple plugin like this one really is not much effort that is real fun to do. If you have some spare hours, you should try it as well!

I think what speaks most for xbar is the scripting opportunity that this flexible and fast development allows. You can write custom trackers for your cloud setups or analytics services, so that the metrics are all visible at at glance in your menu bar in macOS. I like it!

Suggestions

Related

Addendum

Languages