Skip to content
This repository has been archived by the owner on Apr 4, 2019. It is now read-only.

Protocol

artman edited this page Dec 17, 2014 · 9 revisions

Jetstream uses JSON to send messages between the client and server. This section described this protocol in the order that messages are being sent between the client and the server.

SessionCreate message

The client kicks off communications by sending a SessionCreate message.

{
    "type": "SessionCreate",
    "version": "1.0.0",
    "params": <JSON>,
    "index": 0
}
  • type: Identifies the type of the message
  • version: The version of the Jetstream library
  • params: Any application-specific parameters to use for authentication etc.
  • index: An incrementing counter to identify the message, when creating a session the session has not been established yet so this should always be 0.

SessionCreateReply message

The server will respond to a session create request by either accepting or declining the request.

{
    "type": "SessionCreateReply",
    "index": 2,
    "scopeIndex": 1,
    "error": {
        "code": 1,
        "type": "auth-error",
        "message": "Could not authenticate user"
    }
}
  • type: Identifies the type of the message
  • index: An incrementing counter to identify the message
  • scopeIndex: The index of the scope for later reference
  • error: An error object containing details why the scope could not be fetched if an error occurred

ScopeFetch message

The client will then request to get the contents of a scope

{
    "type": "ScopeFetch",
    "index": 1,
    "name": "Canvas",
    "params": <JSON>
}
  • type: Identifies the type of the message
  • index: An incrementing counter to identify the message
  • name: The name of the scope to start fetching
  • params: Any application-specific parameters to send to the server to use for user authentication etc.

ScopeFetchReply message

The client will then request to get the contents of a scope

{
    "type": "ScopeFetchReply",
    "index": 1,
    "name": "Canvas",
    "error": {
        "code": 1,
        "type": "non-existent-scope",
        "message": "The specified scope doesn't exist"
    }
}
  • type: Identifies the type of the message
  • index: An incrementing counter to identify the message
  • name: The name of the scope to start fetching
  • params: Any application-specific parameters to send to the server to use for user authentication etc.

ScopeState message

The server will send the full state of the scope to the client using a ScopeState message.

{
    "type": "ScopeState",
    "index": 1,
    "scopeIndex": 1,
    "rootUUID": "4D7D7A73-5BB7-4A3A-8D2D-CE996179FAE4",
    "fragments": [
        {
            "type": "add",
            "uuid": "4D7D7A73-5BB7-4A3A-8D2D-CE996179FAE4",
            "clsName": "ClassName",
            "properties": {
                "propertyName": "propertyValue",
                "otherPropertyName": 10
            }
        },
        <More_fragments>
    ]
}
  • type: Identifies the type of the message
  • index: An incrementing counter to identify the message
  • scopeIndex: Identifies the index of the scope to which this message is to be applied
  • rootUUID: The uuid of the root model object.
  • fragments: An array of SyncFragments of type "add" to add to the scope.

A sync fragment describes an addition or change to a single model in a scope:

  • type: The type of the sync fragment. Possible values are
    • change: Denotes a change to the properties of a model in the scope
    • add: Denotes the addition of a new model to the scope
  • uuid: The uuid of the object that is targeted with the change
  • clsName: Identifies the name of the class that is affected by the add or change.
  • properties: Key-value pairs of property names an their values to apply to the model. Not present in an remove fragment

Note that the server might at any point decide to reload a scopes state from its backing services and re-send a ScopeState message to all clients to ensure everyone is in sync. The client should be able to accept a ScopeState message at any time and apply changes without fully resetting its internal state. The strategy for this is to:

  • Loop through all your local models and remove any that aren't present as add fragments in the incoming ScopeState message
  • Loop through all fragments in the ScopeState message and create any models that aren't locally present.
  • Loop through all fragments in the ScopeState message and apply their properties to your local models.

ScopeSync message

After a ScopeState message has been received by a client, the server and client are in sync. Changes to models will then be described with ScopeSync messages. Both the server and the client will send out these messages. When the client sends out a ScopeSync message, it should listen on a reply from the server, as it has the authority to accept, reject or modify fragments in a ScopeSync message.

{
    "type": "ScopeSync",
    "index": 1,
    "scopeIndex": 1,
    "atomic": true,
    "fragments": [
        <SyncFragments>
    ]
}
  • type: Identifies the type of the message
  • index: An incrementing counter to identify the message
  • scopeIndex: Identifies the index of the scope to which this message is to be applied
  • atomic: Optional boolean value which describes whether the changes described in fragments should be atomic. Atomicity essentially means that if one of the fragments fail to be applied, all of the fragments should be rejected by the server.
  • fragments: An array of Sync Fragments to apply

ScopeSyncReply message

A response to changes that the client wanted to make

{
    "type": "ScopeSyncReply",
    "index": 2,
    "replyTo": 1,
    "fragmentReplies": [
        {},
        {
            "error": {
                "code": 1,
                "type": "invalid-type",
                "message": "Invalid type for property 'name'"
            }
        },
        {
            "modifications": {
                "name": "Update name",
                "cost": 10.5
            }
        },
        ...
    ]
}
  • type: Identifies the type of the message
  • index: An incrementing counter to identify the message
  • replyTo: The index of the ScopeSync message to which this is a reply to
  • fragmentReplies: An array of responses that correlate to the fragments of the ScopeSync message to which this is a reply to. Each element in the array is an object that describes what happened to the fragment on the backend:
    • Empty object: The fragment was applied on the server without changes
    • Error object: The fragment wasn't applied. The client should roll back the changes for this fragment.
    • Modifications object: The fragment was applied with the given changes. The client should apply the properties listed to the object associated with the original sync fragment.

Ping message

A client should periodically (at least every 15 seconds) send out a Ping message to keep the session active and inform the server which messages it has received. Whenever the client reconnects to an existing session it should immediately send out a ping with the resendMissing-flag set to true to force the server to re-send any messages that the client might not have received.

{
    "type": "Ping",
    "ack": 4,
    "resendMissing": false
}
  • type: Identifies the type of the message
  • ack: The client acknowledges that it has seen all messages up to the given index
  • resendMissing: If true, forces the server to resend all messages that the client hasn't seen. A client will use this to force the server to resend missing messages whenever it reconnects to the server.