// InductAPI - v1.2 function InductAPI(clientId, apiBaseUrl) { // Private properties var _apiVersion = 1; var _apiBaseUrl = apiBaseUrl == undefined ? 'https://api.induct.no' : apiBaseUrl; var _initState = 0; var _api = this; var _isIE = $.browser != undefined ? $.browser.msie : false; var _listeners = {}; var _posting = []; window.InductAPI = _api; // Public properties this.clientId = null; this.apiUrl = null; this.loginStatus = { accessToken: null, companyHostname: null, isAuthenticated: false, // TODO: Remove this (customers must be notified first!) hasAccessTokenCookie: false // TODO: Remove this (customers must be notified first!) }; // Private methods var _setLoginStatus = function (loginStatus) { if (loginStatus.accessToken != undefined) { _createCookie('induct_appt', loginStatus.accessToken.token, 0); // Expires when user close browser } _api.loginStatus = { accessToken: loginStatus.accessToken != undefined ? loginStatus.accessToken : null, companyHostname: loginStatus.companyHostname, isAuthenticated: loginStatus.accessToken != undefined, // TODO: Remove this (customers must be notified first!) hasAccessTokenCookie: _readCookie('induct_appt') != null // TODO: Remove this (customers must be notified first!) }; }; var _injectScript = function (scriptUrl) { var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = scriptUrl; head.appendChild(script); }; // Check auth status var _getLoginStatus = function (callback) { var url = _getResourceUrl('/auth/status'); _get(url, function (data) { callback(data); }); }; // Check if func is a function var _isFunction = function (func) { var getType = {}; return func && getType.toString.call(func) == '[object Function]'; }; var _parseHashToken = function (hash) { if (hash.indexOf('access_token') == -1) return null; var loginData = []; $.each(hash.replace("#", "").split("&"), function (i, value) { value = value.split("="); loginData[value[0]] = value[1]; }); var response = { state: loginData['state'], accessToken: loginData['access_token'], expiresIn: parseInt(loginData['expires_in']) }; return response; }; String.prototype.hashCode = function () { var hash = 0; if (this.length == 0) return hash; for (i = 0; i < this.length; i++) { char = this.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32bit integer } return hash; }; Array.prototype.remove = function (key) { var len = this.length >>> 0; this.splice(this.indexOf(key), 1); }; Array.prototype.add = function (key) { this.push(key); }; if (!Array.prototype.indexOf) { Array.prototype.indexOf = function (elt /*, from*/) { var len = this.length >>> 0; var from = Number(arguments[1]) || 0; from = (from < 0) ? Math.ceil(from) : Math.floor(from); if (from < 0) from += len; for (; from < len; from++) { if (from in this && this[from] === elt) return from; } return -1; }; } var _getResourceUrl = function (resource) { // Insert / if omitted if (resource.indexOf('/') != 0) { resource = '/' + resource; } var url = _api.apiUrl + resource; if (url.indexOf('?') > 0) { url += "&_=" + $.now(); } else { url += "?_=" + $.now(); } return url; }; var _fixResponse = function (data, callback) { if (data === undefined) { callback(null); return; } var response = null; if (data.responseText != undefined && data.responseText != null) { response = $.parseJSON(data.responseText); } else { // Fix the error message from IIS if (data.message != undefined) { data.error = data.message; delete data.message; } response = data; } if (response == null) { response = { error: "No data" }; } callback(response); }; var _validateCallback = function (callback) { if (callback != undefined && !_isFunction(callback)) { throw new Error("InductAPI: Callback is not a function"); } }; var _createCookie = function (name, value, days) { if (days) { var date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); var expires = "; expires=" + date.toGMTString(); } else var expires = ""; document.cookie = name + "=" + value + expires + "; path=/"; }; var _readCookie = function (name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for (var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') c = c.substring(1, c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); } return null; }; var _eraseCookie = function (name) { _createCookie(name, "", -1); }; var _addAccessTokenHeader = function (xhr) { if (_api.loginStatus.accessToken != null) { xhr.setRequestHeader('Authorization', 'Bearer ' + _api.loginStatus.accessToken.token); } }; var _checkInitState = function () { if (_initState == 0) { throw new Error("InductAPI: API not ready!"); } } var _get = function (url, callback) { $.ajax({ type: "GET", url: url, cache: false, crossDomain: true, xhrFields: { withCredentials: false }, dataType: _isIE ? 'jsonp' : 'json', contentType: "application/json; charset=utf-8", beforeSend: _addAccessTokenHeader }).done(function (data) { _fixResponse(data, callback); }).fail(function (data) { _fixResponse(data, callback); }); }; // Public methods this.login = function (mode, callback) { _validateCallback(callback); _checkInitState(); if (['popup', 'iframe', 'redirect'].indexOf(mode) == -1) { throw new Error("InductAPI: Mode - '" + mode + "' not valid! [popup, iframe, redirect]"); } var loginState = "" + Math.floor(Math.random() * 10000000); window.InductAPI.windowMode = mode; var redirectUrl = window.location.href.indexOf('#') > 0 ? window.location.href.split('#')[0] : window.location.href; var url = _api.loginStatus.companyHostname + '/login/?client_id=' + _api.clientId + '&redirect_uri=' + encodeURIComponent(redirectUrl) + '&state=' + loginState + '&response_type=' + encodeURIComponent('token'); if (url.indexOf("https") != 0) { url = "https://" + url; } var _login = function (mode, url, callback) { switch (mode) { case 'popup': { url += '&window_mode=popup'; function popup_params(width, height) { var a = typeof window.screenX != 'undefined' ? window.screenX : window.screenLeft; var i = typeof window.screenY != 'undefined' ? window.screenY : window.screenTop; var g = typeof window.outerWidth != 'undefined' ? window.outerWidth : document.documentElement.clientWidth; var f = typeof window.outerHeight != 'undefined' ? window.outerHeight : (document.documentElement.clientHeight - 22); var h = (a < 0) ? window.screen.width + a : a; var left = parseInt(h + ((g - width) / 2), 10); var top = parseInt(i + ((f - height) / 2.5), 10); return 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',scrollbars=1'; } var loginWindow = window.open(url, '_blank', 'toolbar=0, location=1, menubar=0, ' + popup_params(620, 460)); loginWindow.focus(); var loginWindowInterval = setInterval(function () { if (_api.popupWindowAccessToken != undefined) { clearInterval(loginWindowInterval); loginWindowInterval = null; callback(_api.popupWindowAccessToken); delete _api.popupWindowAccessToken; loginWindow.close(); } else if (loginWindow == null || loginWindow.closed) { clearInterval(loginWindowInterval); loginWindowInterval = null; callback({ error: "Cancelled by user" }); } }, 100); break; } case 'redirect': { window.location.href = url; break; } case 'iframe': { url += '&window_mode=iframe'; var $iframe = $(document.createElement('iframe')); $iframe.attr('id', 'inductapi-login-' + loginState); $iframe.attr('src', url); $iframe.css('display', 'none'); $('body').append($iframe); $iframe.load(function (e) { var iframeInterval = setInterval(function () { var iframeLocation = $iframe[0].contentWindow.location.href; if (iframeLocation.indexOf(redirectUrl) == 0) { var hash = iframeLocation.substring(iframeLocation.indexOf('#')); var accessToken = _parseHashToken(hash); if (accessToken == null) { throw new Error("InductAPI: Could not parse accessToken from login iframe"); } clearTimeout(iframeTimeout); clearInterval(iframeInterval); iframeTimeout = null; iframeInterval = null; $iframe.remove(); callback(accessToken); } }, 1000); var iframeTimeout = setTimeout(function () { callback({ error: "Timeout" }); $iframe.remove(); clearInterval(iframeInterval); iframeInterval = null; }, 10000); }); break; } } }; _login(mode, url, function (data) { if (data.error != undefined) { $(_api).trigger('_loginFailed', data.error); if (callback != undefined) { callback(data); } return; } if (data.state != loginState) { callback({ error: "Invalid login state (maybe a hack attempt)" }); return; } _api.loginStatus.accessToken = { token: data.accessToken }; _getLoginStatus(function (loginStatus) { _setLoginStatus(loginStatus); $(_api).trigger('_loginSuccess', _api.loginStatus); if (callback != undefined) { callback(_api.loginStatus); } }); }); }; this.logout = function (callback) { _validateCallback(callback); _checkInitState(); var url = _getResourceUrl("/auth/access_token"); $.ajax({ type: "DELETE", url: url, cache: false, crossDomain: true, xhrFields: { withCredentials: false }, dataType: 'json', contentType: "application/json; charset=utf-8", beforeSend: _addAccessTokenHeader }).done(function (data) { _eraseCookie('induct_appt'); if (_isFunction(callback)) { _fixResponse(data, callback); } }).fail(function (data) { if (_isFunction(callback)) { _fixResponse(data, callback); } }); } this.GET = function (resource, callback) { _validateCallback(callback); _checkInitState(); var url = _getResourceUrl(resource); _get(url, function(response) { if (callback != undefined) { callback(response); } }); }; this.POST = function (resource, data, callback) { _validateCallback(callback); _checkInitState(); var dataHash = (resource + JSON.stringify(data)).hashCode(); if ($.inArray(dataHash, _posting) >= 0) { callback({ error: "Already posting this data" }); return; } _posting.add(dataHash); var url = _getResourceUrl(resource); if (!_isIE) { $.ajax({ type: "POST", url: url, cache: false, crossDomain: true, xhrFields: { withCredentials: false }, dataType: 'json', contentType: "application/json; charset=utf-8", beforeSend: _addAccessTokenHeader, data: JSON.stringify(data) }).done(function (data) { _posting.remove(dataHash); if (_isFunction(callback)) { _fixResponse(data, callback); } }).fail(function (data) { _posting.remove(dataHash); if (_isFunction(callback)) { _fixResponse(data, callback); } }); } else { var _crossDomainPost = function (url, data, callback) { var iframeId = "inductapi-post" + dataHash; var proxyUrl = _apiBaseUrl + "/ajaxProxy.html"; var $iframe = $(document.createElement("iframe")); $iframe.attr('id', iframeId); $iframe.attr('name', iframeId); $iframe.attr('src', proxyUrl + "?_" + $.now()); $iframe.css('display', 'none'); $('body').append($iframe); var accessToken = null; if (_api.loginStatus.accessToken != null) { accessToken = _api.loginStatus.accessToken.token; } var proxyData = { accessToken: accessToken, method: 'POST', url: url, postData: data }; var _proxyResponse = function (responseObj) { _posting.remove(dataHash); $iframe.remove(); var data = responseObj.returnValue; if (_isFunction(callback)) { var response = $.parseJSON(data); // Fix the error message from IIS if (response && response.message) { response.error = response.message; delete response.message; } callback(response); } }; $iframe.ready(function() { setTimeout(function () { pmrpc.call({ destination: $iframe[0].contentWindow, publicProcedureName: "apiCall", params: [JSON.stringify(proxyData)], destinationDomain: "*", onSuccess: _proxyResponse, onError: _proxyResponse }); }, 10); }); $(window).focus(); }; _crossDomainPost(url, data, callback); } }; this.init = function (clientId, callback) { _validateCallback(callback); _api.clientId = clientId; _api.apiUrl = _apiBaseUrl + '/v' + _apiVersion + '/' + _api.clientId; _initState = 0; var apptCookie = _readCookie("induct_appt"); if (apptCookie != null) { _api.loginStatus.accessToken = { token: apptCookie }; } _getLoginStatus(function (loginStatus) { if (loginStatus.companyHostname == undefined || loginStatus.companyHostname == null) { $(_api).trigger('_error', "Init failed. Check your clientId"); if (callback != undefined) { callback({ error: "Init failed. Check your clientId" }); } } else { _initState = 1; _setLoginStatus(loginStatus); $(_api).trigger('_ready', _api.loginStatus); if (_api.loginStatus.accessToken != null) { $(_api).trigger('_loginSuccess', _api.loginStatus); } if (callback != undefined) { callback({ success: "API ready" }); } } }); }; // Initialize API if (_isIE) { _injectScript(_apiBaseUrl + "/js/json2.js"); _injectScript(_apiBaseUrl + "/js/pmrpc.js"); } // Setup events this.ready = function (callback) { _validateCallback(callback); $(_api).on('_ready', callback); }; this.loginSuccess = function (callback) { _validateCallback(callback); $(_api).on('_loginSuccess', callback); }; this.loginFailed = function (callback) { _validateCallback(callback); $(_api).on('_loginFailed', callback); }; this.error = function (callback) { _validateCallback(callback); $(_api).on('_error', callback); }; var runInit = (clientId != undefined && clientId != null && clientId.length > 0); // Check if this window is the login redirect window if (window.opener != null && window.opener.InductAPI != undefined && window.opener.InductAPI.windowMode == 'popup') { // Popup mode var accessToken = _parseHashToken(window.location.hash); if (accessToken == null) { throw new Error("InductAPI: Could not parse accessToken from login popup"); } runInit = false; window.opener.InductAPI.popupWindowAccessToken = accessToken; } else if (window.top != self && window.top.InductAPI != undefined && window.top.InductAPI.windowMode == 'iframe') { // IFrame mode runInit = false; } else if (accessToken != null) { // Redirect mode runInit = false; window.location.href = window.location.href.indexOf('#') > 0 ? window.location.href.split('#')[0] : window.location.href; } if (runInit) { this.init(clientId); } return _api; };