State Management APIs

Registering State

A Message State must be registered with a Model for it to be used. This registry is done as part of the design of the Model.

One can register a state to be supported across all Message Types using:

Layer.Core.MessageTypeModel.customStates.is_liked = Layer.Constants.CRDT_TYPES.LAST_WRITER_WINS;

In creating a custom Message Type that supports state:

The registerAllStates method is a lifecycle method that gets called on every Message Type Model during initialiation, and is where any supported states should be registered.

registerAllStates() {
  this.responses.registerState('favorite_colors', Layer.Constants.CRDT_TYPES.Set);
  this.responses.registerState('latest_color', Layer.Constants.CRDT_TYPES.LAST_WRITER_WINS);
}

Types of State

The following types of state properties can be registered:

Type Description
Set Any value that is added via an add operation will be added to the Set of values if not already present; remove operations will similarly be allowed from the Set
FIRST_WRITER_WINS Whatever Response Message reaches the server first gets its value accepted; all others are rejected
LAST_WRITER_WINS Whatever Response Message reaches the server last gets its value accepted; all others are overwritten. One can not remove values via the remove operation
LAST_WRITER_WINS_NULLABLE Same as Last Writer Wins, but one can use remove operations to unset the value

All of the above Types can be accessed as Layer.Constants.CRDT_TYPES.Set, Layer.Constants.CRDT_TYPES.LAST_WRITER_WINS, etc…

Setting State

Message state can be set using methods provided by Message Models:

model.responses.addState('favorite_colors', 'red');
model.responses.addState('latest_color', 'red');

The above code snippet will add red to the favorite_colors Set, and will update latest_color to be red.

Note that state is written on a per-user basis. If User A sets latest_color to be red and User B sets it to be blue both states will be part of the message and indexed by the user’s Identity ID.

Sharing State

Once the state has been updated, it must also be shared with the server and other participants; this is done by generating and sending a Response Message.

A Response Message consists of:

  1. A Message Part instructing the server on the changes to be made to the Message whose state is to be changed
  2. A Message Part containing a Status Message to inform users of what change has just occurrred

The following APIs are used to generate the Response Message:

Change operations are added to the Response Message using addState and removeState calls:

model.responses.removeState('favorite_colors', 'blue');
model.responses.addState('latest_color', 'red');

Each call will add operations to the Response Message.

A Status Message should also be added to the Message:

this.responses.setResponseMessageText("Frodo the Dodo has removed blue as a Favorite Color");

The Response Message will automatically send after waiting 100ms for any additional operations to accumulate; you may also simply call sendResponseMessage() to cause it to send imediately:

model.responses.removeState('favorite_colors', 'blue');
model.responses.addState('latest_color', 'red');
model.responses.setResponseMessageText("Frodo the Dodo has removed blue as a Favorite Color");
model.responsees.sendResponseMessage();

Retrieving State

Because state is written on a per-user basis, state must be retrieved using the user’s Identity ID:

const favorites = model.responses.getState("favorite_colors", "layer:///identities/FrodoTheDodo");
const latest = model.responses.getState("latest_color", "layer:///identities/FrodoTheDodo");

Detecting State Changes

Each time a Response Message is sent, there will be state changes in the Message that you operated upon. All participants will see these state changes. The state changes involve adding or updating a Response Summary Message Part. Clients detect this Response Summary’s changes and provide APIs around it.

When state changes, a model’s parseModelResponses method is called; if you are defining a model, then this method is used to both process initial response data (when loading a Message whose response was added some time ago) and called again for every change triggered by a Response Message.

parseModelResponses() {
  this.favorites = model.responses.getState("favorite_colors", "layer:///identities/FrodoTheDodo");
  this.latest = model.responses.getState("latest_color", "layer:///identities/FrodoTheDodo");
}

Ideally, a Model will implement a method for notifying listeners of these changes:

parseModelResponses() {
  const oldFavorites = this.favorites;
  const oldLatest = this.latest;
  this.favorites = model.responses.getState("favorite_colors", "layer:///identities/FrodoTheDodo");
  this.latest = model.responses.getState("latest_color", "layer:///identities/FrodoTheDodo");

  if (!myUtils.arrayEquals(oldFavorites, this.favorites)) {
    this._triggerAsync("message-type-model:change", {
      property: "favorites",
      oldValue: oldFavorites,
      newValue: this.favorites,
    });
  }

  if (oldLatest !== this.latest) {
    this._triggerAsync("message-type-model:change", {
      property: "latest",
      oldValue: oldLatest,
      newValue: this.latest,
    });
  }
}

Views and applications wanting to monitor for changes to the above states can simply use:

model.on("message-type-model:change", function(evt) {
  if (evt.hasProperty("latest")) {
    console.log("Latest has Changed!");
  }
});
Introduction Response Messages