@ -126,24 +126,28 @@ window.textsecure.throwHumanError = function(error, type, humanError) {
throw e ;
throw e ;
}
}
// message_callback({message: decryptedMessage, pushMessage: server-providedPushMessage})
var handleAttachment = function ( attachment ) {
window . textsecure . subscribeToPush = function ( message _callback ) {
function getAttachment ( ) {
var socket = textsecure . api . getMessageWebsocket ( ) ;
return textsecure . api . getAttachment ( attachment . id . toString ( ) ) ;
}
var resource = new WebSocketResource ( socket , function ( request ) {
// TODO: handle different types of requests. for now we only receive
function decryptAttachment ( encrypted ) {
// PUT /messages <base64-encoded encrypted IncomingPushMessageSignal>
return textsecure . protocol . decryptAttachment (
textsecure . protocol . decryptWebsocketMessage ( request . body ) . then ( function ( plaintext ) {
encrypted ,
var proto = textsecure . protobuf . IncomingPushMessageSignal . decode ( plaintext ) ;
attachment . key . toArrayBuffer ( )
// After this point, a) decoding errors are not the server's fault, and
) ;
// b) we should handle them gracefully and tell the user they received an invalid message
}
request . respond ( 200 , 'OK' ) ;
function updateAttachment ( data ) {
return textsecure . protocol . handleIncomingPushMessageProto ( proto ) . then ( function ( decrypted ) {
attachment . data = data ;
// Delivery receipt
}
if ( decrypted === null )
//TODO: Pass to UI
return getAttachment ( ) .
return ;
then ( decryptAttachment ) .
then ( updateAttachment ) ;
} ;
textsecure . processDecrypted = function ( decrypted ) {
// Now that its decrypted, validate the message and clean it up for consumer processing
// Now that its decrypted, validate the message and clean it up for consumer processing
// Note that messages may (generally) only perform one action and we ignore remaining fields
// Note that messages may (generally) only perform one action and we ignore remaining fields
@ -155,16 +159,9 @@ window.textsecure.subscribeToPush = function(message_callback) {
if ( ( decrypted . flags & textsecure . protobuf . PushMessageContent . Flags . END _SESSION )
if ( ( decrypted . flags & textsecure . protobuf . PushMessageContent . Flags . END _SESSION )
== textsecure . protobuf . PushMessageContent . Flags . END _SESSION )
== textsecure . protobuf . PushMessageContent . Flags . END _SESSION )
return ;
return ;
if ( decrypted . flags != 0 )
if ( decrypted . flags != 0 ) {
throw new Error ( "Unknown flags in message" ) ;
throw new Error ( "Unknown flags in message" ) ;
}
var handleAttachment = function ( attachment ) {
return textsecure . api . getAttachment ( attachment . id . toString ( ) ) . then ( function ( encryptedBin ) {
return textsecure . protocol . decryptAttachment ( encryptedBin , attachment . key . toArrayBuffer ( ) ) . then ( function ( decryptedBin ) {
attachment . data = decryptedBin ;
} ) ;
} ) ;
} ;
var promises = [ ] ;
var promises = [ ] ;
@ -172,32 +169,38 @@ window.textsecure.subscribeToPush = function(message_callback) {
decrypted . group . id = getString ( decrypted . group . id ) ;
decrypted . group . id = getString ( decrypted . group . id ) ;
var existingGroup = textsecure . storage . groups . getNumbers ( decrypted . group . id ) ;
var existingGroup = textsecure . storage . groups . getNumbers ( decrypted . group . id ) ;
if ( existingGroup === undefined ) {
if ( existingGroup === undefined ) {
if ( decrypted . group . type != textsecure . protobuf . PushMessageContent . GroupContext . Type . UPDATE )
if ( decrypted . group . type != textsecure . protobuf . PushMessageContent . GroupContext . Type . UPDATE ) {
throw new Error ( "Got message for unknown group" ) ;
throw new Error ( "Got message for unknown group" ) ;
}
textsecure . storage . groups . createNewGroup ( decrypted . group . members , decrypted . group . id ) ;
textsecure . storage . groups . createNewGroup ( decrypted . group . members , decrypted . group . id ) ;
} else {
} else {
var fromIndex = existingGroup . indexOf ( proto . source ) ;
var fromIndex = existingGroup . indexOf ( proto . source ) ;
if ( fromIndex < 0 ) //TODO: This could be indication of a race...
if ( fromIndex < 0 ) {
//TODO: This could be indication of a race...
throw new Error ( "Sender was not a member of the group they were sending from" ) ;
throw new Error ( "Sender was not a member of the group they were sending from" ) ;
}
switch ( decrypted . group . type ) {
switch ( decrypted . group . type ) {
case textsecure . protobuf . PushMessageContent . GroupContext . Type . UPDATE :
case textsecure . protobuf . PushMessageContent . GroupContext . Type . UPDATE :
if ( decrypted . group . avatar !== null )
if ( decrypted . group . avatar !== null )
promises . push ( handleAttachment ( decrypted . group . avatar ) ) ;
promises . push ( handleAttachment ( decrypted . group . avatar ) ) ;
if ( existingGroup . filter ( function ( number ) { decrypted . group . members . indexOf ( number ) < 0 } ) . length != 0 )
if ( existingGroup . filter ( function ( number ) { decrypted . group . members . indexOf ( number ) < 0 } ) . length != 0 ) {
throw new Error ( "Attempted to remove numbers from group with an UPDATE" ) ;
throw new Error ( "Attempted to remove numbers from group with an UPDATE" ) ;
}
decrypted . group . added = decrypted . group . members . filter ( function ( number ) { return existingGroup . indexOf ( number ) < 0 ; } ) ;
decrypted . group . added = decrypted . group . members . filter ( function ( number ) { return existingGroup . indexOf ( number ) < 0 ; } ) ;
var newGroup = textsecure . storage . groups . addNumbers ( decrypted . group . id , decrypted . group . added ) ;
var newGroup = textsecure . storage . groups . addNumbers ( decrypted . group . id , decrypted . group . added ) ;
if ( newGroup . length != decrypted . group . members . length ||
if ( newGroup . length != decrypted . group . members . length ||
newGroup . filter ( function ( number ) { return decrypted . group . members . indexOf ( number ) < 0 ; } ) . length != 0 )
newGroup . filter ( function ( number ) { return decrypted . group . members . indexOf ( number ) < 0 ; } ) . length != 0 ) {
throw new Error ( "Error calculating group member difference" ) ;
throw new Error ( "Error calculating group member difference" ) ;
}
//TODO: Also follow this path if avatar + name haven't changed (ie we should start storing those)
//TODO: Also follow this path if avatar + name haven't changed (ie we should start storing those)
if ( decrypted . group . avatar === null && decrypted . group . added . length == 0 && decrypted . group . name === null )
if ( decrypted . group . avatar === null && decrypted . group . added . length == 0 && decrypted . group . name === null ) {
return ;
return ;
}
//TODO: Strictly verify all numbers (ie dont let verifyNumber do any user-magic tweaking)
//TODO: Strictly verify all numbers (ie dont let verifyNumber do any user-magic tweaking)
@ -222,19 +225,13 @@ window.textsecure.subscribeToPush = function(message_callback) {
}
}
}
}
for ( var i in decrypted . attachments )
for ( var i in decrypted . attachments ) {
promises . push ( handleAttachment ( decrypted . attachments [ i ] ) ) ;
promises . push ( handleAttachment ( decrypted . attachments [ i ] ) ) ;
}
return Promise . all ( promises ) . then ( function ( ) {
return Promise . all ( promises ) . then ( function ( ) {
message _callback ( { pushMessage : proto , message : decrypted } ) ;
return decrypted ;
} ) ;
} )
} ) . catch ( function ( e ) {
// TODO: Show "Invalid message" messages?
console . log ( "Error handling incoming message: " ) ;
console . log ( e ) ;
} ) ;
} ) ;
} ) ;
}
} ;
window . textsecure . registerSingleDevice = function ( number , verificationCode , stepDone ) {
window . textsecure . registerSingleDevice = function ( number , verificationCode , stepDone ) {
var signalingKey = textsecure . crypto . getRandomBytes ( 32 + 20 ) ;
var signalingKey = textsecure . crypto . getRandomBytes ( 32 + 20 ) ;