Skip to content
twiss edited this page Oct 22, 2014 · 3 revisions

Intro

The APIs meant for apps, notably Device Storage, are not implemented directly on top of S3 (which is where we store all files). Instead, they are built on top of an internal API.

Usage

Let's say you want to use fs.putFile(). All fs.* functions are implemented in core.js. That means that if you want to use it inside core.js, you can just call putFile() (without fs.).

If you want to use it in laskyawm.js, though, you might have to define it first (if it isn't already). Look at getFile for inspiration.

If you want to use it in compat.js, you have to say airborn.fs.putFile().

Similarly for core.* functions, except those aren't accessible inside compat.js.

Similarly for wm.* functions, except those are defined in laskyawm.js.

Adding a new API function

To add a fs.* function, define a global function in core.js, add its name to the whitelist in core.js, and add a call to addAction() in compat.js.

To add a wm.* function, define a global function in laskyawm.js, add a case in the 'message' event handler, and add a call to addAction() in compat.js.

Functions

fs.*

fs.getFile

fs.getFile(file, [options], [callback(contents, err)])

Read a file.

file is an absolute path to the file.

options is an optional object.
options.codec is the encoding you want to get the file decoded from. The default is 'utf8String'. Supported codecs:

  • arrayBuffer
  • base64
  • base64url: Same as base64, but with a slightly different alphabet.
  • blob: Currently only supported in putFile() (and then only in compat.js), not in getFile(). This codec automatically sets attributes.type to blob.type.
  • dir: The internal encoding of directories. Decoded, this is an object with filenames as keys and an object with attributes as values. Encoded, this is currently yaml, but don't rely on it. You can rely on support for Date objects, though.
  • hex
  • json and prettyjson
  • raw: Use this if you don't care what you get, as long as you can pass it to putFile. Currently the same as sjcl.
  • sjcl: The internal encoding used by sjcl, the encryption library we use.
  • utf8String: An UTF8 string.
  • yaml: Encodes an object to yaml and decodes vice versa.

If there was a network error, or the file could not be decrypted, or the file could not be decoded as valid utf8/json/etc., contents is null and err is an object with 'status' and 'statusText' properties.

fs.putFile

fs.putFile(file, [options], contents, [attributes], [callback(err)])

file is an absolute path to the file.

options is an optional object. If you don't pass options, though, contents has to be a string.
options.codec is the codec you want to encode contents with. The default is 'utf8String'.

attributes is an object with attributes to be associated with the file.

If you want to store non-standard attributes, please store them in an object called attributes.user (for file attributes settable by a user, such as mp3 ratings) or attributes.x (for anything else). If the attributes are very specific to your app, you can also put them in attributes.x.nameOfYourApp. Keep in mind that other apps can still access and modify those attributes, though, so don't trust their contents.

If you overwrite an existing file, the attributes are added to the ones the file already has. This also works for nested objects (e.g. attributes.user). If you want to delete an attribute, add it in the object with a value of undefined.

If there was a network error, err is an object with 'status' and 'statusText' properties.

Note: when calling putFile() multiple times, uploading files happens in stages. All calls to callbacks will probably be clustered at the end of the upload process, making them useless for a progress indication.

Warning: getFile() and putFile() are prone to race conditions. For example, in compat.js, don't do something like this:

// This function by itself is okay:
function updateSettings(key, value, callback) {
	airborn.fs.getFile('/tmp/settings', {codec: 'json'}, function(settings) {
		settings[key] = value;
		airborn.fs.putFile('/tmp/settings', {codec: 'json'}, settings, callback);
	});
}

// But if you call it twice...
                        // settings = {a: 1}
updateSettings('b', 2); // settings = {a: 1, b: 2}
updateSettings('c', 3); // settings = {a: 1, c: 3} (Oops!)

// Instead, put the calls in a queue and run them sequentially.
// In core.js, the above code is safe though.

fs.prepareFile

fs.prepareFile(file, callback(contents), progress(filesDownloaded, totalFiles))

Because all html, js and css files of an app are stored in the user's Airborn itself, we can't slap an html file in an iframe and be done with it. All references to js and css files have to be followed and replaced with an inline url. Currently, a combination of data: URLs and Object URLs is used, although we hope to switch to Service Workers at some point.

fs.prepareFile downloads a file and passes it to fs.prepareString.

Warning: see warning for fs.prepareString.

fs.prepareString

fs.prepareString(contents, options, callback(contents), progress(filesDownloaded, totalFiles))

Scans an html or css string (contents) for references to other app-local files, downloads them, and replaces them with a data: uri as returned by fs.prepareUrl.

Warning: see warning for fs.prepareUrl.

fs.prepareUrl

fs.prepareUrl(url, options, callback(url), progress(filesDownloaded, totalFiles))

Downloads the file at url.
If url is relative, it is resolved relative to options.relativeParent.
If url is absolute, it is resolved relative to options.rootParent.

If url is a html file, and you pass options.csp, a meta tag with that CSP is added.

Note: prepareUrl will, when url is a .html file, use a two-step bootstrap process. It will not call progress(), and instead call callback() with a simple html file which will call fs.prepareFile with options.bootstrap set to false, and will also call wm.showProgress/wm.setProgress/wm.hideProgress.

Warning: totalFiles is not stable across calls to progress. As more files are discovered that have to be prepared, totalFiles might increase. Thus, filesDownloaded / totalFiles might decrease. However, if you're going to pass that number to wm.setProgress, that function already takes care of this situation, by ignoring decreased values of its first argument.

fs.startTransaction

fs.startTransaction()

If you're going to do multiple file operations, you can wrap them in fs.startTransaction() and fs.endTransaction(). Currently, this doesn't have much effect: S3 doesn't support transactions, and neither do we (at the moment).

It does have a (small) performance benefit: normally, after a call to putFile(), Airborn will wait (100 ms, currently) for more calls to putFile() and automatically wraps them together in what it calls a transaction (allowing it to update the folder hierarchy only once). If you call these functions manually, you don't pay these 100 ms.

Note: as long as you haven't called fs.endTransaction (or it is called automatically, after 100 ms, but don't depend on this happening at all) no callbacks for putFile() will be called. So don't, for example, wait until all callbacks for putFile() have fired before calling endTransaction.

