2014-05-14 18:58:12 +00:00
/ * v i m : t s = 4 : s w = 4
*
2014-05-04 06:34:13 +00:00
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
2014-04-04 06:33:02 +00:00
/* START CRAP TO BE DELETED */
2014-01-12 07:32:13 +00:00
//TODO: Stolen from MDN (copyright...)
function b64ToUint6 ( nChr ) {
return nChr > 64 && nChr < 91 ?
nChr - 65
: nChr > 96 && nChr < 123 ?
nChr - 71
: nChr > 47 && nChr < 58 ?
nChr + 4
: nChr === 43 ?
62
: nChr === 47 ?
63
:
0 ;
}
function base64DecToArr ( sBase64 , nBlocksSize ) {
var
sB64Enc = sBase64 . replace ( /[^A-Za-z0-9\+\/]/g , "" ) , nInLen = sB64Enc . length ,
2014-01-22 06:23:41 +00:00
nOutLen = nBlocksSize ? Math . ceil ( ( nInLen * 3 + 1 >> 2 ) / nBlocksSize ) * nBlocksSize : nInLen * 3 + 1 >> 2 ;
var aBBytes = new ArrayBuffer ( nOutLen ) ;
var taBytes = new Uint8Array ( aBBytes ) ;
2014-01-12 07:32:13 +00:00
for ( var nMod3 , nMod4 , nUint24 = 0 , nOutIdx = 0 , nInIdx = 0 ; nInIdx < nInLen ; nInIdx ++ ) {
nMod4 = nInIdx & 3 ;
nUint24 |= b64ToUint6 ( sB64Enc . charCodeAt ( nInIdx ) ) << 18 - 6 * nMod4 ;
if ( nMod4 === 3 || nInLen - nInIdx === 1 ) {
for ( nMod3 = 0 ; nMod3 < 3 && nOutIdx < nOutLen ; nMod3 ++ , nOutIdx ++ ) {
taBytes [ nOutIdx ] = nUint24 >>> ( 16 >>> nMod3 & 24 ) & 255 ;
}
nUint24 = 0 ;
}
}
2014-01-22 06:23:41 +00:00
return aBBytes ;
2014-01-12 07:32:13 +00:00
}
2014-03-05 01:31:15 +00:00
/* Base64 string to array encoding */
function uint6ToB64 ( nUint6 ) {
return nUint6 < 26 ?
nUint6 + 65
: nUint6 < 52 ?
nUint6 + 71
: nUint6 < 62 ?
nUint6 - 4
: nUint6 === 62 ?
43
: nUint6 === 63 ?
47
:
65 ;
}
function base64EncArr ( aBytes ) {
var nMod3 , sB64Enc = "" ;
for ( var nLen = aBytes . length , nUint24 = 0 , nIdx = 0 ; nIdx < nLen ; nIdx ++ ) {
nMod3 = nIdx % 3 ;
//if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; }
nUint24 |= aBytes [ nIdx ] << ( 16 >>> nMod3 & 24 ) ;
if ( nMod3 === 2 || aBytes . length - nIdx === 1 ) {
sB64Enc += String . fromCharCode ( uint6ToB64 ( nUint24 >>> 18 & 63 ) , uint6ToB64 ( nUint24 >>> 12 & 63 ) , uint6ToB64 ( nUint24 >>> 6 & 63 ) , uint6ToB64 ( nUint24 & 63 ) ) ;
nUint24 = 0 ;
}
}
return sB64Enc . replace ( /A(?=A$|$)/g , "=" ) ;
}
2014-04-04 06:33:02 +00:00
/* END CRAP TO BE DELETED */
2014-05-17 05:53:58 +00:00
window . textsecure = window . textsecure || { } ;
2014-01-12 07:32:13 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * Type conversion utilities * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2014-01-15 07:46:05 +00:00
// Strings/arrays
2014-03-06 21:44:59 +00:00
//TODO: Throw all this shit in favor of consistent types
2014-01-12 14:07:13 +00:00
var StaticByteBufferProto = new dcodeIO . ByteBuffer ( ) . _ _proto _ _ ;
2014-01-22 06:23:41 +00:00
var StaticArrayBufferProto = new ArrayBuffer ( ) . _ _proto _ _ ;
2014-03-06 18:18:11 +00:00
var StaticUint8ArrayProto = new Uint8Array ( ) . _ _proto _ _ ;
var StaticWordArrayProto = CryptoJS . lib . WordArray . create ( '' ) . _ _proto _ _ ;
2014-01-12 07:32:13 +00:00
function getString ( thing ) {
2014-03-06 18:18:11 +00:00
if ( thing === Object ( thing ) ) {
if ( thing . _ _proto _ _ == StaticUint8ArrayProto )
return String . fromCharCode . apply ( null , thing ) ;
if ( thing . _ _proto _ _ == StaticArrayBufferProto )
return getString ( new Uint8Array ( thing ) ) ;
if ( thing . _ _proto _ _ == StaticByteBufferProto )
return thing . toString ( "binary" ) ;
if ( thing . _ _proto _ _ == StaticWordArrayProto )
return thing . toString ( CryptoJS . enc . Latin1 ) ;
}
2014-01-12 07:32:13 +00:00
return thing ;
}
2014-03-05 01:31:15 +00:00
function getStringable ( thing ) {
2014-03-10 22:47:37 +00:00
return ( typeof thing == "string" || typeof thing == "number" || typeof thing == "boolean" ||
2014-03-06 18:18:11 +00:00
( thing === Object ( thing ) &&
( thing . _ _proto _ _ == StaticArrayBufferProto ||
thing . _ _proto _ _ == StaticUint8ArrayProto ||
thing . _ _proto _ _ == StaticByteBufferProto ||
thing . _ _proto _ _ == StaticWordArrayProto ) ) ) ;
2014-03-05 01:31:15 +00:00
}
function toArrayBuffer ( thing ) {
2014-03-06 18:18:11 +00:00
//TODO: Optimize this for specific cases
2014-03-05 01:31:15 +00:00
if ( thing === undefined )
return undefined ;
2014-03-06 21:44:59 +00:00
if ( thing === Object ( thing ) && thing . _ _proto _ _ == StaticArrayBufferProto )
return thing ;
2014-03-10 00:32:00 +00:00
if ( thing instanceof Array ) {
// Assuming Uint16Array from curve25519
var res = new ArrayBuffer ( thing . length * 2 ) ;
var uint = new Uint16Array ( res ) ;
for ( var i = 0 ; i < thing . length ; i ++ )
uint [ i ] = thing [ i ] ;
return res ;
}
2014-03-05 01:31:15 +00:00
if ( ! getStringable ( thing ) )
2014-05-03 03:16:36 +00:00
throw new Error ( "Tried to convert a non-stringable thing of type " + typeof thing + " to an array buffer" ) ;
2014-03-05 01:31:15 +00:00
var str = getString ( thing ) ;
var res = new ArrayBuffer ( str . length ) ;
var uint = new Uint8Array ( res ) ;
for ( var i = 0 ; i < str . length ; i ++ )
uint [ i ] = str . charCodeAt ( i ) ;
return res ;
}
2014-01-22 06:23:41 +00:00
function base64ToArrayBuffer ( string ) {
2014-01-12 07:32:13 +00:00
return base64DecToArr ( string ) ;
}
2014-05-17 05:53:58 +00:00
// Protobuf decoding
2014-01-15 07:46:05 +00:00
//TODO: throw on missing fields everywhere
2014-05-21 19:04:05 +00:00
window . textsecure . protos = function ( ) {
var self = { } ;
2014-01-15 07:46:05 +00:00
2014-05-21 19:04:05 +00:00
self . IncomingPushMessageProtobuf = dcodeIO . ProtoBuf . loadProtoFile ( "protos/IncomingPushMessageSignal.proto" ) . build ( "textsecure.IncomingPushMessageSignal" ) ;
self . decodeIncomingPushMessageProtobuf = function ( string ) {
2014-05-25 23:48:41 +00:00
return self . IncomingPushMessageProtobuf . decode ( btoa ( string ) ) ;
2014-05-21 19:04:05 +00:00
}
2014-01-15 07:46:05 +00:00
2014-05-21 19:04:05 +00:00
self . PushMessageContentProtobuf = dcodeIO . ProtoBuf . loadProtoFile ( "protos/IncomingPushMessageSignal.proto" ) . build ( "textsecure.PushMessageContent" ) ;
self . decodePushMessageContentProtobuf = function ( string ) {
2014-05-25 23:48:41 +00:00
return self . PushMessageContentProtobuf . decode ( btoa ( string ) ) ;
2014-05-21 19:04:05 +00:00
}
2014-01-15 07:46:05 +00:00
2014-05-21 19:04:05 +00:00
self . WhisperMessageProtobuf = dcodeIO . ProtoBuf . loadProtoFile ( "protos/WhisperTextProtocol.proto" ) . build ( "textsecure.WhisperMessage" ) ;
self . decodeWhisperMessageProtobuf = function ( string ) {
2014-05-25 23:48:41 +00:00
return self . WhisperMessageProtobuf . decode ( btoa ( string ) ) ;
2014-05-21 19:04:05 +00:00
}
2014-01-15 07:46:05 +00:00
2014-05-21 19:04:05 +00:00
self . PreKeyWhisperMessageProtobuf = dcodeIO . ProtoBuf . loadProtoFile ( "protos/WhisperTextProtocol.proto" ) . build ( "textsecure.PreKeyWhisperMessage" ) ;
self . decodePreKeyWhisperMessageProtobuf = function ( string ) {
2014-05-25 23:48:41 +00:00
return self . PreKeyWhisperMessageProtobuf . decode ( btoa ( string ) ) ;
2014-05-21 19:04:05 +00:00
}
self . KeyExchangeMessageProtobuf = dcodeIO . ProtoBuf . loadProtoFile ( "protos/WhisperTextProtocol.proto" ) . build ( "textsecure.KeyExchangeMessage" ) ;
self . decodeKeyExchangeMessageProtobuf = function ( string ) {
2014-05-25 23:48:41 +00:00
return self . KeyExchangeMessageProtobuf . decode ( btoa ( string ) ) ;
2014-05-21 19:04:05 +00:00
}
return self ;
} ( ) ;
2014-01-12 07:32:13 +00:00
2014-05-25 22:09:07 +00:00
// Number formatting utils
window . textsecure . utils = function ( ) {
var self = { } ;
2014-05-25 22:45:22 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * Number conversion / checking stuff * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2014-05-25 22:09:07 +00:00
function isNumeric ( string ) {
return string . replace ( /\D/g , '' ) === string ;
}
function splitPrefixedNumber ( number ) {
// number == "+CCNumber"
return [ number . substr ( 1 , 1 ) , number . substr ( 2 ) ] ; //XXX
}
2014-05-27 18:40:39 +00:00
function validateNumber ( number , countryCode ) {
return isNumeric ( number ) && number . length > 3 && number . length < 11 ; //XXX
2014-05-25 22:09:07 +00:00
}
2014-05-27 18:40:39 +00:00
function validateCountryCode ( countryCode ) {
return isNumeric ( countryCode ) && countryCode . length < 4 && countryCode . length > 0 ;
2014-05-25 22:09:07 +00:00
}
self . verifyNumber = function ( number , countryCode ) {
2014-05-27 18:40:39 +00:00
//XXX: All verifyNumber stuff needs to match the server-side verification
2014-05-25 22:09:07 +00:00
var countryCodeValid = true ;
var numberValid = true ;
2014-05-27 18:40:39 +00:00
if ( number . substr ( 0 , 1 ) == '+' ) {
if ( countryCode === undefined ) {
2014-05-25 22:09:07 +00:00
var numberCCPair = splitPrefixedNumber ( number ) ;
2014-05-27 18:40:39 +00:00
if ( numberCCPair != null ) {
countryCode = numberCCPair [ 0 ] ;
number = numberCCPair [ 1 ] ;
} else
numberValid = false ;
} else
numberValid = false ;
} else if ( countryCode === undefined )
numberValid = false ;
2014-05-25 22:09:07 +00:00
2014-05-27 18:40:39 +00:00
if ( numberValid && ! validateNumber ( number , countryCode ) )
2014-05-25 22:09:07 +00:00
numberValid = false ;
2014-05-27 18:40:39 +00:00
if ( countryCode !== undefined )
countryCodeValid = validateCountryCode ( countryCode ) ;
2014-05-25 22:09:07 +00:00
if ( ! countryCodeValid || ! numberValid )
throw { countryCodeValid : countryCodeValid , numberValid : numberValid } ;
2014-01-12 07:32:13 +00:00
2014-05-27 18:40:39 +00:00
return '+' + countryCode + number ;
2014-05-25 22:09:07 +00:00
}
2014-05-25 22:45:22 +00:00
self . unencodeNumber = function ( number ) {
2014-05-31 17:28:46 +00:00
return number . split ( "." ) ;
2014-05-25 22:09:07 +00:00
}
2014-05-25 22:45:22 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * JSON ' ing Utilities * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * /
2014-05-21 19:04:05 +00:00
function ensureStringed ( thing ) {
if ( getStringable ( thing ) )
return getString ( thing ) ;
else if ( thing instanceof Array ) {
var res = [ ] ;
for ( var i = 0 ; i < thing . length ; i ++ )
res [ i ] = ensureStringed ( thing [ i ] ) ;
return res ;
} else if ( thing === Object ( thing ) ) {
var res = { } ;
for ( key in thing )
res [ key ] = ensureStringed ( thing [ key ] ) ;
return res ;
}
throw new Error ( "unsure of how to jsonify object of type " + typeof thing ) ;
}
2014-05-25 22:45:22 +00:00
self . jsonThing = function ( thing ) {
2014-05-21 19:04:05 +00:00
return JSON . stringify ( ensureStringed ( thing ) ) ;
}
2014-05-25 22:45:22 +00:00
return self ;
} ( ) ;
2014-05-27 19:29:44 +00:00
window . textsecure . throwHumanError = function ( error , type , humanError ) {
var e = new Error ( error ) ;
if ( type !== undefined )
e . name = type ;
e . humanError = humanError ;
throw e ;
}
2014-05-25 22:45:22 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * Utilities to store data in local storage * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
window . textsecure . storage = function ( ) {
var self = { } ;
2014-05-17 04:54:12 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * Base Storage Routines * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2014-05-17 05:53:58 +00:00
self . putEncrypted = function ( key , value ) {
2014-05-17 04:54:12 +00:00
//TODO
if ( value === undefined )
throw new Error ( "Tried to store undefined" ) ;
2014-05-25 22:45:22 +00:00
localStorage . setItem ( "e" + key , textsecure . utils . jsonThing ( value ) ) ;
2014-05-17 04:54:12 +00:00
}
2014-01-17 06:08:33 +00:00
2014-05-17 05:53:58 +00:00
self . getEncrypted = function ( key , defaultValue ) {
2014-01-10 07:48:05 +00:00
//TODO
2014-05-17 04:54:12 +00:00
var value = localStorage . getItem ( "e" + key ) ;
if ( value === null )
return defaultValue ;
return JSON . parse ( value ) ;
}
2014-01-10 07:48:05 +00:00
2014-05-17 05:53:58 +00:00
self . removeEncrypted = function ( key ) {
2014-05-17 04:54:12 +00:00
localStorage . removeItem ( "e" + key ) ;
}
2014-01-10 07:48:05 +00:00
2014-05-17 05:53:58 +00:00
self . putUnencrypted = function ( key , value ) {
2014-05-17 04:54:12 +00:00
if ( value === undefined )
throw new Error ( "Tried to store undefined" ) ;
2014-05-25 22:45:22 +00:00
localStorage . setItem ( "u" + key , textsecure . utils . jsonThing ( value ) ) ;
2014-05-17 04:54:12 +00:00
}
2014-01-15 07:46:05 +00:00
2014-05-17 05:53:58 +00:00
self . getUnencrypted = function ( key , defaultValue ) {
2014-05-17 04:54:12 +00:00
var value = localStorage . getItem ( "u" + key ) ;
if ( value === null )
return defaultValue ;
return JSON . parse ( value ) ;
}
2014-01-10 07:48:05 +00:00
2014-05-17 05:53:58 +00:00
self . removeUnencrypted = function ( key ) {
2014-05-17 04:54:12 +00:00
localStorage . removeItem ( "u" + key ) ;
}
2014-01-10 07:48:05 +00:00
2014-05-17 04:54:12 +00:00
/ * * * * * * * * * * * * * * * * * * * * * *
* * * Device Storage * * *
* * * * * * * * * * * * * * * * * * * * * * /
2014-05-17 05:53:58 +00:00
self . devices = function ( ) {
var self = { } ;
2014-05-27 23:14:16 +00:00
self . saveDeviceObject = function ( deviceObject ) {
2014-05-31 17:28:46 +00:00
if ( deviceObject . identityKey === undefined || deviceObject . registrationId === undefined || deviceObject . encodedNumber === undefined )
throw new Error ( "Tried to store invalid deviceObject" ) ;
var number = textsecure . utils . unencodeNumber ( deviceObject . encodedNumber ) [ 0 ] ;
2014-05-27 23:14:16 +00:00
var map = textsecure . storage . getEncrypted ( "devices" + number ) ;
2014-05-17 04:54:12 +00:00
2014-05-27 23:14:16 +00:00
if ( map === undefined )
map = { devices : [ deviceObject ] , identityKey : deviceObject . identityKey } ;
else if ( map . identityKey != getString ( deviceObject . identityKey ) )
throw new Error ( "Identity key changed" ) ;
2014-05-31 17:28:46 +00:00
else {
var updated = false ;
for ( i in map . devices ) {
if ( map . devices [ i ] . encodedNumber == deviceObject . encodedNumber ) {
map . devices [ i ] = deviceObject ;
updated = true ;
}
}
if ( ! updated )
map . devices . push ( deviceObject ) ;
}
2014-05-17 04:54:12 +00:00
2014-05-27 23:14:16 +00:00
textsecure . storage . putEncrypted ( "devices" + number , map ) ;
2014-05-17 04:54:12 +00:00
}
2014-05-27 23:14:16 +00:00
self . getDeviceObjectsForNumber = function ( number ) {
var map = textsecure . storage . getEncrypted ( "devices" + number ) ;
return map === undefined ? [ ] : map . devices ;
2014-05-17 05:53:58 +00:00
}
2014-05-31 17:28:46 +00:00
self . getDeviceObject = function ( encodedNumber ) {
var number = textsecure . utils . unencodeNumber ( encodedNumber ) ;
var devices = self . getDeviceObjectsForNumber ( number [ 0 ] ) ;
if ( devices === undefined )
return undefined ;
for ( i in devices )
if ( devices [ i ] . encodedNumber == encodedNumber )
return devices [ i ] ;
return undefined ;
}
2014-05-27 23:14:16 +00:00
self . removeDeviceIdsForNumber = function ( number , deviceIdsToRemove ) {
var map = textsecure . storage . getEncrypted ( "devices" + number ) ;
if ( map === undefined )
throw new Error ( "Tried to remove device for unknown number" ) ;
var newDevices = [ ] ;
var devicesRemoved = 0 ;
2014-05-28 02:16:45 +00:00
for ( i in map . devices ) {
2014-05-27 23:14:16 +00:00
var keep = true ;
2014-06-01 17:39:35 +00:00
for ( j in deviceIdsToRemove )
if ( map . devices [ i ] . encodedNumber == number + "." + deviceIdsToRemove [ j ] )
2014-05-27 23:14:16 +00:00
keep = false ;
if ( keep )
2014-05-28 02:16:45 +00:00
newDevices . push ( map . devices [ i ] ) ;
2014-05-27 23:14:16 +00:00
else
devicesRemoved ++ ;
2014-05-17 04:54:12 +00:00
}
2014-05-27 23:14:16 +00:00
if ( devicesRemoved != deviceIdsToRemove . length )
throw new Error ( "Tried to remove unknown device" ) ;
2014-05-17 04:54:12 +00:00
}
2014-05-17 05:53:58 +00:00
return self ;
} ( ) ;
return self ;
} ( ) ;
2014-01-15 07:46:05 +00:00
2014-01-22 06:23:41 +00:00
/ * * * * * * * * * * * * * * * * * * * * * *
* * * NaCL Interface * * *
* * * * * * * * * * * * * * * * * * * * * * /
2014-05-17 05:53:58 +00:00
window . textsecure . nacl = function ( ) {
var self = { } ;
self . USE _NACL = false ;
var onLoadCallbacks = [ ] ;
var naclLoaded = 0 ;
self . registerOnLoadFunction = function ( func ) {
if ( naclLoaded || ! self . USE _NACL ) {
func ( ) ;
return ;
}
onLoadCallbacks [ onLoadCallbacks . length ] = func ;
2014-03-10 00:32:00 +00:00
}
2014-01-22 06:23:41 +00:00
2014-05-17 05:53:58 +00:00
var naclMessageNextId = 0 ;
var naclMessageIdCallbackMap = { } ;
window . moduleDidLoad = function ( ) {
common . hideModule ( ) ;
naclLoaded = 1 ;
for ( var i = 0 ; i < onLoadCallbacks . length ; i ++ )
onLoadCallbacks [ i ] ( ) ;
onLoadCallbacks = [ ] ;
}
2014-01-22 06:23:41 +00:00
2014-05-17 05:53:58 +00:00
window . handleMessage = function ( message ) {
naclMessageIdCallbackMap [ message . data . call _id ] ( message . data ) ;
}
2014-01-22 06:23:41 +00:00
2014-05-17 05:53:58 +00:00
self . postNaclMessage = function ( message ) {
if ( ! self . USE _NACL )
throw new Error ( "Attempted to make NaCL call with !USE_NACL?" ) ;
2014-03-10 00:32:00 +00:00
2014-05-17 05:53:58 +00:00
return new Promise ( function ( resolve ) {
naclMessageIdCallbackMap [ naclMessageNextId ] = resolve ;
message . call _id = naclMessageNextId ++ ;
2014-01-15 07:46:05 +00:00
2014-05-17 05:53:58 +00:00
common . naclModule . postMessage ( message ) ;
} ) ;
}
return self ;
} ( ) ;
2014-01-10 07:48:05 +00:00
2014-05-17 05:53:58 +00:00
//TODO: Some kind of textsecure.init(use_nacl)
window . textsecure . registerOnLoadFunction = window . textsecure . nacl . registerOnLoadFunction ;
2014-06-01 17:39:35 +00:00
window . textsecure . replay = function ( ) {
var self = { } ;
self . REPLAY _FUNCS = {
SEND _MESSAGE : 1 ,
INIT _SESSION : 2 ,
}
var functions = { } ;
self . registerReplayFunction = function ( func , functionCode ) {
functions [ functionCode ] = func ;
}
self . replayError = function ( replayData ) {
var args = Array . prototype . slice . call ( arguments ) ;
args . shift ( ) ;
args = replayData . args . concat ( args ) ;
functions [ replayData . replayFunction ] . apply ( window , args ) ;
}
self . createReplayableError = function ( shortMsg , longMsg , replayFunction , args ) {
var e = new Error ( shortMsg ) ;
e . name = "ReplayableError" ;
e . humanError = e . longMessage = longMsg ;
e . replayData = { replayFunction : replayFunction , args : args } ;
e . replay = function ( ) {
self . replayError ( e . replayData ) ;
}
return e ;
}
return self ;
} ( ) ;
2014-05-17 05:53:58 +00:00
// message_callback({message: decryptedMessage, pushMessage: server-providedPushMessage})
window . textsecure . subscribeToPush = function ( ) {
var subscribeToPushMessageSemaphore = 0 ;
return function ( message _callback ) {
subscribeToPushMessageSemaphore ++ ;
if ( subscribeToPushMessageSemaphore <= 0 )
2014-03-25 19:27:19 +00:00
return ;
2014-05-17 20:56:08 +00:00
var socket = textsecure . api . getWebsocket ( ) ;
2014-05-17 05:53:58 +00:00
var pingInterval ;
//TODO: GUI
socket . onerror = function ( socketEvent ) {
console . log ( 'Server is down :(' ) ;
clearInterval ( pingInterval ) ;
subscribeToPushMessageSemaphore -- ;
2014-05-17 21:15:06 +00:00
setTimeout ( function ( ) { textsecure . subscribeToPush ( message _callback ) ; } , 60000 ) ;
2014-05-17 05:53:58 +00:00
} ;
socket . onclose = function ( socketEvent ) {
console . log ( 'Server closed :(' ) ;
clearInterval ( pingInterval ) ;
subscribeToPushMessageSemaphore -- ;
2014-05-17 21:15:06 +00:00
setTimeout ( function ( ) { textsecure . subscribeToPush ( message _callback ) ; } , 60000 ) ;
2014-05-17 05:53:58 +00:00
} ;
socket . onopen = function ( socketEvent ) {
console . log ( 'Connected to server!' ) ;
pingInterval = setInterval ( function ( ) { console . log ( "Sending server ping message." ) ; socket . send ( JSON . stringify ( { type : 2 } ) ) ; } , 30000 ) ;
} ;
socket . onmessage = function ( response ) {
try {
var message = JSON . parse ( response . data ) ;
} catch ( e ) {
console . log ( 'Error parsing server JSON message: ' + response . responseBody . split ( "|" ) [ 1 ] ) ;
return ;
}
if ( message . type == 3 ) {
console . log ( "Got pong message" ) ;
} else if ( message . type === undefined && message . id !== undefined ) {
textsecure . crypto . decryptWebsocketMessage ( message . message ) . then ( function ( plaintext ) {
2014-05-21 19:04:05 +00:00
var proto = textsecure . protos . decodeIncomingPushMessageProtobuf ( getString ( plaintext ) ) ;
2014-05-17 05:53:58 +00:00
// 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
console . log ( "Successfully decoded message with id: " + message . id ) ;
socket . send ( JSON . stringify ( { type : 1 , id : message . id } ) ) ;
return textsecure . crypto . handleIncomingPushMessageProto ( proto ) . then ( function ( decrypted ) {
var handleAttachment = function ( attachment ) {
return textsecure . api . getAttachment ( attachment . id ) . then ( function ( encryptedBin ) {
return textsecure . crypto . decryptAttachment ( encryptedBin , toArrayBuffer ( attachment . key ) ) . then ( function ( decryptedBin ) {
attachment . decrypted = decryptedBin ;
} ) ;
2014-05-15 04:26:37 +00:00
} ) ;
2014-05-17 05:53:58 +00:00
} ;
var promises = [ ] ;
2014-05-31 17:33:41 +00:00
for ( var i = 0 ; i < decrypted . attachments . length ; i ++ )
promises [ i ] = handleAttachment ( decrypted . attachments [ i ] ) ;
2014-05-19 07:06:28 +00:00
return Promise . all ( promises ) . then ( function ( ) {
2014-05-25 22:16:09 +00:00
message _callback ( { pushMessage : proto , message : decrypted } ) ;
2014-05-19 07:06:28 +00:00
} ) ;
2014-05-17 05:53:58 +00:00
} )
} ) . catch ( function ( e ) {
2014-05-27 19:29:44 +00:00
// TODO: Show "Invalid message" messages?
2014-05-17 05:53:58 +00:00
console . log ( "Error handling incoming message: " ) ;
console . log ( e ) ;
} ) ;
}
} ;
}
} ( ) ;
// sendMessage(numbers = [], message = PushMessageContentProto, callback(success/failure map))
window . textsecure . sendMessage = function ( ) {
2014-05-27 23:14:16 +00:00
function getKeysForNumber ( number , updateDevices ) {
2014-05-17 05:53:58 +00:00
return textsecure . api . getKeysForNumber ( number ) . then ( function ( response ) {
2014-05-27 23:14:16 +00:00
var identityKey = getString ( response [ 0 ] . identityKey ) ;
2014-05-28 02:16:45 +00:00
for ( i in response )
if ( getString ( response [ i ] . identityKey ) != identityKey )
2014-06-01 17:39:35 +00:00
throw new Error ( "Identity key not consistent" ) ;
2014-05-27 23:14:16 +00:00
2014-05-28 02:16:45 +00:00
for ( i in response ) {
2014-05-27 23:14:16 +00:00
var updateDevice = ( updateDevices === undefined ) ;
if ( ! updateDevice )
2014-06-01 17:39:35 +00:00
for ( j in updateDevices )
if ( updateDevices [ j ] == response [ i ] . deviceId )
2014-05-27 23:14:16 +00:00
updateDevice = true ;
if ( updateDevice )
textsecure . storage . devices . saveDeviceObject ( {
2014-05-28 02:16:45 +00:00
encodedNumber : number + "." + response [ i ] . deviceId ,
identityKey : response [ i ] . identityKey ,
publicKey : response [ i ] . publicKey ,
preKeyId : response [ i ] . keyId ,
registrationId : response [ i ] . registrationId
2014-05-27 23:14:16 +00:00
} ) ;
2014-05-17 05:53:58 +00:00
}
} ) ;
}
2014-01-11 07:58:58 +00:00
2014-05-17 05:53:58 +00:00
// success_callback(server success/failure map), error_callback(error_msg)
// message == PushMessageContentProto (NOT STRING)
function sendMessageToDevices ( number , deviceObjectList , message , success _callback , error _callback ) {
var jsonData = [ ] ;
var relay = undefined ;
var promises = [ ] ;
var addEncryptionFor = function ( i ) {
2014-05-28 02:16:45 +00:00
if ( deviceObjectList [ i ] . relay !== undefined ) {
if ( relay === undefined )
relay = deviceObjectList [ i ] . relay ;
else if ( relay != deviceObjectList [ i ] . relay )
return new Promise ( function ( ) { throw new Error ( "Mismatched relays for number " + number ) ; } ) ;
} else {
if ( relay === undefined )
relay = "" ;
else if ( relay != "" )
return new Promise ( function ( ) { throw new Error ( "Mismatched relays for number " + number ) ; } ) ;
}
return textsecure . crypto . encryptMessageFor ( deviceObjectList [ i ] , message ) . then ( function ( encryptedMsg ) {
jsonData [ i ] = {
type : encryptedMsg . type ,
2014-05-31 17:28:46 +00:00
destination : number ,
destinationDeviceId : textsecure . utils . unencodeNumber ( deviceObjectList [ i ] . encodedNumber ) [ 1 ] ,
2014-05-28 02:16:45 +00:00
destinationRegistrationId : deviceObjectList [ i ] . registrationId ,
body : encryptedMsg . body ,
timestamp : new Date ( ) . getTime ( )
} ;
if ( deviceObjectList [ i ] . relay !== undefined )
jsonData [ i ] . relay = deviceObjectList [ i ] . relay ;
2014-05-13 19:15:45 +00:00
} ) ;
}
2014-05-27 19:29:44 +00:00
2014-05-17 05:53:58 +00:00
for ( var i = 0 ; i < deviceObjectList . length ; i ++ )
promises [ i ] = addEncryptionFor ( i ) ;
return Promise . all ( promises ) . then ( function ( ) {
return textsecure . api . sendMessages ( number , jsonData ) ;
2014-03-25 19:27:19 +00:00
} ) ;
}
2014-01-10 07:48:05 +00:00
2014-06-01 17:39:35 +00:00
var tryMessageAgain = function ( number , encodedMessage , callback ) {
//TODO: Wipe identity key!
var message = textsecure . protos . decodePushMessageContentProtobuf ( encodedMessage ) ;
textsecure . sendMessage ( [ number ] , message , callback ) ;
}
textsecure . replay . registerReplayFunction ( tryMessageAgain , textsecure . replay . SEND _MESSAGE ) ;
2014-05-17 05:53:58 +00:00
return function ( numbers , message , callback ) {
var numbersCompleted = 0 ;
var errors = [ ] ;
var successfulNumbers = [ ] ;
2014-03-25 19:27:19 +00:00
2014-05-17 05:53:58 +00:00
var numberCompleted = function ( ) {
numbersCompleted ++ ;
if ( numbersCompleted >= numbers . length )
callback ( { success : successfulNumbers , failure : errors } ) ;
}
2014-03-25 19:27:19 +00:00
2014-05-17 05:53:58 +00:00
var registerError = function ( number , message , error ) {
2014-05-27 19:29:44 +00:00
if ( error . humanError )
message = error . humanError ;
2014-05-17 05:53:58 +00:00
errors [ errors . length ] = { number : number , reason : message , error : error } ;
2014-03-25 19:27:19 +00:00
numberCompleted ( ) ;
2014-05-17 05:53:58 +00:00
}
2014-01-13 01:52:58 +00:00
2014-06-01 17:39:35 +00:00
var doSendMessage ;
var reloadDevicesAndSend = function ( number , recurse ) {
return function ( ) {
var devicesForNumber = textsecure . storage . devices . getDeviceObjectsForNumber ( number ) ;
if ( devicesForNumber . length == 0 )
registerError ( number , "Go empty device list when loading device keys" , null ) ;
else
doSendMessage ( number , devicesForNumber , recurse ) ;
}
}
doSendMessage = function ( number , devicesForNumber , recurse ) {
2014-05-17 05:53:58 +00:00
return sendMessageToDevices ( number , devicesForNumber , message ) . then ( function ( result ) {
successfulNumbers [ successfulNumbers . length ] = number ;
numberCompleted ( ) ;
2014-05-13 19:15:45 +00:00
} ) . catch ( function ( error ) {
2014-05-17 05:53:58 +00:00
if ( error instanceof Error && error . name == "HTTPError" && ( error . message == 410 || error . message == 409 ) ) {
2014-06-01 17:39:35 +00:00
if ( ! recurse )
return registerError ( number , "Hit retry limit attempting to reload device list" , error ) ;
if ( error . message == 409 )
textsecure . storage . devices . removeDeviceIdsForNumber ( number , error . response . extraDevices ) ;
2014-05-27 23:14:16 +00:00
var resetDevices = ( ( error . message == 410 ) ? error . response . staleDevices : error . response . missingDevices ) ;
2014-06-01 17:39:35 +00:00
getKeysForNumber ( number , resetDevices )
. then ( reloadDevicesAndSend ( number , false ) )
. catch ( function ( error ) {
if ( error . message !== "Identity key changed" )
registerError ( number , "Failed to reload device keys" , error ) ;
else {
error = textsecure . replay . createReplayableError ( "The destination's identity key has changed" , "The identity of the destination has changed. This may be malicious, or the destination may have simply reinstalled TextSecure." ,
textsecure . replay . SEND _MESSAGE , [ number , getString ( message . encode ( ) ) ] ) ;
registerError ( number , "Identity key changed" , error ) ;
}
} ) ;
2014-05-27 23:14:16 +00:00
} else
registerError ( number , "Failed to create or send message" , error ) ;
2014-05-13 19:15:45 +00:00
} ) ;
2014-05-17 05:53:58 +00:00
}
for ( var i = 0 ; i < numbers . length ; i ++ ) {
var number = numbers [ i ] ;
2014-05-27 23:14:16 +00:00
var devicesForNumber = textsecure . storage . devices . getDeviceObjectsForNumber ( number ) ;
2014-05-17 05:53:58 +00:00
if ( devicesForNumber . length == 0 ) {
2014-06-01 17:39:35 +00:00
getKeysForNumber ( number )
. then ( reloadDevicesAndSend ( number , true ) )
. catch ( function ( error ) {
registerError ( number , "Failed to retreive new device keys for number " + number , error ) ;
} ) ;
2014-05-17 05:53:58 +00:00
} else
2014-06-01 17:39:35 +00:00
doSendMessage ( number , devicesForNumber , true ) ;
2014-05-17 05:53:58 +00:00
}
2014-01-12 07:32:13 +00:00
}
2014-05-17 05:53:58 +00:00
} ( ) ;
2014-01-12 07:32:13 +00:00
2014-05-27 18:40:39 +00:00
window . textsecure . register = function ( ) {
return function ( number , verificationCode , singleDevice , stepDone ) {
var signalingKey = textsecure . crypto . getRandomBytes ( 32 + 20 ) ;
textsecure . storage . putEncrypted ( 'signaling_key' , signalingKey ) ;
var password = btoa ( getString ( textsecure . crypto . getRandomBytes ( 16 ) ) ) ;
password = password . substring ( 0 , password . length - 2 ) ;
textsecure . storage . putEncrypted ( "password" , password ) ;
var registrationId = new Uint16Array ( textsecure . crypto . getRandomBytes ( 2 ) ) [ 0 ] ;
registrationId = registrationId & 0x3fff ;
textsecure . storage . putUnencrypted ( "registrationId" , registrationId ) ;
return textsecure . api . confirmCode ( number , verificationCode , password , signalingKey , registrationId , singleDevice ) . then ( function ( response ) {
if ( singleDevice )
response = 1 ;
var numberId = number + "." + response ;
textsecure . storage . putUnencrypted ( "number_id" , numberId ) ;
stepDone ( 1 ) ;
if ( ! singleDevice ) {
//TODO: Do things???
stepDone ( 2 ) ;
}
return textsecure . crypto . generateKeys ( ) . then ( function ( keys ) {
stepDone ( 3 ) ;
return textsecure . api . registerKeys ( keys ) . then ( function ( ) {
stepDone ( 4 ) ;
} ) ;
} ) ;
} ) ;
}
} ( ) ;