-
Notifications
You must be signed in to change notification settings - Fork 15
ClientProtocolSpecification
PyGoWave Server uses a proprietary (i.e. non-standard, but well, there is no standard) protocol to communicate to its web client interface, just like Google use their proprietary protobuf-based client protocol for the officially released console client. It is roughly based on the Wave Robot API.
This document aims to give an overview how the protocol is structured.
All messages are serialized via JSON and have at least a “type” field. Depending on type they also may have a “property” field. While “type” is strictly a string, “property” can be of any type. It may be an integer, a string or even a hash map as long as it’s serializable via JSON; the contents of “property” is defined by the type of the message.
Messages may also have a direction, where the client-to-server message has different contents than the server-to-client message.
Note: Not all messages are subject to Operational Transformation and there are some messages that do not modify any data on the server but are simply for obtaining data from the server.
These messages are request-response based and thus the client has to wait for the server to answer or take action.
Property:
(none)
Description:
Sent by the client to request initial Wavelet data and to start receiving messages for that Wavelet. The Wavelet ID is retrieved internally from the message broker.
Property:
{
"wavelet": {
"rootBlipId": (string),
"title": (string),
"creator": (string),
"creationTime": (int),
"dataDocuments": None,
"waveletId": (string),
"participants": [(string), ...],
"version": (int),
"lastModifiedTime": (int),
"waveId": (string)
},
"blips": {
(string): {
"blipId": (string),
"content": (string),
"elements": [
{
"index": (int),
"type": (string),
"properties": (obj)
},
...
],
"contributors": [(string), ...],
"creator": (string),
"parentBlipId": (string),
"annotations": [
{
"range": {
"start": (int),
"end": (int)
},
"name": (string),
"value": (obj)
},
...
],
"waveletId": (string),
"version": (int),
"lastModifiedTime": (int),
"childBlipIds": [(string), ...],
"waveId": (string),
"submitted": (boolean)
"checksum": (string)
},
...
}
}
Description:
Contains the Wavelet and its blips in serialized form.
Property:
[
(string),
...
]
Description:
Requests information on one or more participants (by id).
Property:
[
{
"id": (string),
"displayName": (string),
"thumbnailUrl": (string),
"profileUrl": (string),
"isBot": (boolean)
},
...
]
Description:
The resulting participant information.
Property:
(string)
Description:
Searches for participants. Property is the search query.
Property:
"result": (string),
"data": [(string), ...]
Description:
Results of a participant serach. If “result” is “OK”, then data is a list of matching participant IDs. If result is “TOO_SHORT”, then data is an integer of the minimum required length.
Property:
(none)
Description:
Requests a list of all available gadgets.
Property:
[
{
"id": (int),
"uploaded_by": (string),
"name": (string),
"descr": (string),
"url": (string)
},
...
]
Description:
The requested list of participants. “uploaded_by” is the uploader’s participant name.
Property:
(string)
Description:
Adds a participant to the Wavelet. The property is the participant’s ID.
Property:
(string)
Description:
Broadcast message of a newly added participant. The property is the participant’s ID.
Property:
(none)
Description:
Requests to remove the connected participant from the wave.
Property:
(none)
Description:
Acknowledges removal of the connected participant from the wave.
All messages which are subject to Operational Transformation are contained in an
OPERATION_MESSAGE_BUNDLE message. The server responds with a OPERATION_MESSAGE_BUNDLE_ACK message afterwards and sends a OPERATION_MESSAGE_BUNDLE message to each other connected client.
Property:
{
"version": (int),
"operations": [(obj), ...]
}
Description:
Operations sent by the client.
Property:
{
"version": (int),
"operations": [(obj), ...],
"blipsums": {
(string): (string),
...
}
}
Description:
Operations sent by the server. There is an additional “blipsums” field that contains a hash map “blipId: sha1(blipText)”.
Property:
{
"version": (int),
"blipsums": {
(string): (string),
...
}
}
Description:
Send when the server acknowledges a client’s message bundle.
All operations have the following fields:
{
"type": (string),
"wave_id": (string),
"wavelet_id": (string),
"blip_id": (string),
"index": (int),
"property": (obj),
}
Where property again is a custom object depending on the type.
The following operations are implemented:
Property:
(string)
Description:
Insert text into the blip. Property is the text.
Property:
(int)
Description:
Delete text from the blip. Property is the length.
Property:
{
"type": (string),
"properties": (obj)
}
Description:
Insert an element into the blip.
Property:
(none)
Description:
Delete an element from the blip.
Property:
{
(string): (string),
...
}
Description:
Apply a delta to the element’s state object on the blip.
Property:
{
"key": (string),
"value": (string)
}
Description:
Set a UserPref of the element on the blip.
Here are some message dumps for you to see the protocol in action:
-- Client -> Server -- { 'type': 'WAVELET_OPEN' }
-- Server -> Client -- { 'type': 'WAVELET_OPEN', 'property': { 'wavelet': { 'waveletId': 'otdWRSQCcs!conv+root', 'participants': ['opera@localhost'], 'version': 0, 'rootBlipId': 'eUFnen1V90', 'title': 'Demo', 'lastModifiedTime': 1251282070000, 'creator': 'opera@localhost', 'creationTime': 1251282070000, 'waveId': 'otdWRSQCcs', 'dataDocuments': None }, 'blips': { 'eUFnen1V90': { 'blipId': 'eUFnen1V90', 'elements': [], 'contributors': [], 'creator': 'opera@localhost', 'lastModifiedTime': 1251282070000, 'waveId': 'otdWRSQCcs', 'waveletId': 'otdWRSQCcs!conv+root', 'checksum': 'da39a3ee5e6b4b0d3255bfef95601890afd80709', 'parentBlipId': None, 'submitted': False, 'content': '', 'version': 0, 'childBlipIds': [], 'annotations': [] } } }, }
-- Client -> Server -- { 'type': 'PARTICIPANT_INFO', 'property': ['opera@localhost'] }
-- Server -> Client -- { 'type': 'PARTICIPANT_INFO', 'property': { 'opera@localhost': { 'profileUrl': '', 'isBot': 0, 'thumbnailUrl': '/pygowave/media/avatars/f526_Knuckles.png', 'displayName': 'opera', 'id': 'opera@localhost' } } }
-- Client -> Server -- { 'type': 'OPERATION_MESSAGE_BUNDLE', 'property': { 'operations': [ { 'type': 'DOCUMENT_INSERT', 'index': 0, 'blip_id': 'eUFnen1V90', 'wavelet_id': 'otdWRSQCcs!conv+root', 'wave_id': 'otdWRSQCcs', 'property': 'X' } ], 'version': 0 } }
-- Server -> Client -- { 'type': 'OPERATION_MESSAGE_BUNDLE_ACK', 'property': { 'blipsums': { 'eUFnen1V90': 'c032adc1ff629c9b66f22749ad667e6beadf144b' }, 'version': 1 } }
-- Broadcast -- { 'type': 'OPERATION_MESSAGE_BUNDLE', 'property': { 'operations': [ { 'type': 'DOCUMENT_INSERT', 'index': 0, 'blip_id': 'eUFnen1V90', 'wavelet_id': 'otdWRSQCcs!conv+root', 'wave_id': 'otdWRSQCcs', 'property': 'X' } ], 'blipsums': { 'eUFnen1V90': 'c032adc1ff629c9b66f22749ad667e6beadf144b' }, 'version': 1 } }
-- Client -> Server -- { 'type': 'OPERATION_MESSAGE_BUNDLE', 'property': { 'operations': [ { 'type': 'DOCUMENT_DELETE', 'index': 0, 'blip_id': 'eUFnen1V90', 'wavelet_id': 'otdWRSQCcs!conv+root', 'wave_id': 'otdWRSQCcs', 'property': 1 } ], 'version': 1 } }
-- Server -> Client -- { 'type': 'OPERATION_MESSAGE_BUNDLE_ACK', 'property': { 'blipsums': { 'eUFnen1V90': 'da39a3ee5e6b4b0d3255bfef95601890afd80709' }, 'version': 2 } }
-- Broadcast -- { 'type': 'OPERATION_MESSAGE_BUNDLE%2