Introducing Embeddable Textile

Its easier than ever to deploy desktop apps with Textile

When we started to explore ways to facilitate building Textile-based apps on desktop, we didn't anticipate the uptake in the community that we've experienced. With the release of our js-http-client library, suddenly folks like Permaweb were building new apps and concepts on top of Textile and its underlying Go libraries. So we moved to support these efforts by making it easier to install and run go-textile on the desktop– with the Textile Tray app. This has been super useful for us to explore how Textile can be used on desktop, and allows developers to build their proof-of-concept apps quickly and easily. But now its time to kick things up a notch, so that developers don't have to depend on their users' installing another app just to get going. That's why we're excited to introduce Embeddable Textile.

Speaking of Community

One of the big motivators for developing an embeddable Textile framework was to make it easier for developers to build engaging desktop applications on Textile. Case in point: the Permaweb team is working on a Textile-based Groups app for Windows, Linux and Mac. It was important for their team to know that Permaweb users wouldn’t have to run a separate tray application to get started. Instead, their users could just run one executable, Permaweb. To make sure we got that experience right, our two teams worked together pretty early on to develop a framework that made it easier for them to on-board new users. Their desktop app now directly embeds a Textile daemon, meaning they can have users creating Textile-based data from the first click.

We want Permaweb to get the best of what the Dweb brings, but still feel as easy to use as a regular app. Embedded nodes help us do exactly that: one click installers, with the added benefits of running a fully P2P, decentralized and encrypted backend. - Shokunin, CEO of Permaweb.

Embeddable Textile

We're actually talking about a series of new Typescript/Javascript modules released by the Textile team to make it super easy to embed and run a go-textile daemon on desktop. The 'target' usage is embedding in Electron (or similar) apps, but the potential applications don't stop there. In addition to our existing @textile/js-http-client and @textile/wallet modules, we've released three new npm packages to support these efforts:

@textile/go-textile-dep
Automatically download and install go-textile releases from our official releases repo

@textile/go-daemon
Spawn and control a textile deamon from Typescript/Javascript!

@textile/go-textile
Easily install go-textile via npm or yarn, both locally and/or globally

We also added a few new features to some of our existing libraries, and just generally made the experience for developers on desktop a little cleaner. As always, all our modules are developed in Typescript, so you get type safety, hints, and all those goodies when developing with Textile tools. But of course, none of this would have been possible without the awesome corresponding libraries developed by the IPFS team: we based (the above packages originally started as forks) our libraries on the npm-go-ipfs-dep, npm-go-ipfs, and js-ipfsd-ctl libraries from IPFS, so make sure you check them out and/or ⭐️ those repos!

Building with Textile

To make it as easy as possible for other teams to get started with Textile on desktop, we've included some examples within the @textile/go-daemon project, which you can check out and test with just a few commands. One example is written in Typescript and runs headless in Node, and another is written in Javascript, and embeds the Textile daemon in a super simple Electron app. The app itself simply spawns and starts ephemeral Textile peers on command, showcasing the ease with which you can create and use Textile accounts on desktop. But to save you some trouble, let's go through the second example here together:

Start by cloning the repo from GitHub:

git clone [email protected]:textileio/js-textile-go-daemon.git

Now, go ahead and change into the js-textile-go-daemo/examples/electron-embed directory, and run yarn install or npm install:

cd js-textile-go-daemon/examples/electron-embed
yarn install

You'll probably notice while that is running that it's automatically downloading the correct go-textile release for your system and configuration.

