var Clay = require('pebble-clay');
var clayConfig = require('./config');
var clay = new Clay(clayConfig);
const jsSHA = require('./sha');
var messageKeys = require('message_keys');
var sid="";
var status;
var retry;
retry = 0;
function dec2hex(s) {
return (s < 15.5 ? '0' : '') + Math.round(s).toString(16);
}
function hex2dec(s) {
return parseInt(s, 16);
}
function base32tohex(base32) {
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
var bits = "";
var hex = "";
for (var i = 0; i < base32.length; i++) {
var val = base32chars.indexOf(base32.charAt(i).toUpperCase());
bits += leftpad(val.toString(2), 5, '0');
}
//console.log('-- bits : ' + bits)
for (var i = 0; i + 4 <= bits.length; i += 4) {
var chunk = bits.substring(i, i + 4);
hex = hex + parseInt(chunk, 2).toString(16);
}
return hex;
}
function leftpad(str, len, pad) {
if (len + 1 >= str.length) {
str = Array(len + 1 - str.length).join(pad) + str;
}
return str;
}
function getOtp() {
send_message_to_watch("Authenticating..")
key = base32tohex(JSON.parse(localStorage.getItem('clay-settings')).OTP_seed)
//console.log('-- seed:' + JSON.parse(localStorage.getItem('clay-settings')).OTP_seed)
//console.log('-- key:' + key)
var epoch = Math.round(new Date().getTime() / 1000.0);
if ((30 - (epoch % 30)) < 12) {
console.log('------- waiting for new TOTP,' + epoch);
return false;
} else {
var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0');
// updated for jsSHA v2.0.0 - http://caligatio.github.io/jsSHA/
var shaObj = new jsSHA("SHA-1", "HEX");
shaObj.setHMACKey(key, "HEX");
shaObj.update(time);
var hmac = shaObj.getHMAC("HEX");
//console.log('-- hmac:' + hmac)
var offset = hex2dec(hmac.substring(hmac.length - 1));
//console.log('--offset:' + offset)
var otp = (hex2dec(hmac.substring(offset * 2, offset * 2 + 8)) & hex2dec('7fffffff')) + '';
otp = (otp).substring(otp.length - 6, otp.length);
console.log('------- TOTP ' + otp + ' is expiring in ' + (30 - (epoch % 30)));
return otp
}
}
function xhr_to_syno(method, url_path, onload_function, max_retry, timeout) {
console.log('------xhr start')
status = "";
if (JSON.parse(localStorage.getItem('clay-settings')).server) {
var server = JSON.parse(localStorage.getItem('clay-settings')).server
url = server + url_path;
var xhr = new XMLHttpRequest();
xhr.timeout = timeout; // time in milliseconds
xhr.open(method, url, true);
console.log('------xhr opened')
xhr.onreadystatechange = function () {
console.log('------xhr onreadystatechange')
if (xhr.readyState === 4) {
retry = 0;
console.log('------xhr readyState 4');
if (xhr.status == 200) {
console.log('------xhr status 200 ');
onload_function(xhr);
return true;
} else if (xhr.status == 407) { // IP blocked by syno (or by syno Firewall)
console.log('------xhr status 407 IP blocked by syno (or by syno Firewall) ');
onload_function(xhr);
return false;
} else if (xhr.status == 400) { //wrong credentials
console.log('------xhr status wrong user or password ');
onload_function(xhr);
return false;
} else if (xhr.status == 404) { //wrong OTP
console.log('------xhr status 404 wrong OTP ');
onload_function(xhr);
return false;
} else if (xhr.status == 0) { //timeout
console.log('------xhr timed out');
//send back "timeout" to watch
//message = "Time out";
//send_message_to_watch(message)
onload_function(xhr);
} else { // why would this happen?
console.log('------xhr request returned error code ' + xhr.status);
message = "Error (XHR" + xhr.status + ")";
send_message_to_watch(message)
return false;
}
} else {
console.log('------xhr readyState ' + xhr.readyState);
}
};
xhr.ontimeout = function (e) {
};
xhr.send(null);
} else {
Pebble.showSimpleNotificationOnPebble("DSCam H-S", "You need to set your Synology account and server.");
}
}
function authenticate(action_callback) {
send_message_to_watch("Authenticating.")
var response;
sid = ""; //re-use sid from another session (7 days before expiry)
console.log('---- authenticate');
if (JSON.parse(localStorage.getItem('clay-settings')).username && JSON.parse(localStorage.getItem('clay-settings')).password) {
var username = JSON.parse(localStorage.getItem('clay-settings')).username;
var password = JSON.parse(localStorage.getItem('clay-settings')).password;
var url_path = "/webapi/auth.cgi?api=SYNO.API.Auth&method=Login&version=6&account=" + username + "&passwd=" + password + "&session=SurveillanceStation&format=sid";
if (JSON.parse(localStorage.getItem('clay-settings')).OTP_enabled) {
var otp_code = getOtp()
if (!otp_code) {
setTimeout( function () {
send_message_to_watch("Authenticating...");
home_mode_handler(action_callback);
}
,4000);
return false
}
console.log('-- otp_code is :' + otp_code)
url_path = url_path + "&otp_code=" + otp_code
}
var method = "GET";
onload_function = function (xhr) {
response = JSON.parse(xhr.responseText);
if (response.success == true) {
sid = response.data.sid;
console.log('------Authentication succeeded');
if (sid != "") {
message = "Authenticated";
send_message_to_watch(message)
home_mode_handler(action_callback);
} else {
console.log('------Unexpected error : authentication is OK but no SID retrieved');
message = "Auth error, no SID";
send_message_to_watch(message)
}
} else {
console.log('------Authentication failed : ' + JSON.stringify(response));
if (response.error.code == 400) {
console.log('------Authentication failed because of wrong creds');
message = "Authentication failed, check your credentials";
} else if (response.error.code == 404) {
console.log('------Authentication failed because of wrong TOTP');
message = "Authentication failed, check your TOTP seed";
} else if (response.error.code == 407) {
console.log('------Authentication failed because of IP Blocked');
message = "Authentication failed, IP is blocked";
}else if (xhr.status == 0){
console.log('------Authentication failed because of request timed out');
message = "Time out";
}
send_message_to_watch(message)
}
};
send_message_to_watch("Authenticating....");
max_retry = 0;
xhr_to_syno(method, url_path, onload_function, max_retry, 20000);
} else {
console.log("--- failed to get settings");
Pebble.showSimpleNotificationOnPebble("DSCam H-S", "You need to set your Synology account and server.");
}
return true
}
function get_status() {
var response;
if (sid != "") {
status = "";
console.log('---- get_status');
var url_path = "/webapi/entry.cgi?api=SYNO.SurveillanceStation.HomeMode&version=1&method=GetInfo&_sid=" + sid;
var method = "GET";
onload_function = function (xhr) {
response = JSON.parse(xhr.responseText);
if (response.success == true) {
status = response.data.on;
var message;
switch (status) {
case true:
message = "Home mode is ON (camera is off)";
break;
case false:
message = "Home mode is OFF (camera is on)";
break;
default:
message = "something happened, try again ! (IMPOSSIBLE)";
}
} else {
message = "something happened, try again ! (G200)";
}
send_message_to_watch(message)
}
max_retry = 10;
xhr_to_syno(method, url_path, onload_function, max_retry, 10000);
} else {
authenticate("get");
}
}
function home_mode_handler(action) {
var response;
var duration = 15 * 60
if (sid != "") {
console.log('---- switching home mode to ' + action);
var epoch = Math.round(new Date().getTime() / 1000.0);
var start_ts = epoch + 10;
method = "GET";
max_retry = 0;
switch (action) {
case "home_on":
send_message_to_watch("Setting Home Mode to ON")
url_path = "/webapi/entry.cgi?api=SYNO.SurveillanceStation.HomeMode&version=1&method=SaveOneTimeSwitch&onetime_enable_on=true&onetime_disable_on=false&onetime_enable_time=" + start_ts + "&_sid=" + sid;
break;
case "home_off":
send_message_to_watch("Setting Home Mode to OFF")
url_path = "/webapi/entry.cgi?api=SYNO.SurveillanceStation.HomeMode&version=1&method=SaveOneTimeSwitch&onetime_enable_on=false&onetime_disable_on=true&onetime_disable_time=" + start_ts + "&_sid=" + sid;
break;
case "timed":
send_message_to_watch("Temporary setting Home Mode to ON")
var end_ts = duration + start_ts
var d = new Date(end_ts * 1000)
var end_time = d.toLocaleTimeString("fr-FR",{timestyle:'short'});
url_path = "/webapi/entry.cgi?api=SYNO.SurveillanceStation.HomeMode&version=1&method=SaveOneTimeSwitch&onetime_enable_on=true&onetime_disable_on=true&onetime_enable_time=" + start_ts + "&onetime_disable_time=" + end_ts + "&_sid=" + sid;
break;
default:
message = "something happened, try again ! (IMPOSSIBLE)";
}
onload_switch_function = function (xhr) {
response = JSON.parse(xhr.responseText);
console.log(response.success);
if (response.success == true) {
switch (action) {
case "home_on":
message = "Home mode is ON (camera is off)";
break;
case "home_off":
message = "Home mode is OFF (camera is on)";
break;
case "timed":
message = "Home mode is ON until " + end_time;
break;
default:
message = "something happened, try again ! (IMPOSSIBLE)";
}
} else {
message = "something happened, try again ! (S200)";
}
send_message_to_watch(message)
}
max_retry = 0;
xhr_to_syno(method, url_path, onload_switch_function, max_retry, 10000);
} else {
authenticate(action);
}
}
function send_message_to_watch(message){
// Build message
var dict = {
'status': message,
};
console.log('DSCam Home-Switch: -- message to sent to watch:'+JSON.stringify(dict));
// Send the message
Pebble.sendAppMessage(dict, function (e) {
console.log('DSCam Home-Switch: -- message sent to watch');
}, function () {
console.log('DSCam Home-Switch: -- Failed to message sent to watch');
});
}
// Get JS readiness events
Pebble.addEventListener('ready', function (e) {
console.log("---- local storage:");
console.log("user " + JSON.parse(localStorage.getItem('clay-settings')).username);
console.log('PebbleKit JS is ready');
// Update Watch on this
Pebble.sendAppMessage({ 'JSReady': 1 });
send_message_to_watch("Welcome !")
});
// Get AppMessage events
Pebble.addEventListener('appmessage', function (e) {
// Get the dictionary from the message
var dict = e.payload;
switch (dict[0]) {
case 'get':
get_status();
break;
case 'timed':
home_mode_handler("timed");
break;
case 'home_on':
home_mode_handler("home_on");
break;
case 'home_off':
home_mode_handler("home_off");
break;
default:
console.log('Sorry. I don\'t understand your request :' + dict[0]);
}
});