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) { console.log(JSON.stringify(xhr)); if (xhr.status == 0){ console.log('------Authentication failed because of request timed out'); message = "Time out"; send_message_to_watch(message) } else { 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"; } 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]); } });