To establish a new IC WebSocket connection, the client (via the IC WebSocket Frontend SDK):
- chooses a WS Gateway among the available ones and creates a new instance of
IcWebSocket
and passes the client’s identity to it (if any, otherwise a random one is generated by the SDK). - opens a WebSocket connection to the specified Gateway.
- once the WebSocket connection is open, it creates a WebSocket Actor which is used to send requests, signed with the provided identity, to the Gateway. Each request specifies the canister and the method which the request is for.
- the first request sent is a signed envelope with content of
Call
variant. The content contains, among other things, the principal of the canister the client is connecting to,ws_open
as the method name, and the argument of typeCanisterWsOpenArguments
. - sends the envelope to the WS Gateway.
- once it receives the response containing the result of type
CanisterWsOpenResultValue
, triggers theonWsOpen
callback.
The Gateway:
- receives the envelope with content of variant
Call
from the client and relays it to thecanister/<canister_id>/call
endpoint of the Internet Computer. - receives the HTTP response from the IC containing
Ok(())
in the body and relays it to the client. This response is not enough for the client to triggeronWsOpen
. - creates a mapping between the
client_id
assigned to the WebSocket connection and the client’s key composed of a principal (either corresponding to the client’s identity or to the one randomly generated by the SDK) and the nonce specified by the client SDK. - if the client is the first connecting to the specified canister via this WS Gateway, the latter starts polling the canister by querying the
ws_get_messages
endpoint, otherwise the Gateway is already polling the canister. - once the poller returns certified messages from the canister, it relays each of them to the clients via their corresponding WebSocket connection, together with the certificate.
The canister (via the IC WebSocket Backend CDK):
- receives a request on the
ws_open
method from the client (relayed by the Gateway in a way that is transparent to the canister). - if the client is the first connecting via this Gateway, it creates a message queue where it stores all the messages of clients connected via that Gateway, and which only this can poll. Otherwise, the queue already exists.
- once the canister processes the request to the
ws_open
method, it puts the message containing the result of typeCanisterWsOpenResultValue
in the respective message queue which the Gateway fetches in the next polling iteration. - triggers the
on_open
callback.
Types:
CanisterWsOpenArguments
- client nonce used by the canister to distinguish two different connections from the same client.
CanisterWsOpenResult
- result with empty
Ok
value. Needed only to let the client know that the IC WebSocket connection has been opened.
- result with empty
Once the connection is established, the client can send WebSocket messages to the canister. In order to do so, the client (via the IC WebSocket Frontend SDK):
- creates a signed envelope with content of
Call
variant. The content contains, among other things, the principal of the canister the client is connected to,ws_message
as the method name, and the argument of typeCanisterWsMessageArguments
. - sends the envelope to the WS Gateway.
The Gateway:
- receives the envelope from the client and relays it to the
canister/<canister_id>/call
endpoint of the Internet Computer. - receives the HTTP response from the canister containing
Ok(())
in the body and relays it to the client. This is not enough to acknowledge the client’s message.
The canister (via the IC WebSocket Backend CDK):
- receives a request on the
ws_message
method from the client (relayed by the Gateway in a way that is transparent to the canister). - checks whether the sequence number of the
WebSocketMessage
(included automatically by the client’s SDK) corresponds to the next expected sequence number from the respective client. - triggers the
on_message
callback.
Types:
CanisterWsMessageArguments
- message of type
WebSocketMessage
- sequence number used to identify the client message
- serialized content
- client key composed of client’s principal and nonce specified during the opening of the connection
- timestamp
is_service_message
flag used to determine whether the message is only used by the CDK and SDK to detect eventual bad behaviour of the WS Gateway. Messages flagged as true are not passed to the client.
- message of type
Once the connection is established, the canister can send WebSocket messages to the client. In order to do so, the canister (via the IC WebSocket Backend CDK):
- calls the
ws_send
method, specifying the client key of the client it wants to send the message to and the serialized message to be sent. - the message of type
CanisterOutputMessage
is stored in the message queue corresponding to the WS Gateway which the client is connected to.
The Gateway:
- fetches the messages of type
CanisterOutputMessage
from the respective queue of the canister in the next polling iteration by querying thews_get_messages
method with the argument of typeCanisterWsGetMessagesArguments
. The list of fetched messages is of typeCanisterOutputCertifiedMessages
. - for each message of type
CanisterOutputMessage
, gets theclient_id
corresponding to client key specified in the message. - constructs a message of type
CanisterToClientMessage
from the one of typeCanisterOutputMessage
. - relays the message of type
CanisterToClientMessage
to the client via the WebSocket connection identified by theclient_id
.
The client (via the IC WebSocket Frontend SDK):
- receives the message of type
CanisterToClientMessage
from the Gateway via WebSocket. - verifies the certificate which proves that the message has been created by the canister.
- checks whether the sequence number of the
WebSocketMessage
corresponds to the next expected sequence number from the canister. - triggers the
onWsMessage
callback.
Types:
CanisterWsGetMessagesArguments
- nonce used by the WS Gateway to let the CDK know which was the last polled message. This way the CDK does not return messages that have already been relayed to the clients.
CanisterOutputCertifiedMessages
- vector of messages of type
CanisterOutputMessage
- certificate of all the messages
- certified state tree
- vector of messages of type
CanisterOutputMessage
- client key of the client which the message is for. This is used by the WS Gateway to get the
client_id
corresponding to the WebSocket connection with the respective client. - content of type
WebSocketMessage
- sequence number used to identify the client message
- serialized content
- client key composed of client’s principal and nonce specified during the opening of the connection
- timestamp
is_service_message
flag used to determine whether the message is only used by the CDK and SDK to detect eventual bad behaviour of the WS Gateway. Messages flagged as true are not passed to the client.
- key constructed by appending the next outgoing message nonce to the gateway principal. This key is used by the client to verify the certificate of the response and by the Gateway to determine the nonce to poll from in the next polling iteration.
- client key of the client which the message is for. This is used by the WS Gateway to get the
CanisterToClientMessage
- content of type
WebSocketMessage
(same as inCanisterOutputMessage
). - key constructed by appending the next outgoing message nonce to the gateway principal (same as in
CanisterOutputMessage
). - certificate of all the messages
- certified state tree, containing that message
- content of type
All messages are relayed by the Gateway which acts as a man-in-the-middle and could therefore tamper, reorder, or block messages. Tampering and reordering can be detected thanks to signed messages and sequence numbers, respectively. However, client and canister have to be able to detect whether the Gateway is blocking the messages within reasonable time. To achieve this, the canister (via the IC WebSocket Backend CDK):
- periodically (with configurable period
T
) creates a service message of typeWebsocketServiceMessageContent::AckMessage
containing an acknowledgement message of typeCanisterAckMessageContent
and pushes it in the message queue for each client connected to it. - once it receives a service message of type
WebsocketServiceMessageContent::KeepAliveMessage
containing a keep alive message of typeClientKeepAliveMessageContent
for a certain client, it records the time current time. 1/2 T
after the acknowledgement for a client has been sent (i.e. pushed to the message queue), it checks whether the time of the last keep alive message received for that client is less than3/2 T
. If that’s not the case, the gateway blocked the messages.
The gateway:
- fetches the acknowledgement message for a client from the respective queue of the canister in the next polling iteration, as if it were a normal canister message.
- checks the
client_id
corresponding to the client key of the client which the message has to be delivered to. - relays the message to the client via the WebSocket connection identified by the
client_id
. - keeps relaying the signed envelopes received from the client to the canister as these include, among others, the keep alive message sent by the client in response to the acknowledgement message from the canister.
The client (via the IC WebSocket Frontend SDK):
- upon sending a message via the WebSocket, it records the current time.
- upon receiving a service message of type
WebSocketMessage
from the Gateway with content of typeWebsocketServiceMessageContent::AckMessage(CanisterAckMessageContent)
, first performs the same steps as when receiving a relayed canister message (as explained in a previous section). Then, considers all the messages with sequence number lower or equal to the one contained in the service message as acknowledged. For all the others, it checks whether more than3/2 T
has passed, if so the gateway blocked some messages. Otherwise, it still does not considers these messages as acknowledged (and therefore an other acknowledgement for these will be expected) but it sends a serviceWebSocketMessagee
with content of typeWebsocketServiceMessageContent::AckMessage(ClientKeepAliveMessageContent)
.
Types:
CanisterAckMessageContent
- sequence number of the last message received by the canister from the respective client. This acknowledgement is serialized and used as content of a
WebsocketMessage
message whereis_service_message
is set totrue
andsequence_num
is the next sequence number of the messages sent from the canister to the respective client.
- sequence number of the last message received by the canister from the respective client. This acknowledgement is serialized and used as content of a
ClientKeepAliveMessageContent
- sequence number of the last message that the client sending the keep alive message received from the canister.