bring protocol up to date

This commit is contained in:
Finn 2021-11-02 21:26:30 -07:00
parent 28b7d16568
commit 924e9c3767
4 changed files with 376 additions and 73 deletions

View file

@ -2,9 +2,9 @@
"doc_version": "v1",
"version": {
"name": "signald",
"version": "0.14.1-48-7d927883",
"version": "0.15.0-23-981b4409",
"branch": "main",
"commit": "7d92788343f34c08634abfeda06045ae13e18670"
"commit": "981b44098da8ddd748832597d5f5bde019197902"
},
"info": "This document describes objects that may be used when communicating with signald.",
"types": {
@ -145,7 +145,20 @@
"type": "boolean"
}
},
"doc": "indicates when the incoming connection to the signal server has started or stopped"
"doc": "prior attempt to indicate signald connectivity state. WebSocketConnectionState messages will be delivered at the same time as well as in other parts of the websocket lifecycle."
},
"WebSocketConnectionState": {
"fields": {
"state": {
"type": "String",
"doc": "One of: DISCONNECTED, CONNECTING, CONNECTED, RECONNECTING, DISCONNECTING, AUTHENTICATION_FAILED, FAILED"
},
"socket": {
"type": "String",
"doc": "One of: UNIDENTIFIED, IDENTIFIED"
}
},
"doc": "indicates when the websocket connection state to the signal server has changed"
},
"ClientMessageWrapper": {
"fields": {
@ -164,9 +177,56 @@
"error": {
"type": "Boolean",
"doc": "true if the incoming message represents an error"
},
"account": {
"type": "String",
"doc": "the account this message is from"
}
},
"doc": "Wraps all incoming messages after a v1 subscribe request is issued"
"doc": "Wraps all incoming messages sent to the client after a v1 subscribe request is issued"
},
"DuplicateMessageError": {
"fields": {
"message": {
"type": "String"
}
},
"error": true
},
"ProtocolInvalidMessageError": {
"fields": {
"sender": {
"type": "String"
},
"message": {
"type": "String"
},
"sender_device": {
"type": "int"
},
"content_hint": {
"type": "int"
},
"group_id": {
"type": "String"
}
},
"error": true
},
"UntrustedIdentityError": {
"fields": {
"identifier": {
"type": "String"
},
"message": {
"type": "String"
},
"identity_key": {
"type": "IdentityKey",
"version": "v1"
}
},
"error": true
},
"SendRequest": {
"fields": {
@ -208,6 +268,12 @@
"list": true,
"type": "JsonPreview",
"version": "v1"
},
"members": {
"list": true,
"type": "JsonAddress",
"version": "v1",
"doc": "Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified"
}
}
},
@ -283,7 +349,15 @@
"type": "String"
}
},
"doc": "an internal error in signald has occured.",
"doc": "an internal error in signald has occurred. typically these are things that \"should never happen\" such as issues saving to the local disk, but it is also the default error type and may catch some things that should have their own error type. If you find tht your code is depending on the exception list for any particular behavior, please file an issue so we can pull those errors out to a separate error type: https://gitlab.com/signald/signald/-/issues/new",
"error": true
},
"InvalidRequestError": {
"fields": {
"message": {
"type": "String"
}
},
"error": true
},
"UnknownGroupError": {
@ -294,6 +368,14 @@
},
"error": true
},
"RateLimitError": {
"fields": {
"message": {
"type": "String"
}
},
"error": true
},
"InvalidRecipientError": {
"fields": {
"message": {
@ -324,6 +406,12 @@
},
"timestamp": {
"type": "long"
},
"members": {
"list": true,
"type": "JsonAddress",
"version": "v1",
"doc": "Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified"
}
},
"doc": "react to a previous message"
@ -339,7 +427,7 @@
},
"version": {
"type": "String",
"example": "\"0.14.1-48-7d927883\""
"example": "\"0.15.0-23-981b4409\""
},
"branch": {
"type": "String",
@ -347,7 +435,7 @@
},
"commit": {
"type": "String",
"example": "\"7d92788343f34c08634abfeda06045ae13e18670\""
"example": "\"981b44098da8ddd748832597d5f5bde019197902\""
}
}
},
@ -573,14 +661,6 @@
}
}
},
"InvalidRequestError": {
"fields": {
"message": {
"type": "String"
}
},
"error": true
},
"InvalidInviteURIError": {
"fields": {
"message": {
@ -788,21 +868,6 @@
}
}
},
"UntrustedIdentityError": {
"fields": {
"identifier": {
"type": "String"
},
"message": {
"type": "String"
},
"identity_key": {
"type": "IdentityKey",
"version": "v1"
}
},
"error": true
},
"GetProfileRequest": {
"fields": {
"account": {
@ -1058,8 +1123,8 @@
},
"uri": {
"type": "String",
"doc": "the tsdevice:/ uri provided (typically in qr code form) by the new device",
"example": "\"tsdevice:/?uuid=jAaZ5lxLfh7zVw5WELd6-Q&pub_key=BfFbjSwmAgpVJBXUdfmSgf61eX3a%2Bq9AoxAVpl1HUap9\"",
"doc": "the sgnl://linkdevice uri provided (typically in qr code form) by the new device",
"example": "\"sgnl://linkdevice?uuid=jAaZ5lxLfh7zVw5WELd6-Q&pub_key=BfFbjSwmAgpVJBXUdfmSgf61eX3a%2Bq9AoxAVpl1HUap9\"",
"required": true
}
},
@ -1489,6 +1554,12 @@
"timestamp": {
"type": "long",
"required": true
},
"members": {
"list": true,
"type": "JsonAddress",
"version": "v1",
"doc": "Optionally set to a sub-set of group members. Ignored if group isn't specified"
}
},
"doc": "delete a message previously sent"
@ -1590,6 +1661,21 @@
},
"doc": "deny a request to join a group"
},
"SubmitChallengeRequest": {
"fields": {
"account": {
"type": "String",
"required": true
},
"challenge": {
"type": "String",
"required": true
},
"captcha_token": {
"type": "String"
}
}
},
"JsonDataMessage": {
"fields": {
"timestamp": {
@ -1795,6 +1881,26 @@
}
}
},
"IdentityKey": {
"fields": {
"added": {
"type": "long",
"doc": "the first time this identity key was seen"
},
"safety_number": {
"type": "String",
"example": "\"373453558586758076680580548714989751943247272727416091564451\""
},
"qr_code_data": {
"type": "String",
"doc": "base64-encoded QR code data"
},
"trust_level": {
"type": "String",
"doc": "One of TRUSTED_UNVERIFIED, TRUSTED_VERIFIED or UNTRUSTED"
}
}
},
"JsonQuote": {
"fields": {
"id": {
@ -1888,6 +1994,10 @@
},
"identityFailure": {
"type": "String"
},
"proof_required_failure": {
"type": "ProofRequiredError",
"version": "v1"
}
}
},
@ -1986,26 +2096,6 @@
},
"doc": "information about a legacy group"
},
"IdentityKey": {
"fields": {
"added": {
"type": "long",
"doc": "the first time this identity key was seen"
},
"safety_number": {
"type": "String",
"example": "\"373453558586758076680580548714989751943247272727416091564451\""
},
"qr_code_data": {
"type": "String",
"doc": "base64-encoded QR code data"
},
"trust_level": {
"type": "String",
"doc": "One of TRUSTED_UNVERIFIED, TRUSTED_VERIFIED or UNTRUSTED"
}
}
},
"Capabilities": {
"fields": {
"gv2": {
@ -2334,6 +2424,26 @@
}
}
},
"ProofRequiredError": {
"fields": {
"token": {
"type": "String"
},
"options": {
"list": true,
"type": "String",
"doc": "possible list values are RECAPTCHA and PUSH_CHALLENGE"
},
"message": {
"type": "String"
},
"retry_after": {
"type": "long",
"doc": "value in seconds"
}
},
"error": true
},
"ServerCDN": {
"fields": {
"number": {
@ -3287,9 +3397,15 @@
{
"name": "InternalError"
},
{
"name": "InvalidRequestError"
},
{
"name": "UnknownGroupError"
},
{
"name": "RateLimitError"
},
{
"name": "InvalidRecipientError"
}
@ -3320,6 +3436,12 @@
},
{
"name": "UnknownGroupError"
},
{
"name": "InvalidRequestError"
},
{
"name": "RateLimitError"
}
]
},
@ -3349,6 +3471,9 @@
},
{
"name": "InternalError"
},
{
"name": "InvalidRequestError"
}
]
},
@ -3374,6 +3499,9 @@
},
{
"name": "GroupVerificationError"
},
{
"name": "InvalidRequestError"
}
]
},
@ -3402,6 +3530,9 @@
},
{
"name": "InvalidGroupStateError"
},
{
"name": "InvalidRequestError"
}
]
},
@ -3421,6 +3552,9 @@
},
{
"name": "NoSuchAccountError"
},
{
"name": "InvalidRequestError"
}
]
},
@ -3479,6 +3613,9 @@
},
{
"name": "NoSuchAccountError"
},
{
"name": "InvalidRequestError"
}
]
},
@ -3527,6 +3664,9 @@
},
{
"name": "InvalidBase64Error"
},
{
"name": "InvalidRequestError"
}
]
},
@ -3600,6 +3740,9 @@
},
{
"name": "NoSuchAccountError"
},
{
"name": "InvalidRequestError"
}
]
},
@ -3678,6 +3821,9 @@
},
{
"name": "GroupVerificationError"
},
{
"name": "InvalidRequestError"
}
]
},
@ -3882,6 +4028,9 @@
},
{
"name": "UnknownGroupError"
},
{
"name": "InvalidRequestError"
}
]
},
@ -3894,22 +4043,28 @@
"name": "InternalError"
},
{
"name": "InvalidProxyError"
"name": "ServerNotFoundError"
},
{
"name": "ServerNotFoundError"
"name": "InvalidProxyError"
},
{
"name": "NoSuchAccountError"
},
{
"name": "InvalidRecipientError"
"name": "InvalidRequestError"
},
{
"name": "NoSendPermissionError"
},
{
"name": "UnknownGroupError"
},
{
"name": "RateLimitError"
},
{
"name": "InvalidRecipientError"
}
]
},
@ -4016,6 +4171,9 @@
},
{
"name": "GroupVerificationError"
},
{
"name": "InvalidRequestError"
}
]
},
@ -4111,6 +4269,12 @@
},
{
"name": "UnknownGroupError"
},
{
"name": "InvalidRequestError"
},
{
"name": "RateLimitError"
}
]
},
@ -4172,6 +4336,12 @@
},
{
"name": "NoSendPermissionError"
},
{
"name": "InvalidRequestError"
},
{
"name": "RateLimitError"
}
]
},
@ -4214,6 +4384,29 @@
{
"name": "GroupVerificationError"
},
{
"name": "InternalError"
},
{
"name": "InvalidRequestError"
}
]
},
"submit_challenge": {
"request": "SubmitChallengeRequest",
"errors": [
{
"name": "NoSuchAccountError"
},
{
"name": "InvalidRequestError"
},
{
"name": "ServerNotFoundError"
},
{
"name": "InvalidProxyError"
},
{
"name": "InternalError"
}

View file

@ -39,6 +39,13 @@ func mkerr(response client_protocol.BasicResponse) error {
return err
}
return result
case "DuplicateMessageError":
result := DuplicateMessageError{}
err := json.Unmarshal(response.Error, &result)
if err != nil {
return err
}
return result
case "FingerprintVersionMismatchError":
result := FingerprintVersionMismatchError{}
err := json.Unmarshal(response.Error, &result)
@ -179,6 +186,27 @@ func mkerr(response client_protocol.BasicResponse) error {
return err
}
return result
case "ProofRequiredError":
result := ProofRequiredError{}
err := json.Unmarshal(response.Error, &result)
if err != nil {
return err
}
return result
case "ProtocolInvalidMessageError":
result := ProtocolInvalidMessageError{}
err := json.Unmarshal(response.Error, &result)
if err != nil {
return err
}
return result
case "RateLimitError":
result := RateLimitError{}
err := json.Unmarshal(response.Error, &result)
if err != nil {
return err
}
return result
case "ServerNotFoundError":
result := ServerNotFoundError{}
err := json.Unmarshal(response.Error, &result)
@ -253,6 +281,14 @@ func (e CaptchaRequiredError) Error() string {
return e.Message
}
type DuplicateMessageError struct {
Message string `json:"message,omitempty" yaml:"message,omitempty"`
}
func (e DuplicateMessageError) Error() string {
return e.Message
}
type FingerprintVersionMismatchError struct {
Message string `json:"message,omitempty" yaml:"message,omitempty"`
}
@ -285,7 +321,7 @@ func (e GroupVerificationError) Error() string {
return e.Message
}
// InternalError: an internal error in signald has occured.
// InternalError: an internal error in signald has occurred. typically these are things that "should never happen" such as issues saving to the local disk, but it is also the default error type and may catch some things that should have their own error type. If you find tht your code is depending on the exception list for any particular behavior, please file an issue so we can pull those errors out to a separate error type: https://gitlab.com/signald/signald/-/issues/new
type InternalError struct {
Exceptions []string `json:"exceptions,omitempty" yaml:"exceptions,omitempty"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
@ -417,6 +453,37 @@ func (e ProfileUnavailableError) Error() string {
return e.Message
}
type ProofRequiredError struct {
Message string `json:"message,omitempty" yaml:"message,omitempty"`
Options []string `json:"options,omitempty" yaml:"options,omitempty"` // possible list values are RECAPTCHA and PUSH_CHALLENGE
RetryAfter int64 `json:"retry_after,omitempty" yaml:"retry_after,omitempty"` // value in seconds
Token string `json:"token,omitempty" yaml:"token,omitempty"`
}
func (e ProofRequiredError) Error() string {
return e.Message
}
type ProtocolInvalidMessageError struct {
ContentHint int32 `json:"content_hint,omitempty" yaml:"content_hint,omitempty"`
GroupId string `json:"group_id,omitempty" yaml:"group_id,omitempty"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
Sender string `json:"sender,omitempty" yaml:"sender,omitempty"`
SenderDevice int32 `json:"sender_device,omitempty" yaml:"sender_device,omitempty"`
}
func (e ProtocolInvalidMessageError) Error() string {
return e.Message
}
type RateLimitError struct {
Message string `json:"message,omitempty" yaml:"message,omitempty"`
}
func (e RateLimitError) Error() string {
return e.Message
}
type ServerNotFoundError struct {
Message string `json:"message,omitempty" yaml:"message,omitempty"`
UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"`

View file

@ -1132,6 +1132,31 @@ func (r *SetProfile) Submit(conn *signald.Signald) (err error) {
}
func (r *SubmitChallengeRequest) Submit(conn *signald.Signald) (err error) {
r.Version = "v1"
r.Type = "submit_challenge"
if r.ID == "" {
r.ID = signald.GenerateID()
}
err = conn.RawRequest(r)
if err != nil {
log.Println("signald-go: error submitting request to signald")
return
}
responseChannel := conn.GetResponseListener(r.ID)
defer conn.CloseResponseListener(r.ID)
rawResponse := <-responseChannel
if rawResponse.Error != nil {
err = mkerr(rawResponse)
return
}
return err
}
// Submit: receive incoming messages. After making a subscribe request, incoming messages will be sent to the client encoded as ClientMessageWrapper. Send an unsubscribe request or disconnect from the socket to stop receiving messages.
func (r *SubscribeRequest) Submit(conn *signald.Signald) (err error) {
r.Version = "v1"

View file

@ -35,7 +35,7 @@ type AccountList struct {
type AddLinkedDeviceRequest struct {
Request
Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to interact with
Uri string `json:"uri,omitempty" yaml:"uri,omitempty"` // the tsdevice:/ uri provided (typically in qr code form) by the new device
Uri string `json:"uri,omitempty" yaml:"uri,omitempty"` // the sgnl://linkdevice uri provided (typically in qr code form) by the new device
}
// AddServerRequest: add a new server to connect to. Returns the new server's UUID.
@ -82,8 +82,9 @@ type Capabilities struct {
Storage bool `json:"storage,omitempty" yaml:"storage,omitempty"`
}
// ClientMessageWrapper: Wraps all incoming messages after a v1 subscribe request is issued
// ClientMessageWrapper: Wraps all incoming messages sent to the client after a v1 subscribe request is issued
type ClientMessageWrapper struct {
Account string `json:"account,omitempty" yaml:"account,omitempty"` // the account this message is from
Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // the incoming object. The structure will vary from message to message, see `type` and `version` fields
Error bool `json:"error,omitempty" yaml:"error,omitempty"` // true if the incoming message represents an error
Type string `json:"type,omitempty" yaml:"type,omitempty"` // the type of object to expect in the `data` field
@ -399,11 +400,12 @@ type JsonReadMessage struct {
}
type JsonSendMessageResult struct {
Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"`
IdentityFailure string `json:"identityFailure,omitempty" yaml:"identityFailure,omitempty"`
NetworkFailure bool `json:"networkFailure,omitempty" yaml:"networkFailure,omitempty"`
Success *SendSuccess `json:"success,omitempty" yaml:"success,omitempty"`
UnregisteredFailure bool `json:"unregisteredFailure,omitempty" yaml:"unregisteredFailure,omitempty"`
Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"`
IdentityFailure string `json:"identityFailure,omitempty" yaml:"identityFailure,omitempty"`
NetworkFailure bool `json:"networkFailure,omitempty" yaml:"networkFailure,omitempty"`
ProofRequiredFailure *ProofRequiredError `json:"proof_required_failure,omitempty" yaml:"proof_required_failure,omitempty"`
Success *SendSuccess `json:"success,omitempty" yaml:"success,omitempty"`
UnregisteredFailure bool `json:"unregisteredFailure,omitempty" yaml:"unregisteredFailure,omitempty"`
}
type JsonSentTranscriptMessage struct {
@ -481,7 +483,7 @@ type ListGroupsRequest struct {
Account string `json:"account,omitempty" yaml:"account,omitempty"`
}
// ListenerState: indicates when the incoming connection to the signal server has started or stopped
// ListenerState: prior attempt to indicate signald connectivity state. WebSocketConnectionState messages will be delivered at the same time as well as in other parts of the websocket lifecycle.
type ListenerState struct {
Connected bool `json:"connected,omitempty" yaml:"connected,omitempty"`
}
@ -529,11 +531,12 @@ type ProfileList struct {
// ReactRequest: react to a previous message
type ReactRequest struct {
Request
Reaction *JsonReaction `json:"reaction,omitempty" yaml:"reaction,omitempty"`
RecipientAddress *JsonAddress `json:"recipientAddress,omitempty" yaml:"recipientAddress,omitempty"`
RecipientGroupID string `json:"recipientGroupId,omitempty" yaml:"recipientGroupId,omitempty"`
Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified
Reaction *JsonReaction `json:"reaction,omitempty" yaml:"reaction,omitempty"`
RecipientAddress *JsonAddress `json:"recipientAddress,omitempty" yaml:"recipientAddress,omitempty"`
RecipientGroupID string `json:"recipientGroupId,omitempty" yaml:"recipientGroupId,omitempty"`
Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
}
type ReceiptMessage struct {
@ -582,10 +585,11 @@ type RemoteDelete struct {
// RemoteDeleteRequest: delete a message previously sent
type RemoteDeleteRequest struct {
Request
Account string `json:"account,omitempty" yaml:"account,omitempty"` // the account to use
Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` // the address to send the delete message to. should match address the message to be deleted was sent to. required if group is not set.
Group string `json:"group,omitempty" yaml:"group,omitempty"` // the group to send the delete message to. should match group the message to be deleted was sent to. required if address is not set.
Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`
Account string `json:"account,omitempty" yaml:"account,omitempty"` // the account to use
Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` // the address to send the delete message to. should match address the message to be deleted was sent to. required if group is not set.
Group string `json:"group,omitempty" yaml:"group,omitempty"` // the group to send the delete message to. should match group the message to be deleted was sent to. required if address is not set.
Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // Optionally set to a sub-set of group members. Ignored if group isn't specified
Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`
}
// RemoveLinkedDeviceRequest: Remove a linked device from the Signal account. Only allowed when the local device id is 1
@ -637,6 +641,7 @@ type SendPaymentRequest struct {
type SendRequest struct {
Request
Attachments []*v0.JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"`
Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified
Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"`
MessageBody string `json:"messageBody,omitempty" yaml:"messageBody,omitempty"`
Previews []*JsonPreview `json:"previews,omitempty" yaml:"previews,omitempty"`
@ -713,6 +718,13 @@ type SetProfile struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"` // New profile name. Set to empty string for no profile name
}
type SubmitChallengeRequest struct {
Request
Account string `json:"account,omitempty" yaml:"account,omitempty"`
CaptchaToken string `json:"captcha_token,omitempty" yaml:"captcha_token,omitempty"`
Challenge string `json:"challenge,omitempty" yaml:"challenge,omitempty"`
}
// SubscribeRequest: receive incoming messages. After making a subscribe request, incoming messages will be sent to the client encoded as ClientMessageWrapper. Send an unsubscribe request or disconnect from the socket to stop receiving messages.
type SubscribeRequest struct {
Request
@ -788,3 +800,9 @@ type VerifyRequest struct {
type VersionRequest struct {
Request
}
// WebSocketConnectionState: indicates when the websocket connection state to the signal server has changed
type WebSocketConnectionState struct {
Socket string `json:"socket,omitempty" yaml:"socket,omitempty"` // One of: UNIDENTIFIED, IDENTIFIED
State string `json:"state,omitempty" yaml:"state,omitempty"` // One of: DISCONNECTED, CONNECTING, CONNECTED, RECONNECTING, DISCONNECTING, AUTHENTICATION_FAILED, FAILED
}