Spotify on Workflow
Tags: workflowwm, javascript, showdev, productivity
Published: 2018-09-17 12:44:07

---
title: Spotify on Workflow
published: true
description: How to write a workflow app module
tags: workflow-wm, javascript, showdev, productivity
---

App modules are at the center of [workflow](https://github.com/havardh/workflow). They are the important glue between the internals of workflow and actual applications running on your platform. If you are new to `workflow`, then the [introduction post](https://dev.to/havardh/introducing-workflow-18h6) is a good place to start.

_Updated code samples to support [workflow@2.x](https://github.com/havardh/workflow/commit/ca77860)_.

![spotify code sample](https://thepracticaldev.s3.amazonaws.com/i/lbgcph0q626fo28jh388.png)

This post will guide you through a practical example of extending workflow with support for Spotify. Workflow works cross platform, and the implementation for the various platform differs. If you need to make an app run on a specific platform, the other platforms can be skipped. This post will show how to write apps for `osx` and `i3`.

### Running example 

As a running example in this post we will create a new app component for Spotify. Spotify does not fall into the previously defined categories of applications that `workflow` supports, being Terminals, Browser and Text Editors. So lets pick a simple use case for our initial version of the Spotify app. Spotify has defined a  `uri` specification which can be used for automation. Let us use the `uri` to open Spotify with a given playlist.

### Initializing the application

To get started with writing applications there is a `npm` initializer called `create-workflow-app`. Lets run it with the `npx` cli.

```
npx create-workflow-app workflow-app-spotify
```

This will generate an example application which opens `emacs` it the terminal. The three notable files are `flows/Example.js`, `cli.js` and `src/index.js`. The `cli.js` implements a simple workflow config which lets us test our app, by default it will use `workflow-wm-terminal`. The `yarn example` command is set up to run the `flows/Example.js`.

### Putting our example into code.

Lets start defining our interface by implementing the example. The `spotify` cli takes an `uri` parameter, one variant of this parameter lets us specify a playlist. The `uri`'s format is `spotify:user:<username>:playlist:<playlist-id>`. So lets define an example of using this specification from jsx.

```jsx
<Spotify minimized play>
  <Playlist user={'<username>'} id={'<playlist id>'} />
</Spotify>

```

We have given our top level `Spotify` component a parameter called `minimized` which will cause the Spotify application to be launched minimized. And we have given it a child for specifying the playlist to open. This component has the `username` and `playlist id` properties. The `play` prop on the `Spotify` component will trigger autoplay.

### The application scaffold

In the `src/index.js` file we have the scaffold for making any app for `workflow`. The following properties are mandatory for any app.


```js
const Spotify = {
  type: 'app',
  name: 'Spotify',
  params: ['minimized', 'play'],
  open: ({minimized, play}, context, children) => {
    // code for the app
  }
};
```


The `type` property is used by `workflow` to distinguish the app node from `layout` and `workspace` nodes. The `name` property is used in debugging information an is exposed to the `wm` adapter layer. The `params` is used to validate the arguments passed to the node in the `open` function. The `open` function is responsible for opening the application and making sure it is placed in the expected position on the screen. The parameters to the `open` function are the *parameters* to the node in the flow, a *context variable* which is specific to the underlying platform and windows manager, and any *child nodes* passed to the node. The application it self is free to define the specification of allowed children and arguments. 

### Supporting workflow-wm-i3

Let us start with adding support for the i3 windows manager. The i3 windows manager is identified by `context = {platform: "linux", wm: "i3"}`. It requires the app to define an additional property called `xClass` [1]. For Spotify this is simply `Spotify`. The open function should return a shell command which can be executed to open the application, this is specific to i3. `workflow-wm-i3` will generate a `layout tree` based on the `xClass` which will match the various applications when opened [2]. 

```js
const Spotify = {
  xClass: 'Spotify',
  open: ({ minimized, play }, context, children) => {
    if (children.length !== 1) {
      throw new Error('Spotify does not support more or less than one child node');
    }

    const [child] = children;
    const uri = child.open(child, context, child.children);

    return `spotify --uri='${uri}' &`;
  }
};
```

We do also need to define the `Platform` child node [3]. Inside the platform node we build up the uri which the `spotify` node will return to `workflow-wm-i3`. This design lets us easily add new types of child nodes, which will be called by the spotify node. 


```js
const Platform = {
  type: "app",
  name: "Platform",
  params: ["user", "id"],
  open: ({user, id}, context, children) => {
    return `spotify:user:${user}:playlist:${id}`;
  }
};
```


That is all it takes to add support for spotify running under `workflow-wm-i3`.

**Note** The example above does not actually trigger autoplay on linux. If you figure out how to activate it, please have a look at [this issue](https://github.com/havardh/workflow/issues/155).

### Supporting `workflow-wm-osx`

The OSX integration follows a more standardized method of writing apps for `workflow`. `workflow-wm-osx` will call each apps open function with the arguments passed to the app and the absolute position on the screen. The app is responsible for opening the application and positioning it on the given position. This is usually done with JXA [4]. For convenience, `workflow-wm-osx` will pass a function called `run` through the `context` parameter which can be used to execute `JXA` code. The basic scaffold for psudo implementation is given below.

```js
const Spotify = {
  open: async (app, context, children) => {
    const uri = getUri(children, context);

    await context.run(({ minimized, play, position }, uri) => {
      const spotify = Application("Spotify");
    
      spotify.activate();

      const window = spotify.windows[0];
      window.bounds = app.position;

      spotify.playTrack(uri);
    }, app, uri);
  }
};

```

Now the most notable thing about the code above is invocation of the `run` function. This will call into [`@jxa/run`](https://github.com/JXA-userland/JXA/tree/master/packages/%40jxa/run) which executes the function parameter with `osascript` and returns a promise. This means that the function passed cannot be a closure and must only reference its parameters and the context provided by the `osascript` environment. The code opens `spotify` and sets the position of the `window` to the `position` in from the `app`. The Spotify specific function `playTrack` is used to start the playlist. 

The api available on the Spotify application can be found in the `Script Editor` application on OSX. It is possible to generate TypeScript definitions for the api, check out [this](https://github.com/JXA-userland/JXA/pull/8#issuecomment-419169843) for getting started. 

Now, the actual code to make this work properly on OSX is a bit more complex. Check out the [source code](https://github.com/havardh/workflow/blob/master/packages/workflow-app-spotify/src/index.js#L47) for the working version. 

## Footnotes

[1]: This is the X11 `WM_CLASS` as found by the `xprop` utility.

[2]: This simplistic implementation causes [this](https://github.com/havardh/workflow/issues/156) bug. 

[3]: For now these nodes will either use the type `layout` or `app`. Where a `layout` node is used as a support node for positioning other nodes, and the `app` node denotes something that will be visible on the screen.

[4]: JXA, or [Javascript for Automation](https://developer.apple.com/library/archive/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/OSX10-10.html#//apple_ref/doc/uid/TP40014508-CH109-SW1), the OSX way of writing automation scripts using Javascript