The (not so) daily

Blockstack Electron apps

TLDR: sign-in via blockstack to an electron app by serving manifest.json on a separate child-process and make sure to package & run your app to register the protocol client. Sample app here.

I’ve always been interested in the concept of identity, both from a philosophical and a practical standpoint. Blockstack’s promise of a self managing identity is one of the best implementations I’ve come across and in the beginning of this year I set out to explore how to build apps that take advantage of this self-sovereign identity system.

For a better user experience I wanted to build a desktop application and since most of Blockstack’s framework is built using Javascript I’ve decided to build an electron app. But some people were already having some issues (https://github.com/blockstack/blockstack.js/issues/270) so I went on to build a simple demo app illustrating the sign in process that you can find here.

Electron Blockstack demo app

If you’re looking to build your own app, these are the steps that you need to take:

1 - Install and run electron-forge to create your electron app

npm install -g electron-forge
electron-forge init electron-blockstack-demo
cd electron-blockstack-demo

2 - Add blockstack.js to your app.

yarn add blockstack

3 - Create a manifest.json file inside your src folder

touch src/manifest.json

4 - Add the following content to the file replacing ‘electronblockstackdemo’ by your app name. This is the structured required by Blockstack.

{
  "name": "Electron Blockstack Demo",
  "domain_name": "electronblockstackdemo://auth",
  "start_url": "electronblockstackdemo://auth",
  "description": "Electron Blockstack demo app",
  "icons": [{
    "src": "https://helloblockstack.com/icon-192x192.png",
    "sizes": "192x192",
    "type": "image/png"
  }]
}

5 - Now create a server.js file inside the src folder that will serve the manifest.json file:

touch src/server.js

6 - Add the following content to the server.js file:

var express = require("express");
var app = express();
var cors = require("cors");

var server = app.listen(9876);
app.use(cors());

app.get("/manifest.json", function (req, res) {
  res.sendFile(__dirname + "/manifest.json");
});

// Process to handle quit app
process.on("message", message => {
  server.close();
});

7 - To start the server when booting the app we need to create a child process and quit it once the app is also going to quit. Add to your src/index.js file:

import cp from 'child_process';

// Start process to serve manifest file
const server = cp.fork(__dirname + '/server.js');

// Quit server process if main app will quit
app.on('will-quit', () => {
  server.send('quit');
});

8 - We also need to register the default protocol client for the redirect so after the last content, add to the same file this line replacing electronblockstackdemo with your own app name:

// Set default protocol client for redirect
app.setAsDefaultProtocolClient('electronblockstackdemo');

9 - The registration is only complete for MacOS if we add that same information to the configuration file of the packaged project. So to do that, we need to open package.json and after the make_targets directive add electronPackagerConfig once again replacing the app name:

"make_targets": {
  "win32": [
    "squirrel"
  ],
  "darwin": [
    "zip"
  ],
  "linux": [
    "deb",
    "rpm"
  ]
},
"electronPackagerConfig": {
  "packageManager": "yarn",
  "protocol": ["electronblockstackdemo"],
  "protocolName": ["electronblockstackdemo"]
},
"electronWinstallerConfig": {
  "name": "electron_blockstack_demo"
}

10 - Now that we’re done with configurations, it’s just a matter of building our front-end and elements connections. Add to the body of your index.html:

<h1 id="welcome">Electron Blockstack Demo</h1>
<button id="signin-button">Login</button>
<script src="./js/blockstack-service.js"></script>

11 - Create a src/js/blockstack-service.js file and add:

import * as blockstack from 'blockstack';
import { ipcRenderer } from 'electron';

document.getElementById('signin-button').addEventListener('click', function () {
  var authRequest = blockstack.makeAuthRequest(blockstack.generateAndStoreTransitKey(), 'electronblockstackdemo://auth', 'http://localhost:9876/manifest.json', blockstack.DEFAULT_SCOPE, 'electronblockstackdemo://auth')
  blockstack.redirectToSignInWithAuthRequest(authRequest)
})

ipcRenderer.on('displayUsername', function(event, profile) {
  document.getElementById('welcome').innerHTML = "Welcome " + profile.name
  document.getElementById('signin-button').style.display = "none"
});

12 - Finally, handle the ipc communication from the front-end to get the identity of the user. On the main index.js file add:

import queryString from 'query-string';

app.on('open-url', function (ev, url) {
  ev.preventDefault();

  // Bring app window to front
  mainWindow.focus();

  const queryDict = queryString.parse(url);
  var token = queryDict["electronblockstackdemo://auth?authResponse"] ? queryDict["electronblockstackdemo://auth?authResponse"] : null;

  const tokenPayload = blockstack.decodeToken(token).payload

  const profileURL = tokenPayload.profile_url
  fetch(profileURL)
    .then(response => {
      if (!response.ok) {
        console.log("Error fetching user profile")
      } else {
        response.text()
        .then(responseText => JSON.parse(responseText))
        .then(wrappedProfile => blockstack.extractProfile(wrappedProfile[0].token))
        .then(profile => {
          mainWindow.webContents.send('displayUsername', profile);
        })
      }
    })
});

Blockstack is under very active development so the implementation described here will probably be deprecated a few months from now but I’ll make sure to update it once things move forward.

I’m really excited about the possibilities that this new era of decentralization, self-sovereign authority and trustless interactions will bring. A lot still needs to be done but practical implementations and its ease of use will dictate the speed of adoption.

Tiago Alves