With that process done (it'll be a bit slow the first time around because it needs to download Electron), you should be able to run the example in dev mode (yarn dev), make changes to the src/main and src/renderer directories, break things, etc. The example uses the amazing electron-builder and electron-webpack packages to make building and deploying Electron apps suspiciously easy. That's pretty much it, but read on if you want to know a bit more about how the example app is structured, and how to actually 'deploy' an installable desktop app.

Code

Right now, the code in src/main and src/renderer is pretty basic. But let's break it down a little to give you an idea of what you'd need to include in your own app if you wanted to embed Textile like this. It helps if you are somewhat familiar with Electron app architectures, but not required. Essentially (according to the official Electron docs), our main process creates pages, and each instance of a page runs in its own renderer process. When a page instance is destroyed, the corresponding renderer process is also terminated. So the main process manages all pages and their corresponding renderer processes, and each renderer process is isolated and only cares about the page running in it.

So in our app, we separate concerns by having two different scripts, one for our renderer process (we only have a single page app), and one (as always) for our main process. Let's start with the src/main/index.js file. This is pretty much exactly line for line what comes with the electron-webpack-quick-start boilerplate electron project, plus one key difference – we need to create and use our DaemonFactory to actually spawn a new Textile daemon at the right time.

So first things first, we import { DaemonFactory } from'@textile/go-daemon' at the top of our file. Then, we add a bit of logic for when we receive an inter-process communication (ipcMain) from our renderer process:

ipcMain.on('start', ({ sender }) => {
  console.log('starting disposable Textile')
  sender.send('message', 'starting disposable Textile')

  const df = new DaemonFactory()
  df.spawn({ disposable: true })
    .then(daemon => {
      console.log('get profile')
      sender.send('message', 'get profile')
      daemon.api.profile.get().then(profile => {
        console.log('got profile address', profile.address)
        sender.send('message', JSON.stringify(profile, undefined, 2))
        daemon.stop()
      })
        .catch(err => {
          sender.send('error', err.toString())
        })
    })
    .catch(err => {
      sender.send('error', err.toString())
    })
})

In the above example, each time our renderer process sends us a 'start' event, we use our DaemonFactory to spawn a new (disposable) Textile daemon, and then query its api client for the current peer profile information. That's right, each Daemon object comes bundled with its own Textile js-http-client API instance, so handy! In this case, it then sends the profile information back to the renderer process for display on the page, and kills the new daemon while it's at it.

The corresponding code in the renderer process is pretty basic. All we have here is some code to handle messages from the main process, and some vanilla Javascript to create the very simple UI:

const { ipcRenderer } = require('electron')

function appendPre (data) {
  // Simple function to append pre tags to body
}

// Prepare handle messages
ipcRenderer.on('message', (event, msg) => appendPre(msg))
ipcRenderer.on('error', (event, err) => appendPre(err))

// Create and hook up button
const button = document.createElement('button')
button.innerHTML = 'test textile app'
document.body.appendChild(button)
document.querySelector('button')
  .addEventListener('click', () => {
    ipcRenderer.once('profile', (event, id) => appendPre(id))
    ipcRenderer.send('start')
  })

Each time we receive a message (or error) from the main process, we append it to the page, and each time the user clicks the 'test textile app' button, it tells the main process to spin up another temporary Textile peer. That's about it!

Deploying

I mentioned deploying earlier... so now that we have some code that works in dev mode, let's build our app installer for 'release'. It's as easy as:

yarn dist

Or, if you just want to build the local app/executable (much faster and quite useful for testing), you can do that with:

yarn dist:dir

Some things to note here. 1) This just builds for your current machine's OS and architecture, and 2) we have to remember to set the electron-builder asarUnpack configuration option to include @textile/go-textile-dep in our output binary package:

electron-builder -c.asarUnpack=**/node_modules/@textile/go-textile-dep/**/*

This specifies which files/folders to unpack when creating the asar archive. What does that mean? Well, here's what the Electron docs have to say about this (emphasis added):

An asar archive is a simple tar-like format that concatenates files into a single file. Electron can read arbitrary files from it without unpacking the whole file.

But if you need to (like we do here), you can leave various files unpacked using the above configuration option. What this does is creates a new folder named app.asar.unpacked along with the default app.asar file in the compiled output. It contains the unpacked files and gets shipped together with the app.asar archive. Of course, you can just copy-pasta the above code to your own package, and this will all be taken care of for you! Bonus: @textile/go-daemon is actually designed to look for these files paths automatically, so you don't even have to specify this path in your app!

You can also customize which version and release gets downloaded for things like building packages via CI or specific platforms. For example, to build a Windows release on Linux or MacOS, first, install for Windows:

TARGET_OS=windows yarn install

and then build for Windows:

TARGET_OS=windows yarn dist:dir --windows

So that's just about it. If you have the app running, you'll probably see something a bit like the gif at the top of this post. If you didn't get it running, let us know, its early days so there are bound to be issues. Also, if you're interested in testing out this new functionality on your own app or idea, get in touch, and let's experiment together! Additionally, if you like this type of content, let us know, or make requests for additional examples... in the mean time, happy coding 👋👨‍💻👩‍💻.

Carson Farmer

Carson Farmer