Storage Reference

Table of contents


Introduction

The storage object provides an interface to device and cloud-based storage and backup for user input. Device storage allows the application to restart with the same settings when it was last active. Device storage is implemented using the browser's localStorage. The user's saved input is stored in localStorage under the key ${ctl.appId}_input. A new set of user input is saved after an input state variable change causes the Controller to synchronize the Model and View.

Detecting localStorage changes made by another tab of the same app is not implemented. The code to do so is commented out in storage.init(), so each tab works from its own state and the last one to save wins.

Importing

storage is not exported by mvcs/export.js. The Controller imports it internally, and other modules that need it import it directly:

import {storage} from '../mvcs/storage.js';

(adjust the relative path to the module's location).

Initialization

storage.init() is called by ctl._init() and does the following:

On an installed iOS app, storage.migrateLegacyLocalStorage() runs before ctl._init().

Cloud state

The storage_cloudState state variable records the state of cloud backup and Cloud Sync:

Cloud saving is only attempted when storage is enabled, the device is online, server.isEnabled is true, and the cloud state is none of 'inactive', 'incompatible', 'user', or 'checking'.

GUID

When an application starts up on a device for the first time, it creates a globally unique ID (GUID) by storing the string GUID-${Date.now()} in localStorage under the key ${ctl.appId}_guid.

Cloud storage and Cloud Sync

Cloud storage saves the current data and takes backup snapshots of the data that the user can request in case local storage becomes corrupt or they make an unrecoverable mistake when entering data. When the server detects it is storing new data that is more than a day newer than the previous data, it saves the previous data as a backup snapshot. Up to four backup snapshots will be preserved. When a new snapshot is created, the oldest snapshot is discarded after four snapshots are present. If the user has not entered an email address and password, all cloud storage is associated with the GUID.

The storage.setState() method saves user input data locally, and if online, a copy is sent to the cloud. The storage.getState() method only gets the local state.

Enable and disable

Saving data can be disabled and re-enabled using the storage.disable() and storage.enable() methods, respectively. Storage can be disabled for many reasons, such as running a self-test or loading a trouble report. A subsystem that disables and enables saving can use a unique id string so that competing requests can be matched. Storage is only re-enabled when all the disabled IDs have been re-enabled. When storage becomes enabled, the current app state is saved using ctl.save().

Cloud Sync

Cloud Sync allows user input data to be transferred between devices and saved input to be recovered when a device is lost. It is enabled when the user enters an email address and a password. Once Cloud Sync is enabled, data stored on the server is associated with the email address and password. Any data previously associated with the GUID (including backup snapshots) is also associated with the email address and password.

By design, this procedure does not require any user registration process. The user may choose any combination of email address and password. Setting a new password will create an independent set of data from the old password. The user can deliberately use this to keep, for example, a different set of aircraft data for testing or to keep aircraft associated with different companies separated.

The server will accept email addresses and passwords with the following characters:

/[\w\-+!#$%&'()*+,.;=@\[\]\^_`{}~]/

The email must have the form of an email address with any illegal characters stripped out but is otherwise unverified. The password must have at least two characters after any illegal character are stripped out. The character restrictions allow the server to use raw files to store user data.

When new user input is storage to device storage, a copy is also sent to the cloud using the server.setSavedInput() method. The server will return an error if the GUID in the passed id argument does not match the GUID in the previously saved input data. This tells the app that another device with a different GUID but the same email and password has written new data since the last time is wrote. The app then asks the user whether they want to use this device's current data or transfer the other device's data from the cloud.

If the user wants to use the cloud data, the data is retrieved using the server.getSavedInput() method, and the data is used to set the app's inputs. In either case, the app's state is then saved to the cloud using storage.setCloudData() method with the takeover argument set to true. This forces the server to overwrite the other device's data with this device's data, which includes this device's GUID.

Object storage

The storage object also contain interfaces to the indexedDB browser service to store larger objects using a key. In the POH Performance apps, this is primarily used to store airport database updates. The object storage interfaces are:


storage methods

deleteObjStoreVal()

Syntax

storage.deleteObjStoreVal(key)

Deletes the value associated with key.

Parameters

Return value

Returns a Promise that resolves if the operation is successful and rejects with the event errorCode when it is unsuccessful.


disable()

Syntax

storage.disable()
storage.disable(id)

Disables storage saving. An id string that identifies the subsystem that will re-enable storage should be provided.

Parameters


enable()

Syntax

storage.enable()
storage.enable(id)

Enables storage saving if no other subsystems have disabled storage. An id string that identifies the subsystem that disabled storage should be provided. If id is provided but did not previously disable storage, the call is ignored. When storage becomes enabled, enable() calls ctl.save() to save the current app state.

Parameters


getCloudData()

Syntax

storage.getCloudData()

Gets the cloud data associated with server.id. Returns a Promise that resolves with a input state object, if the operation is successful. The method calls the server.getSavedInput() to make the request. The result is decoded from JSON and upgraded to the current app version using ctl.upgradeSavedState()) before being returned. Any state variable values whose save property is 'local' are then replaced with this device's current values, so they are preserved rather than overwritten by the cloud data.

If ctl.upgradeSavedState() returns an error string, then the Promise is rejected with the error string.

Return value

Returns a Promise that resolves if the operation is successful and rejects with one of the following error strings:


getObjStoreVal()

Syntax

storage.getObjStoreVal(key)

Returns a Promise that resolves with the value associated with key.

Parameters

Return value

Returns a Promise that resolves with the value associated with key if the operation is successful. The Promise rejects with the event errorCode when it is unsuccessful.


getState()

Syntax

storage.getState()

Get and decode locally stored user input data. Returns the result of fetching and decoding the JSON string stored in localStorage under the ${ctl.appId}_input key. If successful, the decoded results are upgraded to the current app version (see ctl.upgradeSavedState()) before being returned.

Return value

One of the following:

ctl.restore() resets the app values if the result is not an object.


hasObjStoreVal()

Syntax

storage.hasObjStoreVal(key)

Returns a Promise that resolves with true if there is a value associated with key.

Parameters

Return value

Returns a Promise that resolves with true if there is a value associated with key, if the operation is successful. The Promise rejects with the event errorCode when it is unsuccessful.


isValidCloudId()

Syntax

storage.isValidCloudId(email, password)

Returns a true value if both email and password are valid. To be valid, the email string must match the form x@y.z (any characters, with @ and a following . separated by at least one character) when illegal characters are stripped. To be valid, the password string must be at least two characters in length when illegal characters are stripped. Empty strings are not valid.

Parameters

Return value

A true value if both parameters meet the validity requirements. Otherwise a false value, which can be null (the result of a failed email match) rather than false.


migrateLegacyLocalStorage()

Syntax

storage.migrateLegacyLocalStorage()

On an installed iOS Cordova app, migrates saved input (and the GUID) from the legacy file:// origin to the current app://localhost origin using the legacyLocalStorage Cordova plugin. It never overwrites data already present at the current origin, and all failures fail open so that normal startup continues. This method is called by the Controller before ctl._init().

Return value

Returns a Promise that always resolves with one of the following strings:


newCloudId()

Syntax

storage.newCloudId(email, password)

Sets a new cloud sync email and password. If the server has data associated with this email/password, then use it to set the app state. If not, then ask the user whether they want to create a new data store on the server. This prevents accidentally creating a new store for a mis-typed email or password.

If email or password is empty, the request is accepted as an anonymous backup: the cloud state is set to 'active:backup' and the Promise resolves with 'success' without contacting the server.

If both are non-empty and storage.isValidCloudId() is false, the Promise is rejected with 'badSaveId'. If the device is offline or server.isEnabled is false, the new ID is accepted without checking the server.

If email and password are valid (see storage.isValidCloudId()), then newCloudId() composes a new JSON-encoded ID object containing email, password, and guid elements and set server.id.

If the retrieved state is from a newer version of the app, then it's considered incompatible and Cloud Sync is disabled until the app is upgraded.

Parameters

Return value

Returns a Promise that resolves with 'success' if the new ID is accepted and is rejected if the user cancels email/password change or if it's invalid. Rejection returns one of the following strings:


setCloudData()

Syntax

storage.setCloudData()
storage.setCloudData(input)
storage.setCloudData(input, ask)

Sets the cloud data associated with server.id to input. Returns a Promise that resolves if the operation is successful. The method calls the server.setSavedInput() to make the request. If cloud backup is not ready (see Cloud state), the Promise resolves with 'success' immediately without contacting the server.

Writes are serialized: each write is queued behind the previous one and starts after it settles, whatever its result. The returned Promise is the end of this queue.

If the server responds with a status code of 409 (Conflict), it indicates that the GUID element in server.id did not match the GUID in the previously stored input data because another device last wrote it. In that case, if ask is omitted or falsey, then storage.setCloudData() will retrieve the cloud data using the server.getSavedInput() method and use it to set the app's input state. If ask is truthy, then storage.setCloudData() will issue a dialog.choose() that ask the user to choose between the current app state and the cloud data from the other device. If the cloud data is chosen then the storage.setCloudData() will retrieve the cloud data using server.getSavedInput() and use it to set the app's input state. Finally, once the previous steps are done, storage.setCloudData() calls the server.setSavedInput() method one more time with the latest app state and the takeover argument set to true to store take ownership of the cloud data.

Parameters

Return value

Returns a Promise that resolves with 'success' if the operation is successful. It rejects with the numeric server.setSavedInput() response status code, or with the string 'offline' or 'serverOffline' (for example when the cloud data must be retrieved after a 409 and that retrieval fails), when it is unsuccessful.


startCloudSync()

Syntax

storage.startCloudSync(email, password)

Starts Cloud Sync with an initial email and password. Sets storage.email to email. If both email and password are valid (see storage.isValidCloudId(), then it sets storage.password to password. Otherwise, it sets it to ''. It sets server.id to a JSON string containing email, password, and guid. If cloud backup is ready, it sets storage_cloudState to 'active' when storage.email and storage.password are both set, and to 'active:backup' otherwise.

Called from the initialization routing of the page that contains the email and password.

Parameters


setObjStoreVal()

Syntax

storage.setObjStoreVal(key, value)

Sets the value associated with key.

Parameters

Return value

Returns a Promise that resolves if the operation is successful and rejects with the event errorCode when it is unsuccessful.


setState()

Syntax

storage.setState(input)

Set the locally stored saved input to input. If storage is disabled (see Enable and disable), nothing is saved. Otherwise, input is JSON-encoded and saved to localStorage, and if cloud backup is ready (see Cloud state), a copy is saved to the cloud as well (see storage.setCloudData()). The first call does not ask the user about ownership conflicts; later calls do.

Parameters

Return value

Returns a Promise that resolves with 'noop' if storage is disabled, and with 'success' when the local and cloud operations are complete. If the cloud operation fails, the Promise rejects as described for storage.setCloudData().


storage properties

email

The current email address to use in server.id.

guid

The current GUID to use in server.id.

password

The current password to use in server.id.