NAV Navbar
Logo

Freewrite SDK

As of May 2017 and firmware version v1.4, the Freewrite SDK is in limited beta. Developers seeking an invitation to use the SDK should email mailto:developer@astrohaus.com.

The full SDK will be launched publicly with firmware version v1.5.

Features expected to land in the next firmware version v1.5:

Getting Started

Adding a Freewrite USB ethernet device on Linux:

# /etc/network/interfaces
allow-hotplug usb0
auto usb0
iface usb0 inet static
address 192.168.99.44
netmask 255.255.255.0

To develop on your Freewrite, you will need:

The following credentials allow sFTP access to the extensions/ folder on your Freewrite for local extension development:

sdk:sdk@192.168.99.190

Creating an Extension package

Here is an example package.json:

{
  "name": "offline-folder",
  "description": "This extension disables uploading documents for a single folder."
  "sdk_versions": [
    "1.0"
  ]
}

To register a file path as an extension:

// index.js
function(sdk_version, register){ 
  register('preflights/openpgp.js', {
    type: 'preflight',
    folder: null, // optional
  });

  register('components/hint-modal.js', {
    type: 'component',
    triggers: {
      hotkey: 'z'
    },
    assets: [
      'stylesheets/style.css' // not implemented yet. use fetch API.
    ]
  });
}

An extension comprises at least two files:

package.json

To create a package.json, install npm package manager and perform this command in your terminal: npm init

The following fields are mandatory:

index.js

The index.js file is used to register files as extensions. You can register more than one extension in a package (e.g. both a Preflight extension and a Console extension).

The index.js file is invoked as an anonymous function and receives two arguments:

Parameter Type Description
sdk_version String '1.0'
register Function This is a callable function used to register file paths as extensions.

Developers can register files within their package as extensions by invoking the register() callable function in their index.js.

The first argument is a relative path to the extension’s source code file. Different extension types require different values included in the second options argument.

Option Required Description
type Yes String. Must be one of preflight, component, console, or canvas
folder No String. Limits the extension to being active in the given folder a, b, or c.
triggers No Dictionary. Register hotkeys for applicable extensions using a dictionary-like data structure.

There are four extension types available:

A Preflight extension modifies the document before uploading it to Postbox. This can be useful for operations like encryption.

A Console extension adds a new status bar component to the console at the bottom of your Freewrite display.

A Canvas extension replaces the editor at the top of your Freewrite display.

Finally, a Component extension is a custom UI element that is displayed when triggered by the user. Triggers often include custom hotkeys like NEW + [Hotkey].

Preflight Extensions

Register a Preflight in index.js:

  register('preflights/openpgp.js', {
    type: 'preflight',
    folder: 'a'  // only use this Preflight for folder A
  });

Return the new Document object, a Promise, or null:

function(document, user, modules){
  let openpgp = modules.opengpg;
  openpgp.initWorker();

  return 
    fetch('extension://my-extension-name/public_key.txt')
    .then((response) => {
      return response.text();
    })
    .then((public_key) => {
      let options = {
        data: document.body,
        publicKeys: openpgp.key.readArmored(pubkey).keys
      }
      return openpgp.encrypt(options)
    })
    .then(function(ciphertext) {
      return ciphertext.data;
    });
}

A Preflight extension performs an operation on a document just before uploading the document to Postbox.

The Preflight extension receives these arguments:

Position Argument Type Description
1 document object A document object
2 user object A user object
3 modules object An object exporting several third-party libraries for use in extensions

A Preflight extension must return one of the following values:

Type Example Description
String ‘new text’ A complete text buffer of the document body
Object {body: 'new text', data: {}} A Document object
Promise fetch().then((obj) => {return obj}) A Promise object
Null null A null return value will be ignored; the document will not be uploaded.

Components

Register a component in index.js:

  register('components/character-generator.js', {
    type: 'console'
  });
  register('components/canvas.js', {
    type: 'canvas',
    folder: 'a'
  });
  register('components/file-manager.js', {
    type: 'component'
  });

Example:

function(modules) {
    return class MyComponent extends modules.React.Component {
        constructor() {
            super()

            this.state = {
                visible: false
            }

            this.onHotkey_bound = this.onHotkey.bind(this);
        }

        onHotkey(event){
            if(event.key === 'm'){
                this.setState({
                    visible: !this.state.visible
                });
            }
        }

        componentWillUnmount() {
            this.props.emitter.off('hotkey', this.onHotkey_bound);
        }

        componentDidMount() {
            this.props.emitter.on('hotkey', this.onHotkey_bound);
        }

        render() {
            if(!this.state.visible){
                return modules.React.createElement('span');
            }
            return modules.React.createElement('div', { 
                style: {
                    position: 'fixed',
                    width: '25%',
                    right: '0px',
                    top: '0px',
                    height: '100%',
                    border: '1px solid black',
                    backgroundColor: 'white'
                }
            }, 'My Text');
        }
    };
}

The Console, Canvas, and Component extensions are user interface elements that are added to the Freewrite application.

A Console extension adds a new status bar to the lower console on a Freewrite.

A Canvas extension replaces the text editor for a provided folder.

A Component extension is injected to application and can respond to certain events to make itself visible or hidden.

Properties

Components are written using React. (JSX support may be available in the future.)

Certain properties are passed to the component and can be accessed through this.props:

Property Description
emitter An event emitter used to bind to Freewrite events like hotkey
data An object containing extension data

Actions

Actions are also passed to components and can be accessed through this.props.actions:

Action Description
patchExtensionData({...}) Updates this.props.data available to all extension components
putExtensionData({...}) Overwrites this.props.data available to all extension components

These two patch/put actions can update data across multiple components registered in the same extension.

Event Emitter

An event emitter is provided for listening to and emitting events.

Subscriber Events Arguments Description
hotkey event Emits an event object containing event.key which represents a NEW+{key} hotkey press.
document.load document
Publisher Events Arguments Description
document.select
document.new
document.prev
document.next
block.create
block.update
block.delete
language.cycle
toggle-frontlight

Upcoming Events (not implemented yet)

Assets & Data

Register a JSON data fixture in index.js:

  register('component.json', {
    type: 'component'
  });
  register('names.json', {
    type: 'json'
  });

The data fixture names.json:

{
    "names": [
        "chrono",
        "lucca",
        "marle",
        "frog",
        "magus",
        "ayla",
        "robo"
    ]
}

Accessing the data in a component:

function(modules) {
    class GeneratorComponent extends modules.React.Component {
        getRandomName(){
            // The data fixture is keyed by its filename
            let names = this.props.data['names.json']['names'];

            let index = Math.floor(Math.random() * names.length);
            return names[index];
        }
    }
}

Accessing any Extension asset using fetch:

fetch_secret = function(){
  fetch('extension://secretSauce/secret.txt')
  .then((response) => {
    console.log(response.text());
  });
}

Data fixtures can be loaded by registering a file path as a json extension type. JSON data loaded this way will be available to all components included in the extension and accessible through this.props.data.

Alternatively, any asset can be loaded asynchronously by using the fetch API (example at right).