Refactor Server API functions

The details of the server API are now mostly relegated to api.js, and
accessed through the API container object, improving modularity and
readability, and setting us up to derive a FakeAPI for serverless
development.
This commit is contained in:
lilia 2014-03-11 01:21:28 -07:00
parent 288d66b4a3
commit 6934ba0b92
6 changed files with 170 additions and 86 deletions

View file

@ -16,6 +16,7 @@
<script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script> <script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script>
<script type="text/javascript" src="js-deps/ProtoBuf.min.js"></script> <script type="text/javascript" src="js-deps/ProtoBuf.min.js"></script>
<script type="text/javascript" src="js/helpers.js"></script> <script type="text/javascript" src="js/helpers.js"></script>
<script type="text/javascript" src="js/api.js"></script>
<script type="text/javascript" src="js/background.js"></script> <script type="text/javascript" src="js/background.js"></script>
</head> </head>
<body data-name="curve25519" data-tools="pnacl" data-configs="Debug Release" data-path="pnacl/{config}"> <body data-name="curve25519" data-tools="pnacl" data-configs="Debug Release" data-path="pnacl/{config}">

145
js/api.js Normal file
View file

@ -0,0 +1,145 @@
/************************************************
*** Utilities to communicate with the server ***
************************************************/
var URL_BASE = "http://textsecure-test.herokuapp.com";
//var URL_BASE = "https://textsecure-service.whispersystems.org";
var URL_CALLS = {};
URL_CALLS['accounts'] = "/v1/accounts";
URL_CALLS['devices'] = "/v1/devices";
URL_CALLS['keys'] = "/v1/keys";
URL_CALLS['push'] = "/v1/messagesocket";
URL_CALLS['messages'] = "/v1/messages/";
var API = new function() {
/**
* REQUIRED PARAMS:
* call: URL_CALLS entry
* httpType: POST/GET/PUT/etc
* OPTIONAL PARAMS:
* success_callback: function(response object) called on success
* error_callback: function(http status code = -1 or != 200) called on failure
* urlParameters: crap appended to the url (probably including a leading /)
* user: user name to be sent in a basic auth header
* password: password to be sent in a basic auth headerA
* do_auth: alternative to user/password where user/password are figured out automagically
* jsonData: JSON data sent in the request body
*/
this.doAjax = function doAjax(param) {
if (param.urlParameters === undefined)
param.urlParameters = "";
if (param.do_auth) {
param.user = storage.getUnencrypted("number_id");
param.password = storage.getEncrypted("password");
}
$.ajax(URL_BASE + URL_CALLS[param.call] + param.urlParameters, {
type : param.httpType,
data : param.jsonData && jsonThing(param.jsonData),
contentType : 'application/json; charset=utf-8',
dataType : 'json',
beforeSend : function(xhr) {
if (param.user !== undefined &&
param.password !== undefined) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(getString(param.user) + ":" + getString(param.password)));
}
},
success : function(response, textStatus, jqXHR) {
if (param.success_callback !== undefined)
param.success_callback(response);
},
error : function(jqXHR, textStatus, errorThrown) {
var code = jqXHR.status;
if (code == 200) {
// happens sometimes when we get no response
// (TODO: Fix server to return 204? instead)
if (param.success_callback !== undefined)
param.success_callback(null);
return;
}
if (code > 999 || code < 100)
code = -1;
if (param.error_callback !== undefined)
param.error_callback(code);
}
});
};
this.requestVerificationCode = function(number, success_callback, error_callback) {
this.doAjax({
call : 'accounts',
httpType : 'GET',
urlParameters : '/sms/code/' + number,
success_callback : success_callback,
error_callback : error_callback
});
};
this.confirmCode = function(code, number, password,
signaling_key, single_device,
success_callback, error_callback) {
var call = single_device ? 'accounts' : 'devices';
var urlPrefix = single_device ? '/code/' : '/';
API.doAjax({
call : call,
httpType : 'PUT',
urlParameters : urlPrefix + code,
user : number,
password : password,
jsonData : { signalingKey : btoa(getString(signaling_key)),
supportsSms : false,
fetchesMessages : true },
success_callback : success_callback,
error_callback : error_callback
});
};
this.registerKeys = function(keys, success_callback, error_callback) {
this.doAjax({
call : 'keys',
httpType : 'PUT',
do_auth : true,
jsonData : keys,
success_callback : success_callback,
error_callback : error_callback
});
};
this.getKeysForNumber = function(number, success_callback, error_callback) {
this.doAjax({
call : 'keys',
httpType : 'GET',
do_auth : true,
urlParameters : "/" + getNumberFromString(number) + "?multikeys",
success_callback : success_callback,
error_callback : error_callback
});
};
this.sendMessages = function(jsonData, success_callback, error_callback) {
this.doAjax({
call : 'messages',
httpType : 'POST',
do_auth : true,
jsonData : jsonData,
success_callback : success_callback,
error_callback : error_callback
});
};
this.pushMessage = function(messageId) {
this.doAjax({
call : 'push',
httpType : 'PUT',
urlParameters : '/' + message.id,
do_auth : true
});
};
}(); // API