Warning: calls to startTransaction and endTransaction cannot currently be nested. For example, if you call startTransaction twice and endTransaction once, all files written up to that point will be uploaded and a second call to endTransaction will have no effect.

fs.endTransaction

End a transaction as started by fs.startTransaction.

fs.listenForFileChanges

fs.listenForFileChanges(listener(path, reason))

path is the absolute path that has changed. This may be either a file or a directory, in which case it means any of its descendants have been added, changed, or removed.

reason is one of:

  • 'added'
  • 'modified'
  • 'deleted' (Not currently implemented.)

wm.*

wm.focus

wm.focus()

Brings the window to the foreground.

wm.reportClicked

wm.reportClicked()

Report that the user has clicked inside the window. Please do not use this unless that actually happened. If wm.focus does not do what you want, propose to add another API function.

wm.setTitle

wm.setTitle(title)

Set the title of the tab. Currently this also changes the browser window's title with core.setTitle.

wm.setIcon

wm.setIcon(url)

Set the icon of the window. Currently this does not change the browser window's icon.

wm.openFile

wm.openFile(path, [target], [options])

Will probably be deprecated by Web Activities.

Open the file at path in an appropriate viewer or editor.

target can be:

  • An action, as defined in filetypes, such as 'view', 'edit' or 'install'.
  • The string 'replace' (if called from compat.js), in which case the file is opened in the calling tab.
  • A path to an app, such as '/Apps/fancyedit/'.

options can only be used from laskyawm.js.

If options.callback is set, it is called with (iframeWin, tab, div).

The options of wm.openWindow also work here.

wm.openWindow

wm.openWindow(appPath, options, callback(iframeWin, tab, div))

appPath is a path to an app, such as '/Apps/myfancyapp/'.

If options.targetDiv is not set, a new window is opened. If options.targetDiv is set and options.innewtab is set, a new tab in the existing window is opened. If options.targetDiv is set and options.targetTab is set, the app is loaded in that tab.

If options.originDiv is set, the new window is positioned near that window.

The options from wm.showProgress/wm.setProgress/wm.hideProgress also work here.

Note: there is also an openWindow() in core.js which does something similar but has a different signature, isn't part of this API and is only used to open laskyawm.html.

wm.showProgress

wm.showProgress(options)

Start showing a progress bar.

options.loaderElm is the loader element to use. For example, around the location bar is a loaderElm with the class 'loader'.

wm.setProgress

wm.setProgress(fraction, options)

Set the location bar progress to fraction (from 0 to 1).

The options from wm.showProgress also work here.

wm.hideProgress

wm.hideProgress(options)

Stop showing a progress bar.

If options.loaderHighlight is not false, it also shows a brief fadeout animation indicating that an operation has finished.

The options from wm.showProgress also work here.

core.*

core.setTitle

core.setTitle([title])

Set the browser window's title to title + ' - Airborn', or simply 'Airborn' if you pass a falsy (empty) title.

core.setIcon

core.setIcon(url)

Set the browser window's favicon to url.

core.logout

core.logout()

Logout from Airborn and refresh the page.

Note: the window manager should probably individually close all windows first, to give windows a chance to alert about non-saved changes. laskyawm doesn't do this yet, though.