View file

@ -860,65 +860,6 @@ var crypto_tests = {};
}( window.crypto = window.crypto || {}, jQuery )); }( window.crypto = window.crypto || {}, jQuery ));
/************************************************
*** Utilities to communicate with the server ***
************************************************/
var URL_BASE = "http://textsecure-test.herokuapp.com";
var URL_CALLS = {};
URL_CALLS['accounts'] = "/v1/accounts";
URL_CALLS['devices'] = "/v1/devices";
URL_CALLS['keys'] = "/v1/keys";
URL_CALLS['push'] = "/v1/messagesocket";
URL_CALLS['messages'] = "/v1/messages/";
/**
* REQUIRED PARAMS:
* call: URL_CALLS entry
* httpType: POST/GET/PUT/etc
* OPTIONAL PARAMS:
* success_callback: function(response object) called on success
* error_callback: function(http status code = -1 or != 200) called on failure
* urlParameters: crap appended to the url (probably including a leading /)
* user: user name to be sent in a basic auth header
* password: password to be sent in a basic auth headerA
* do_auth: alternative to user/password where user/password are figured out automagically
* jsonData: JSON data sent in the request body
*/
function doAjax(param) {
if (param.urlParameters === undefined)
param.urlParameters = "";
if (param.do_auth) {
param.user = storage.getUnencrypted("number_id");
param.password = storage.getEncrypted("password");
}
$.ajax(URL_BASE + URL_CALLS[param.call] + param.urlParameters, {
type: param.httpType,
data: param.jsonData && jsonThing(param.jsonData),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
beforeSend: function(xhr) {
if (param.user !== undefined && param.password !== undefined)
xhr.setRequestHeader("Authorization", "Basic " + btoa(getString(param.user) + ":" + getString(param.password)));
},
success: function(response, textStatus, jqXHR) {
if (param.success_callback !== undefined)
param.success_callback(response);
},
error: function(jqXHR, textStatus, errorThrown) {
var code = jqXHR.status;
if (code == 200) {// happens sometimes when we get no response (TODO: Fix server to return 204? instead)
if (param.success_callback !== undefined)
param.success_callback(null);
return;
}
if (code > 999 || code < 100)
code = -1;
if (param.error_callback !== undefined)
param.error_callback(code);
},
cache: false
});
}
// message_callback(decoded_protobuf) (use decodeMessage(proto)) // message_callback(decoded_protobuf) (use decodeMessage(proto))
function subscribeToPush(message_callback) { function subscribeToPush(message_callback) {
@ -951,7 +892,7 @@ function subscribeToPush(message_callback) {
// After this point, a) decoding errors are not the server's fault, and // 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 // b) we should handle them gracefully and tell the user they received an invalid message
doAjax({call: 'push', httpType: 'PUT', urlParameters: '/' + message.id, do_auth: true}); API.pushMessage(message.id);
} catch (e) { } catch (e) {
console.log("Error decoding message: " + e); console.log("Error decoding message: " + e);
return; return;
@ -974,8 +915,8 @@ function subscribeToPush(message_callback) {
// success_callback(identity_key), error_callback(error_msg) // success_callback(identity_key), error_callback(error_msg)
function getKeysForNumber(number, success_callback, error_callback) { function getKeysForNumber(number, success_callback, error_callback) {
doAjax({call: 'keys', httpType: 'GET', do_auth: true, urlParameters: "/" + getNumberFromString(number) + "?multikeys", API.getKeysForNumber(number,
success_callback: function(response) { function(response) {
for (var i = 0; i < response.length; i++) { for (var i = 0; i < response.length; i++) {
try { try {
saveDeviceObject({ saveDeviceObject({
@ -990,10 +931,9 @@ function getKeysForNumber(number, success_callback, error_callback) {
} }
} }
success_callback(response[0].identityKey); success_callback(response[0].identityKey);
}, error_callback: function(code) { }, function(code) {
error_callback("Error making HTTP request: " + code); error_callback("Error making HTTP request: " + code);
} });
});
} }
// success_callback(server success/failure map), error_callback(error_msg) // success_callback(server success/failure map), error_callback(error_msg)
@ -1011,8 +951,8 @@ function sendMessageToDevices(deviceObjectList, message, success_callback, error
}; };
//TODO: need to encrypt with session key? //TODO: need to encrypt with session key?
} }
doAjax({call: 'messages', httpType: 'POST', do_auth: true, jsonData: jsonData, API.sendMessages(jsonData,
success_callback: function(result) { function(result) {
if (result.missingDeviceIds.length > 0) { if (result.missingDeviceIds.length > 0) {
var responsesLeft = result.missingDeviceIds.length; var responsesLeft = result.missingDeviceIds.length;
var errorThrown = 0; var errorThrown = 0;
@ -1030,10 +970,10 @@ function sendMessageToDevices(deviceObjectList, message, success_callback, error
} else { } else {
success_callback(result); success_callback(result);
} }
}, error_callback: function(code) { }, function(code) {
error_callback("Failed to conect to data channel: " + code); error_callback("Failed to conect to data channel: " + code);
} }
}); );
} }
// success_callback(success/failure map, see second-to-last line), error_callback(error_msg) // success_callback(success/failure map, see second-to-last line), error_callback(error_msg)

View file

@ -22,7 +22,7 @@ $('#number').on('change', function() {//TODO
$('#number').attr('style', ''); $('#number').attr('style', '');
}); });
var single_device = false; var single_device = true;
var signaling_key = getRandomBytes(32 + 20); var signaling_key = getRandomBytes(32 + 20);
var password = btoa(getRandomBytes(16)); var password = btoa(getRandomBytes(16));
password = password.substring(0, password.length - 2); password = password.substring(0, password.length - 2);
@ -38,12 +38,12 @@ $('#init-go-single-client').click(function() {
single_device = true; single_device = true;
doAjax({call: 'accounts', httpType: 'GET', urlParameters: '/sms/code/' + number, API.requestVerificationCode(number,
success_callback: function(response) { }, function(response) { },
error_callback: function(code) { function(code) {
alert("Failed to send key?" + code); //TODO alert("Failed to send key?" + code); //TODO
} }
}); );
} }
}); });
@ -58,12 +58,8 @@ $('#init-go').click(function() {
$('#verify4done').html(''); $('#verify4done').html('');
$('#verify').show(); $('#verify').show();
var call = single_device ? 'accounts' : 'devices'; API.confirmCode(code, number, password, signaling_key, single_device,
var urlPrefix = single_device ? '/code/' : '/'; function(response) {
doAjax({call: call, httpType: 'PUT', urlParameters: urlPrefix + $('#code').val(), user: number, password: password,
jsonData: {signalingKey: btoa(getString(signaling_key)), supportsSms: false, fetchesMessages: true},
success_callback: function(response) {
if (single_device) if (single_device)
response = 1; response = 1;
var number_id = number + "." + response; var number_id = number + "." + response;
@ -76,16 +72,16 @@ $('#init-go').click(function() {
$('#verify2done').html('done'); $('#verify2done').html('done');
crypto.generateKeys(function(keys) { crypto.generateKeys(function(keys) {
$('#verify3done').html('done'); $('#verify3done').html('done');
doAjax({call: 'keys', httpType: 'PUT', do_auth: true, jsonData: keys, API.registerKeys(keys,
success_callback: function(response) { function(response) {
$('#complete-number').html(number); $('#complete-number').html(number);
$('#verify').hide(); $('#verify').hide();
$('#setup-complete').show(); $('#setup-complete').show();
registrationDone(); registrationDone();
}, error_callback: function(code) { }, function(code) {
alert(code); //TODO alert(code); //TODO
} }
}); );
}); });
} }
@ -102,7 +98,7 @@ $('#init-go').click(function() {
} else { } else {
register_keys_func(); register_keys_func();
} }
}, error_callback: function(code) { }, function(code) {
var error; var error;
switch(code) { switch(code) {
case 403: case 403:
@ -117,7 +113,7 @@ $('#init-go').click(function() {
} }
alert(error); //TODO alert(error); //TODO
} }
}); );
} }
}); });

View file

@ -40,6 +40,7 @@
<script type="text/javascript" src="js-deps/Long.min.js"></script> <script type="text/javascript" src="js-deps/Long.min.js"></script>
<script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script> <script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script>
<script type="text/javascript" src="js-deps/ProtoBuf.min.js"></script> <script type="text/javascript" src="js-deps/ProtoBuf.min.js"></script>
<script type="text/javascript" src="js/api.js"></script>
<script type="text/javascript" src="js/helpers.js"></script> <script type="text/javascript" src="js/helpers.js"></script>
<script type="text/javascript" src="js/options.js"></script> <script type="text/javascript" src="js/options.js"></script>
</body> </body>

View file

@ -29,6 +29,7 @@
<script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script> <script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script>
<script type="text/javascript" src="js-deps/ProtoBuf.min.js"></script> <script type="text/javascript" src="js-deps/ProtoBuf.min.js"></script>
<script type="text/javascript" src="js/helpers.js"></script> <script type="text/javascript" src="js/helpers.js"></script>
<script type="text/javascript" src="js/api.js"></script>
<script type="text/javascript" src="js/popup.js"></script> <script type="text/javascript" src="js/popup.js"></script>
</body> </body>
</html> </html>