Coverage

39%
903
354
549

src/feedhenry.js

76%
69
53
16
LineHitsSource
11var constants = require("./modules/constants");
21var events = require("./modules/events");
31var logger = require("./modules/logger");
41var ajax = require("./modules/ajax");
51var events = require("./modules/events");
61var cloud = require("./modules/waitForCloud");
71var api_act = require("./modules/api_act");
81var api_auth = require("./modules/api_auth");
91var api_sec = require("./modules/api_sec");
101var api_hash = require("./modules/api_hash");
111var api_mbaas = require("./modules/api_mbaas");
121var api_cloud = require("./modules/api_cloud");
131var api_push = require("./modules/api_push");
141var fhparams = require("./modules/fhparams");
151var appProps = require("./modules/appProps");
161var device = require("./modules/device");
171var syncCloudHandler = require("./modules/sync_cloud_handler");
18
191var defaultFail = function(msg, error) {
200 logger.error(msg + ":" + JSON.stringify(error));
21};
22
231var addListener = function(type, listener) {
242 events.addListener(type, listener);
25 if (type === constants.INIT_EVENT) {
26 //for fhinit event, need to check the status of cloud and may need to fire the listener immediately.
27 if (cloud.isReady()) {
280 listener(null, {
29 host: cloud.getCloudHostUrl()
30 });
31 } else if (cloud.getInitError()) {
320 listener(cloud.getInitError());
33 }
34 }
35};
36
371var once = function(type, listener) {
38 if (type === constants.INIT_EVENT && cloud.isReady()) {
390 listener(null, {
40 host: cloud.getCloudHostUrl()
41 });
42 } else if (type === constants.INIT_EVENT && cloud.getInitError()) {
430 listener(cloud.getInitError());
44 } else {
450 events.once(type, listener);
46 }
47};
48
49//Legacy shim. Init hapens based on fhconfig.json or, for v2, global var called fh_app_props which is injected as part of the index.html wrapper
501var init = function(opts, success, fail) {
510 logger.warn("$fh.init will be deprecated soon");
520 cloud.ready(function(err, host) {
53 if (err) {
54 if (typeof fail === "function") {
550 return fail(err);
56 }
57 } else {
58 if (typeof success === "function") {
590 success(host.host);
60 }
61 }
62 });
63};
64
651var fh = window.$fh || {};
661fh.init = init;
671fh.act = api_act;
681fh.auth = api_auth;
691fh.cloud = api_cloud;
701fh.sec = api_sec;
711fh.hash = api_hash;
721fh.push = api_push;
731fh.ajax = fh.__ajax = ajax;
741fh.mbaas = api_mbaas;
75
76// Mount sync to fh namespace
771fh.sync = require("fh-sync-js");
781fh.sync.setCloudHandler(syncCloudHandler);
79
801fh._getDeviceId = device.getDeviceId;
811fh.fh_timeout = 60000; //keep backward compatible
82
831fh.getCloudURL = function() {
840 return cloud.getCloudHostUrl();
85};
86
871fh.getFHParams = function() {
880 return fhparams.buildFHParams();
89};
90
911fh.getFHHeaders = function() {
920 return fhparams.getFHHeaders();
93};
94
95//events
961fh.addListener = addListener;
971fh.on = addListener;
981fh.once = once;
991var methods = ["removeListener", "removeAllListeners", "setMaxListeners", "listeners", "emit"];
100for (var i = 0; i < methods.length; i++) {
1015 fh[methods[i]] = events[methods[i]];
102}
103
104//keep backward compatibility
1051fh.on(constants.INIT_EVENT, function(err, host) {
106 if (err) {
1070 fh.cloud_props = {};
1080 fh.app_props = {};
109 } else {
1101 fh.cloud_props = {
111 hosts: {
112 url: host.host
113 }
114 };
1151 fh.app_props = appProps.getAppProps();
116 }
117});
118
119//keep backward compatibility
1201fh.on(constants.INTERNAL_CONFIG_LOADED_EVENT, function(err, host) {
121 if (err) {
1220 fh.app_props = {};
123 } else {
1241 fh.app_props = appProps.getAppProps();
125 }
126
127 // Emit config loaded event - appprops set at this point
128 // V2 legacy SDK uses this to know when to fire $fh.ready (i.e. appprops is now set)
1291 events.emit(constants.CONFIG_LOADED_EVENT, null);
130});
131
132//for test
1331fh.reset = cloud.reset;
134//we should really stop polluting global name space. Ideally we should ask browserify to use "$fh" when umd-fy the module. However, "$" is not allowed as the standard module name.
135//So, we assign $fh to the window name space directly here. (otherwise, we have to fork the grunt browserify plugin, then fork browerify and the dependent umd module, really not worthing the effort).
1361window.$fh = fh;
1371module.exports = fh;

src/modules/XDomainRequestWrapper.js

19%
41
8
33
LineHitsSource
12var urlparser = require('url');
2
32var XDomainRequestWrapper = function(xdr){
40 this.xdr = xdr;
50 this.isWrapper = true;
60 this.readyState = 0;
70 this.onreadystatechange = null;
80 this.status = 0;
90 this.statusText = "";
100 this.responseText = "";
110 this.headers = {};
120 var self = this;
130 this.xdr.onload = function(){
140 self.readyState = 4;
150 self.status = 200;
160 self.statusText = "";
170 self.responseText = self.xdr.responseText;
18 if(self.onreadystatechange){
190 self.onreadystatechange();
20 }
21 };
220 this.xdr.onerror = function(){
23 if(self.onerror){
240 self.onerror();
25 }
260 self.readyState = 4;
270 self.status = 0;
280 self.statusText = "";
29 if(self.onreadystatechange){
300 self.onreadystatechange();
31 }
32 };
330 this.xdr.ontimeout = function(){
340 self.readyState = 4;
350 self.status = 408;
360 self.statusText = "timeout";
37 if(self.onreadystatechange){
380 self.onreadystatechange();
39 }
40 };
41};
42
432XDomainRequestWrapper.prototype.open = function(method, url, asyn){
440 var parsedUrl = urlparser.parse(url, true);
450 parsedUrl.query = parsedUrl.query || {};
460 parsedUrl.query.fh_headers = this.headers;
470 this.xdr.open(method, urlparser.format(parsedUrl));
48};
49
502XDomainRequestWrapper.prototype.send = function(data){
510 this.xdr.send(data);
52};
53
542XDomainRequestWrapper.prototype.abort = function(){
550 this.xdr.abort();
56};
57
582XDomainRequestWrapper.prototype.setRequestHeader = function(n, v){
59 //not supported by xdr
60 //Good doc on limitations of XDomainRequest http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
61 //XDomainRequest doesn't allow setting custom request headers. But it is the only available option to do CORS requests in IE8 & 9. In IE10, they finally start to use standard XMLHttpRequest.
62 //To support FH auth tokens in IE8&9, we will append them as query parameters, use the key "fh_headers"
630 this.headers[n] = v;
64};
65
662XDomainRequestWrapper.prototype.getResponseHeader = function(n){
67 //not supported by xdr
68};
69
702module.exports = XDomainRequestWrapper;
71

src/modules/ajax.js

50%
120
61
59
LineHitsSource
1//a shameless copy from https://github.com/ForbesLindesay/ajax/blob/master/index.js.
2//it has the same methods and config options as jQuery/zeptojs but very light weight. see http://api.jquery.com/jQuery.ajax/
3//a few small changes are made for supporting IE 8 and other features:
4//1. use getXhr function to replace the default XMLHttpRequest implementation for supporting IE8
5//2. Integrate with events emitter. So to subscribe ajax events, you can do $fh.on("ajaxStart", handler). See http://api.jquery.com/Ajax_Events/ for full list of events
6//3. allow passing xhr factory method through options: e.g. $fh.ajax({xhr: function(){/*own implementation of xhr*/}});
7//4. Use fh_timeout value as the default timeout
8//5. an extra option called "tryJSONP" to allow try the same call with JSONP if normal CORS failed - should only be used internally
9//6. for jsonp, allow to specify the callback query param name using the "jsonp" option
10
112var eventsHandler = require("./events");
122var XDomainRequestWrapper = require("./XDomainRequestWrapper");
132var logger = require("./logger");
14
152var type
16try {
172 type = require('type-of')
18} catch (ex) {
19 //hide from browserify
200 var r = require
210 type = r('type')
22}
23
242var jsonpID = 0,
25 document = window.document,
26 key,
27 name,
28 rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
29 scriptTypeRE = /^(?:text|application)\/javascript/i,
30 xmlTypeRE = /^(?:text|application)\/xml/i,
31 jsonType = 'application/json',
32 htmlType = 'text/html',
33 blankRE = /^\s*$/;
34
352var ajax = module.exports = function (options) {
363 var settings = extend({}, options || {})
37 //keep backward compatibility
38 if(window && window.$fh && typeof window.$fh.fh_timeout === "number"){
392 ajax.settings.timeout = window.$fh.fh_timeout;
40 }
41
42 for (key in ajax.settings)
43 if (settings[key] === undefined) settings[key] = ajax.settings[key]
44
453 ajaxStart(settings)
46
47 if (!settings.crossDomain) {
483 settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) && (RegExp.$1 != window.location.protocol || RegExp.$2 != window.location.host)
49 }
50
513 var dataType = settings.dataType,
52 hasPlaceholder = /=\?/.test(settings.url)
53 if (dataType == 'jsonp' || hasPlaceholder) {
54 if (!hasPlaceholder) {
550 settings.url = appendQuery(settings.url, (settings.jsonp? settings.jsonp: '_callback') + '=?');
56 }
570 return ajax.JSONP(settings)
58 }
59
60 if (!settings.url) settings.url = window.location.toString()
613 serializeData(settings)
62
633 var mime = settings.accepts[dataType],
64 baseHeaders = {},
65 protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
66 xhr = settings.xhr(settings.crossDomain),
67 abortTimeout = null;
68
69 if (!settings.crossDomain) baseHeaders['X-Requested-With'] = 'XMLHttpRequest'
70 if (mime) {
713 baseHeaders['Accept'] = mime
72 if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]
733 xhr.overrideMimeType && xhr.overrideMimeType(mime)
74 }
75 if (settings.contentType || (settings.data && !settings.formdata && settings.type.toUpperCase() != 'GET'))
76 baseHeaders['Content-Type'] = (settings.contentType || 'application/x-www-form-urlencoded')
773 settings.headers = extend(baseHeaders, settings.headers || {})
78
79 if (typeof Titanium !== 'undefined') {
800 xhr.onerror = function(){
81 if (!abortTimeout){
820 return;
83 }
840 clearTimeout(abortTimeout);
850 ajaxError(null, 'error', xhr, settings);
86 };
87 }
88
893 xhr.onreadystatechange = function () {
90
91 if (xhr.readyState == 4) {
923 clearTimeout(abortTimeout)
933 abortTimeout = undefined;
943 var result, error = false
95 if(settings.tryJSONP){
96 //check if the request has fail. In some cases, we may want to try jsonp as well. Again, FH only...
97 if(xhr.status === 0 && settings.crossDomain && !xhr.isTimeout && protocol != 'file:'){
980 logger.debug("retry ajax call with jsonp")
990 settings.type = "GET";
1000 settings.dataType = "jsonp";
101
102 if (settings.data) {
1030 settings.data = "_jsonpdata=" + JSON.stringify(
104 require("./fhparams").addFHParams(JSON.parse(settings.data))
105 );
106 } else {
1070 settings.data = "_jsonpdata=" + settings.data;
108 }
109
1100 return ajax(settings);
111 }
112 }
113 if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) {
1142 dataType = dataType || mimeToDataType(xhr.getResponseHeader('content-type'))
1152 result = xhr.responseText
1162 logger.debug("ajax response :: status = " + xhr.status + " :: body = " + result)
117
118 try {
119 if (dataType == 'script')(1, eval)(result)
120 else if (dataType == 'xml') result = xhr.responseXML
121 else if (dataType == 'json') result = blankRE.test(result) ? null : JSON.parse(result)
122 } catch (e) {
1230 error = e
124 }
125
126 if (error) {
1270 logger.debug("ajax error", error);
1280 ajaxError(error, 'parsererror', xhr, settings)
129 }
130 else ajaxSuccess(result, xhr, settings)
131 } else {
1321 ajaxError(null, 'error', xhr, settings)
133 }
134 }
135 }
136
1373 var async = 'async' in settings ? settings.async : true
1383 logger.debug("ajax call settings", settings)
1393 xhr.open(settings.type, settings.url, async)
140
141 for (name in settings.headers) xhr.setRequestHeader(name, settings.headers[name])
142
143 if (ajaxBeforeSend(xhr, settings) === false) {
1440 logger.debug("ajax call is aborted due to ajaxBeforeSend")
1450 xhr.abort()
1460 return false
147 }
148
149 if (settings.timeout > 0) abortTimeout = setTimeout(function () {
1500 logger.debug("ajax call timed out")
1510 xhr.onreadystatechange = empty
1520 xhr.abort()
1530 xhr.isTimeout = true
1540 ajaxError(null, 'timeout', xhr, settings)
155 }, settings.timeout)
156
157 // avoid sending empty string (#319)
1583 xhr.send(settings.data ? settings.data : null)
1593 return xhr
160}
161
162
163// trigger a custom event and return true
164function triggerAndReturn(context, eventName, data) {
16515 eventsHandler.emit(eventName, data);
16615 return true;
167}
168
169// trigger an Ajax "global" event
170function triggerGlobal(settings, context, eventName, data) {
171 if (settings.global) return triggerAndReturn(context || document, eventName, data)
172}
173
174// Number of active Ajax requests
1752ajax.active = 0
176
177function ajaxStart(settings) {
178 if (settings.global && ajax.active++ === 0) triggerGlobal(settings, null, 'ajaxStart')
179}
180
181function ajaxStop(settings) {
182 if (settings.global && !(--ajax.active)) triggerGlobal(settings, null, 'ajaxStop')
183}
184
185// triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable
186function ajaxBeforeSend(xhr, settings) {
1873 var context = settings.context
188 if (settings.beforeSend.call(context, xhr, settings) === false)
189 return false
190
1913 triggerGlobal(settings, context, 'ajaxSend', [xhr, settings])
192}
193
194function ajaxSuccess(data, xhr, settings) {
1952 var context = settings.context,
196 status = 'success'
1972 settings.success.call(context, data, status, xhr)
1982 triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data])
1992 ajaxComplete(status, xhr, settings)
200}
201// type: "timeout", "error", "abort", "parsererror"
202function ajaxError(error, type, xhr, settings) {
2031 var context = settings.context
2041 settings.error.call(context, xhr, type, error)
2051 triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error])
2061 ajaxComplete(type, xhr, settings)
207}
208// status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
209function ajaxComplete(status, xhr, settings) {
2103 var context = settings.context
2113 settings.complete.call(context, xhr, status)
2123 triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings])
2133 ajaxStop(settings)
214}
215
216// Empty function, used as default callback
217function empty() {}
218
2192ajax.JSONP = function (options) {
220 if (!('type' in options)) return ajax(options)
221
2220 var callbackName = 'jsonp' + (++jsonpID),
223 script = document.createElement('script'),
224 abort = function () {
225 //todo: remove script
226 //$(script).remove()
227 if (callbackName in window) window[callbackName] = empty
2280 ajaxComplete('abort', xhr, options)
229 },
230 xhr = {
231 abort: abort
232 }, abortTimeout,
233 head = document.getElementsByTagName("head")[0] || document.documentElement
234
235 if (options.error) script.onerror = function () {
2360 xhr.abort()
2370 options.error()
238 }
239
2400 window[callbackName] = function (data) {
2410 clearTimeout(abortTimeout)
2420 abortTimeout = undefined;
243 //todo: remove script
244 //$(script).remove()
2450 delete window[callbackName]
2460 ajaxSuccess(data, xhr, options)
247 }
248
2490 serializeData(options)
2500 script.src = options.url.replace(/=\?/, '=' + callbackName)
251
252 // Use insertBefore instead of appendChild to circumvent an IE6 bug.
253 // This arises when a base node is used (see jQuery bugs #2709 and #4378).
2540 head.insertBefore(script, head.firstChild);
255
256 if (options.timeout > 0) abortTimeout = setTimeout(function () {
2570 xhr.abort()
2580 ajaxComplete('timeout', xhr, options)
259 }, options.timeout)
260
2610 return xhr
262}
263
264function isIE(){
2653 var ie = false;
266 if(navigator.userAgent && navigator.userAgent.indexOf("MSIE") >=0 ){
2670 ie = true;
268 }
2693 return ie;
270}
271
272function getXhr(crossDomain){
2733 var xhr = null;
274 //always use XMLHttpRequest if available
275 if(window.XMLHttpRequest){
2763 xhr = new XMLHttpRequest();
277 }
278 //for IE8 only. Need to make sure it's not used when running inside Cordova.
279 if(isIE() && (crossDomain === true) && typeof window.XDomainRequest !== "undefined" && typeof window.cordova === "undefined"){
2800 xhr = new XDomainRequestWrapper(new XDomainRequest());
281 }
282 // For Titanium SDK
283 if (typeof Titanium !== 'undefined'){
2840 var params = {};
285 if(ajax.settings && ajax.settings.timeout){
2860 params.timeout = ajax.settings.timeout;
287 }
2880 xhr = Titanium.Network.createHTTPClient(params);
289 }
290
2913 return xhr;
292}
293
2942ajax.settings = {
295 // Default type of request
296 type: 'GET',
297 // Callback that is executed before request
298 beforeSend: empty,
299 // Callback that is executed if the request succeeds
300 success: empty,
301 // Callback that is executed the the server drops error
302 error: empty,
303 // Callback that is executed on request complete (both: error and success)
304 complete: empty,
305 // The context for the callbacks
306 context: null,
307 // Whether to trigger "global" Ajax events
308 global: true,
309 // Transport
310 xhr: getXhr,
311 // MIME types mapping
312 accepts: {
313 script: 'text/javascript, application/javascript',
314 json: jsonType,
315 xml: 'application/xml, text/xml',
316 html: htmlType,
317 text: 'text/plain'
318 },
319 // Whether the request is to another domain
320 crossDomain: false
321}
322
323function mimeToDataType(mime) {
3240 return mime && (mime == htmlType ? 'html' :
325 mime == jsonType ? 'json' :
326 scriptTypeRE.test(mime) ? 'script' :
327 xmlTypeRE.test(mime) && 'xml') || 'text'
328}
329
330function appendQuery(url, query) {
3310 return (url + '&' + query).replace(/[&?]{1,2}/, '?')
332}
333
334// serialize payload and append it to the URL for GET requests
335function serializeData(options) {
336 if (type(options.data) === 'object') {
337 if(typeof options.data.append === "function"){
338 //we are dealing with FormData, do not serialize
3390 options.formdata = true;
340 } else {
3410 options.data = param(options.data)
342 }
343 }
344 if (options.data && (!options.type || options.type.toUpperCase() == 'GET'))
345 options.url = appendQuery(options.url, options.data)
346}
347
3482ajax.get = function (url, success) {
3490 return ajax({
350 url: url,
351 success: success
352 })
353}
354
3552ajax.post = function (url, data, success, dataType) {
356 if (type(data) === 'function') dataType = dataType || success, success = data, data = null
3570 return ajax({
358 type: 'POST',
359 url: url,
360 data: data,
361 success: success,
362 dataType: dataType
363 })
364}
365
3662ajax.getJSON = function (url, success) {
3670 return ajax({
368 url: url,
369 success: success,
370 dataType: 'json'
371 })
372}
373
3742var escape = encodeURIComponent;
375
376function serialize(params, obj, traditional, scope) {
3770 var array = type(obj) === 'array';
378 for (var key in obj) {
3790 var value = obj[key];
380
381 if (scope) key = traditional ? scope : scope + '[' + (array ? '' : key) + ']'
382 // handle data in serializeArray() format
383 if (!scope && array) params.add(value.name, value.value)
384 // recurse into nested objects
385 else if (traditional ? (type(value) === 'array') : (type(value) === 'object'))
386 serialize(params, value, traditional, key)
387 else params.add(key, value)
388 }
389}
390
391function param(obj, traditional) {
3920 var params = []
3930 params.add = function (k, v) {
3940 this.push(escape(k) + '=' + escape(v))
395 }
3960 serialize(params, obj, traditional)
3970 return params.join('&').replace('%20', '+')
398}
399
400function extend(target) {
4016 var slice = Array.prototype.slice;
4026 slice.call(arguments, 1).forEach(function (source) {
403 for (key in source)
404 if (source[key] !== undefined)
405 target[key] = source[key]
406 })
4076 return target
408}
409

src/modules/api_act.js

34%
23
8
15
LineHitsSource
11var logger =require("./logger");
21var cloud = require("./waitForCloud");
31var fhparams = require("./fhparams");
41var ajax = require("./ajax");
51var handleError = require("./handleError");
61var appProps = require("./appProps");
71var _ = require('underscore');
8
9function doActCall(opts, success, fail){
100 var cloud_host = cloud.getCloudHost();
110 var url = cloud_host.getActUrl(opts.act);
120 var params = opts.req || {};
130 var headers = fhparams.getFHHeaders();
14 if (opts.headers) {
150 headers = _.extend(headers, opts.headers);
16 }
170 return ajax({
18 "url": url,
19 "tryJSONP": true,
20 "type": "POST",
21 "dataType": "json",
22 "data": JSON.stringify(params),
23 "headers": headers,
24 "contentType": "application/json",
25 "timeout": opts.timeout || appProps.timeout,
26 "success": success,
27 "error": function(req, statusText, error){
280 return handleError(fail, req, statusText, error);
29 }
30 });
31}
32
331module.exports = function(opts, success, fail){
340 logger.debug("act is called");
35 if(!fail){
360 fail = function(msg, error){
370 logger.debug(msg + ":" + JSON.stringify(error));
38 };
39 }
40
41 if(!opts.act){
420 return fail('act_no_action', {});
43 }
44
450 cloud.ready(function(err, cloudHost){
460 logger.debug("Calling fhact now");
47 if(err){
480 return fail(err.message, err);
49 } else {
500 doActCall(opts, success, fail);
51 }
52 });
53};
54

src/modules/api_auth.js

28%
56
16
40
LineHitsSource
11var logger = require("./logger");
21var cloud = require("./waitForCloud");
31var fhparams = require("./fhparams");
41var ajax = require("./ajax");
51var handleError = require("./handleError");
61var device = require("./device");
71var constants = require("./constants");
81var checkAuth = require("./checkAuth");
91var appProps = require("./appProps");
101var data = require('./data');
11
12function callAuthEndpoint(endpoint, data, opts, success, fail){
130 var app_props = appProps.getAppProps();
140 var path = app_props.host + constants.boxprefix + "admin/authpolicy/" + endpoint;
15
16 if (app_props.local) {
170 path = cloud.getCloudHostUrl() + constants.boxprefix + "admin/authpolicy/" + endpoint;
18 }
19
200 ajax({
21 "url": path,
22 "type": "POST",
23 "tryJSONP": true,
24 "data": JSON.stringify(data),
25 "dataType": "json",
26 "contentType": "application/json",
27 "timeout": opts.timeout || app_props.timeout,
28 "headers": fhparams.getFHHeaders(),
29 success: function(res){
30 if(success){
310 return success(res);
32 }
33 },
34 error: function(req, statusText, error){
350 logger.error('got error when calling ' + endpoint, req.responseText || req, error);
36 if(fail){
370 fail(req, statusText, error);
38 }
39 }
40 });
41}
42
431var auth = function(opts, success, fail) {
44 if (!fail) {
450 fail = function(msg, error) {
460 logger.debug(msg + ":" + JSON.stringify(error));
47 };
48 }
49 if (!opts.policyId) {
500 return fail('auth_no_policyId', {});
51 }
52 if (!opts.clientToken) {
530 return fail('auth_no_clientToken', {});
54 }
55
560 cloud.ready(function(err, data) {
57 if (err) {
580 return fail(err.message, err);
59 } else {
600 var req = {};
610 req.policyId = opts.policyId;
620 req.clientToken = opts.clientToken;
630 var cloudHost = cloud.getCloudHost();
64 if(cloudHost.getEnv()){
650 req.environment = cloudHost.getEnv();
66 }
67 if (opts.endRedirectUrl) {
680 req.endRedirectUrl = opts.endRedirectUrl;
69 if (opts.authCallback) {
700 req.endRedirectUrl += (/\?/.test(req.endRedirectUrl) ? "&" : "?") + "_fhAuthCallback=" + opts.authCallback;
71 }
72 }
730 req.params = {};
74 if (opts.params) {
750 req.params = opts.params;
76 }
770 var endurl = opts.endRedirectUrl || "status=complete";
780 req.device = device.getDeviceId();
790 req = fhparams.addFHParams(req);
800 callAuthEndpoint('auth', req, opts, function(res){
810 auth.authenticateHandler(endurl, res, success, fail);
82 }, function(req, statusText, error){
830 handleError(fail, req, statusText, error);
84 });
85 }
86 });
87};
88
891auth.hasSession = function(cb){
900 data.sessionManager.exists(cb);
91};
92
931auth.clearSession = function(cb){
940 data.sessionManager.read(function(err, session){
95 if(err){
960 return cb(err);
97 }
98 if(session){
99 //try the best to delete the remote session
1000 callAuthEndpoint('revokesession', session, {});
101 }
1020 data.sessionManager.remove(cb);
1030 fhparams.setAuthSessionToken(undefined);
104 });
105};
106
1071auth.authenticateHandler = checkAuth.handleAuthResponse;
108
1091auth.verify = function(cb){
1100 data.sessionManager.read(function(err, session){
111 if(err){
1120 return cb(err);
113 }
114 if(session){
115 //try the best to delete the session in remote
1160 callAuthEndpoint('verifysession', session, {}, function(res){
1170 return cb(null, res.isValid);
118 }, function(req, statusText, error){
1190 return cb('network_error');
120 });
121 } else {
1220 return cb('no_session');
123 }
124 });
125};
126
1271module.exports = auth;

src/modules/api_cloud.js

30%
26
8
18
LineHitsSource
11var logger =require("./logger");
21var cloud = require("./waitForCloud");
31var fhparams = require("./fhparams");
41var ajax = require("./ajax");
51var handleError = require("./handleError");
61var appProps = require("./appProps");
71var _ = require('underscore');
8
9function doCloudCall(opts, success, fail){
100 var cloud_host = cloud.getCloudHost();
110 var url = cloud_host.getCloudUrl(opts.path);
120 var params = opts.data || {};
130 var type = opts.method || "POST";
140 var data;
15 if (["POST", "PUT", "PATCH", "DELETE"].indexOf(type.toUpperCase()) !== -1) {
160 data = JSON.stringify(params);
17 } else {
180 data = params;
19 }
20
210 var headers = fhparams.getFHHeaders();
22 if (opts.headers) {
230 headers = _.extend(headers, opts.headers);
24 }
25
260 return ajax({
27 "url": url,
28 "type": type,
29 "dataType": opts.dataType || "json",
30 "data": data,
31 "contentType": opts.contentType || "application/json",
32 "timeout": opts.timeout || appProps.timeout,
33 "headers": headers,
34 "success": success,
35 "error": function(req, statusText, error){
360 return handleError(fail, req, statusText, error);
37 }
38 });
39}
40
411module.exports = function(opts, success, fail){
420 logger.debug("cloud is called");
43 if(!fail){
440 fail = function(msg, error){
450 logger.debug(msg + ":" + JSON.stringify(error));
46 };
47 }
48
490 cloud.ready(function(err, cloudHost){
500 logger.debug("Calling fhact now");
51 if(err){
520 return fail(err.message, err);
53 } else {
540 doCloudCall(opts, success, fail);
55 }
56 });
57};
58

src/modules/api_hash.js

28%
7
2
5
LineHitsSource
11var hashImpl = require("./security/hash");
2
31module.exports = function(p, s, f){
40 var params = {};
5 if(typeof p.algorithm === "undefined"){
60 p.algorithm = "MD5";
7 }
80 params.act = "hash";
90 params.params = p;
100 hashImpl(params, s, f);
11};

src/modules/api_mbaas.js

38%
21
8
13
LineHitsSource
11var logger =require("./logger");
21var cloud = require("./waitForCloud");
31var fhparams = require("./fhparams");
41var ajax = require("./ajax");
51var handleError = require("./handleError");
61var consts = require("./constants");
71var appProps = require("./appProps");
8
91module.exports = function(opts, success, fail){
100 logger.debug("mbaas is called.");
11 if(!fail){
120 fail = function(msg, error){
130 console.debug(msg + ":" + JSON.stringify(error));
14 };
15 }
16
170 var mbaas = opts.service;
180 var params = opts.params;
19
200 cloud.ready(function(err, cloudHost){
210 logger.debug("Calling mbaas now");
22 if(err){
230 return fail(err.message, err);
24 } else {
250 var cloud_host = cloud.getCloudHost();
260 var url = cloud_host.getMBAASUrl(mbaas);
270 params = fhparams.addFHParams(params);
280 return ajax({
29 "url": url,
30 "tryJSONP": true,
31 "type": "POST",
32 "dataType": "json",
33 "data": JSON.stringify(params),
34 "headers": fhparams.getFHHeaders(),
35 "contentType": "application/json",
36 "timeout": opts.timeout || appProps.timeout,
37 "success": success,
38 "error": function(req, statusText, error){
390 return handleError(fail, req, statusText, error);
40 }
41 });
42 }
43 });
44};

src/modules/api_push.js

28%
14
4
10
LineHitsSource
11var logger = require("./logger");
21var appProps = require("./appProps");
31var cloud = require("./waitForCloud");
4
51module.exports = function (onNotification, success, fail, config) {
6 if (!fail) {
70 fail = function (msg, error) {
80 logger.debug(msg + ":" + JSON.stringify(error));
9 };
10 }
11
120 cloud.ready(function(err, cloudHost){
130 logger.debug("push is called");
14 if(err){
150 return fail(err.message, err);
16 } else {
17 if (window.push) {
180 var props = appProps.getAppProps();
190 props.pushServerURL = props.host + '/api/v2/ag-push';
20 if (config) {
21 for(var key in config) {
220 props[key] = config[key];
23 }
24 }
250 window.push.register(onNotification, success, fail, props);
26 } else {
270 fail('push plugin not installed');
28 }
29 }
30 });
31};
32

src/modules/api_sec.js

25%
20
5
15
LineHitsSource
11var keygen = require("./security/aes-keygen");
21var aes = require("./security/aes-node");
31var rsa = require("./security/rsa-node");
41var hash = require("./security/hash");
5
61module.exports = function(p, s, f){
7 if (!p.act) {
80 f('bad_act', {}, p);
90 return;
10 }
11 if (!p.params) {
120 f('no_params', {}, p);
130 return;
14 }
15 if (!p.params.algorithm) {
160 f('no_params_algorithm', {}, p);
170 return;
18 }
190 p.params.algorithm = p.params.algorithm.toLowerCase();
20 if(p.act === "hash"){
210 return hash(p, s, f);
22 } else if(p.act === "encrypt"){
23 if(p.params.algorithm === "aes"){
240 return aes.encrypt(p, s, f);
25 } else if(p.params.algorithm === "rsa"){
260 return rsa.encrypt(p, s, f);
27 } else {
280 return f('encrypt_bad_algorithm:' + p.params.algorithm, {}, p);
29 }
30 } else if(p.act === "decrypt"){
31 if(p.params.algorithm === "aes"){
320 return aes.decrypt(p, s, f);
33 } else {
340 return f('decrypt_bad_algorithm:' + p.params.algorithm, {}, p);
35 }
36 } else if(p.act === "keygen"){
37 if(p.params.algorithm === "aes"){
380 return keygen(p, s, f);
39 } else {
400 return f('keygen_bad_algorithm:' + p.params.algorithm, {}, p);
41 }
42 }
43};

src/modules/appProps.js

76%
30
23
7
LineHitsSource
11var consts = require("./constants");
21var ajax = require("./ajax");
31var logger = require("./logger");
41var qs = require("./queryMap");
51var _ = require('underscore');
6
71var app_props = null;
8
91var load = function(cb) {
101 var doc_url = document.location.href;
111 var url_params = qs(doc_url.replace(/#.*?$/g, ''));
121 var url_props = {};
13
14 //only use fh_ prefixed params
15 for(var key in url_params){
16 if(url_params.hasOwnProperty(key) ){
17 if(key.indexOf('fh_') === 0){
180 url_props[key.substr(3)] = decodeURI(url_params[key]);
19 }
20 }
21 }
22
23 //default properties
241 app_props = {
25 appid: "000000000000000000000000",
26 appkey: "0000000000000000000000000000000000000000",
27 projectid: "000000000000000000000000",
28 connectiontag: "0.0.1"
29 };
30
31 function setProps(props){
321 _.extend(app_props, props, url_props);
33
34 if(typeof url_params.url !== 'undefined'){
351 app_props.host = url_params.url;
36 }
37
381 app_props.local = !!(url_props.host || url_params.url || (props.local && props.host));
391 cb(null, app_props);
40 }
41
421 var config_url = url_params.fhconfig || consts.config_js;
431 ajax({
44 url: config_url,
45 dataType: "json",
46 success: function(data) {
471 logger.debug("fhconfig = " + JSON.stringify(data));
48 //when load the config file on device, because file:// protocol is used, it will never call fail call back. The success callback will be called but the data value will be null.
49 if (null == data) {
50 //fh v2 only
51 if(window.fh_app_props){
520 return setProps(window.fh_app_props);
53 }
540 return cb(new Error("app_config_missing"));
55 } else {
56
571 setProps(data);
58 }
59 },
60 error: function(req, statusText, error) {
61 //fh v2 only
62 if(window.fh_app_props){
630 return setProps(window.fh_app_props);
64 }
650 logger.error(consts.config_js + " Not Found");
660 cb(new Error("app_config_missing"));
67 }
68 });
69};
70
711var setAppProps = function(props) {
720 app_props = props;
73};
74
751var getAppProps = function() {
763 return app_props;
77};
78
791module.exports = {
80 load: load,
81 getAppProps: getAppProps,
82 setAppProps: setAppProps
83};
84

src/modules/checkAuth.js

21%
42
9
33
LineHitsSource
11var logger = require("./logger");
21var queryMap = require("./queryMap");
31var fhparams = require("./fhparams");
41var data = require('./data');
5
61var checkAuth = function(url) {
7 if (/\_fhAuthCallback/.test(url)) {
80 var qmap = queryMap(url);
9 if (qmap) {
100 var fhCallback = qmap["_fhAuthCallback"];
11 if (fhCallback) {
12 if (qmap['result'] && qmap['result'] === 'success') {
130 var sucRes = {'sessionToken': qmap['fh_auth_session'], 'authResponse' : JSON.parse(decodeURIComponent(decodeURIComponent(qmap['authResponse'])))};
140 fhparams.setAuthSessionToken(qmap['fh_auth_session']);
150 data.sessionManager.save(qmap['fh_auth_session']);
160 window[fhCallback](null, sucRes);
17 } else {
180 window[fhCallback]({'message':qmap['message']});
19 }
20 }
21 }
22 }
23};
24
251var handleAuthResponse = function(endurl, res, success, fail){
26 if(res.status && res.status === "ok"){
27
280 var onComplete = function(res){
29 if(res.sessionToken){
300 fhparams.setAuthSessionToken(res.sessionToken);
310 data.sessionManager.save(res.sessionToken, function(){
320 return success(res);
33 });
34 } else {
350 return success(res);
36 }
37 };
38 //for OAuth, a url will be returned which means the user should be directed to that url to authenticate.
39 //we try to use the ChildBrower plugin if it can be found. Otherwise send the url to the success function to allow developer to handle it.
40 if(res.url){
410 var inappBrowserWindow = null;
420 var locationChange = function(new_url){
43 if(new_url.indexOf(endurl) > -1){
44 if(inappBrowserWindow){
450 inappBrowserWindow.close();
46 }
470 var qmap = queryMap(new_url);
48 if(qmap) {
49 if(qmap['result'] && qmap['result'] === 'success'){
500 var sucRes = {'sessionToken': qmap['fh_auth_session'], 'authResponse' : JSON.parse(decodeURIComponent(decodeURIComponent(qmap['authResponse'])))};
510 onComplete(sucRes);
52 } else {
53 if(fail){
540 fail("auth_failed", {'message':qmap['message']});
55 }
56 }
57 } else {
58 if(fail){
590 fail("auth_failed", {'message':qmap['message']});
60 }
61 }
62 }
63 };
64 if(window.PhoneGap || window.cordova){
65 if(window.plugins && window.plugins.childBrowser){
66 //found childbrowser plugin,add the event listener and load it
67 //we need to know when the OAuth process is finished by checking for the presence of endurl. If the endurl is found, it means the authentication finished and we should find if it's successful.
68 if(typeof window.plugins.childBrowser.showWebPage === "function"){
690 window.plugins.childBrowser.onLocationChange = locationChange;
700 window.plugins.childBrowser.showWebPage(res.url);
710 inappBrowserWindow = window.plugins.childBrowser;
72 }
73 } else {
74 try {
750 inappBrowserWindow = window.open(res.url, "_blank", 'location=yes');
760 inappBrowserWindow.addEventListener("loadstart", function(ev){
770 locationChange(ev.url);
78 });
79 } catch(e){
800 logger.info("InAppBrowser plugin is not intalled.");
810 onComplete(res);
82 }
83 }
84 } else {
850 document.location.href = res.url;
86 }
87 } else {
880 onComplete(res);
89 }
90 } else {
91 if(fail){
920 fail("auth_failed", res);
93 }
94 }
95};
96
97//This is mainly for using $fh.auth inside browsers. If the authentication method is OAuth, at the end of the process, the user will be re-directed to
98//a url that we specified for checking if the auth is successful. So we always check the url to see if we are on the re-directed page.
99if (window.addEventListener) {
1001 window.addEventListener('load', function(){
1011 checkAuth(window.location.href);
102 }, false); //W3C
103} else if (window.attachEvent) {
1040 window.attachEvent('onload', function(){
1050 checkAuth(window.location.href);
106 }); //IE
107}
108
1091module.exports = {
110 "handleAuthResponse": handleAuthResponse
111};
112

src/modules/constants.js

100%
1
1
0
LineHitsSource
11module.exports = {
2 "boxprefix": "/box/srv/1.1/",
3 "sdk_version": "BUILD_VERSION",
4 "config_js": "fhconfig.json",
5 "INIT_EVENT": "fhinit",
6 "INTERNAL_CONFIG_LOADED_EVENT": "internalfhconfigloaded",
7 "CONFIG_LOADED_EVENT": "fhconfigloaded",
8 "SESSION_TOKEN_STORAGE_NAME": "fh_session_token",
9 "SESSION_TOKEN_KEY_NAME":"sessionToken"
10};
11

src/modules/cookies.js

9%
11
1
10
LineHitsSource
11module.exports = {
2 readCookieValue : function (cookie_name) {
30 var name_str = cookie_name + "=";
40 var cookies = document.cookie.split(";");
5 for (var i = 0; i < cookies.length; i++) {
60 var c = cookies[i];
7 while (c.charAt(0) === ' ') {
80 c = c.substring(1, c.length);
9 }
10 if (c.indexOf(name_str) === 0) {
110 return c.substring(name_str.length, c.length);
12 }
13 }
140 return null;
15 },
16
17 createCookie : function (cookie_name, cookie_value) {
180 var date = new Date();
190 date.setTime(date.getTime() + 36500 * 24 * 60 * 60 * 1000); //100 years
200 var expires = "; expires=" + date.toGMTString();
210 document.cookie = cookie_name + "=" + cookie_value + expires + "; path = /";
22 }
23};
24

src/modules/data.js

54%
24
13
11
LineHitsSource
11var Lawnchair = require('../../libs/generated/lawnchair');
21var lawnchairext = require('./lawnchair-ext');
31var logger = require('./logger');
41var constants = require("./constants");
5
61var data = {
7 //dom adapter doens't work on windows phone, so don't specify the adapter if the dom one failed
8 //we specify the order of lawnchair adapters to use, lawnchair will find the right one to use, to keep backward compatibility, keep the order
9 //as dom, webkit-sqlite, localFileStorage, window-name
10 DEFAULT_ADAPTERS : ["dom", "webkit-sqlite", "window-name", "titanium"],
11 getStorage: function(name, adapters, fail){
121 var adpts = data.DEFAULT_ADAPTERS;
131 var errorHandler = fail || function(){};
14 if(adapters && adapters.length > 0){
150 adpts = (typeof adapters === 'string'?[adapters]: adapters);
16 }
171 var conf = {
18 name: name,
19 adapter: adpts,
20 fail: function(msg, err){
210 var error_message = 'read/save from/to local storage failed msg:' + msg + ' err:' + err;
220 logger.error(error_message, err);
230 errorHandler(error_message, {});
24 }
25 };
261 var store = Lawnchair(conf, function(){});
271 return store;
28 },
29 addFileStorageAdapter: function(appProps, hashFunc){
300 Lawnchair.adapter('localFileStorage', lawnchairext.fileStorageAdapter(appProps, hashFunc));
31 },
32 sessionManager: {
33 read: function(cb){
341 data.getStorage(constants.SESSION_TOKEN_STORAGE_NAME).get(constants.SESSION_TOKEN_KEY_NAME, function(session){
35 if(cb){
361 return cb(null, session);
37 }
38 });
39 },
40 exists: function(cb){
410 data.getStorage(constants.SESSION_TOKEN_STORAGE_NAME).exists(constants.SESSION_TOKEN_KEY_NAME, function(exist){
42 if(cb){
430 return cb(null, exist);
44 }
45 });
46 },
47 remove: function(cb){
480 data.getStorage(constants.SESSION_TOKEN_STORAGE_NAME).remove(constants.SESSION_TOKEN_KEY_NAME, function(){
49 if(cb){
500 return cb();
51 }
52 });
53 },
54 save: function(sessionToken, cb){
550 data.getStorage(constants.SESSION_TOKEN_STORAGE_NAME).save({key: constants.SESSION_TOKEN_KEY_NAME, sessionToken: sessionToken}, function(obj){
56 if(cb){
570 return cb();
58 }
59 });
60 }
61 }
62};
63
641module.exports = data;
65

src/modules/device.js

14%
28
4
24
LineHitsSource
11var cookies = require("./cookies");
21var uuidModule = require("./uuid");
31var logger = require("./logger");
4
51module.exports = {
6 //try to get the unique device identifier
7 "getDeviceId": function(){
8 //check for cordova/phonegap first
9 if(typeof window.fhdevice !== "undefined" && typeof window.fhdevice.uuid !== "undefined"){
100 return window.fhdevice.uuid;
11 } else if(typeof window.device !== "undefined" && typeof window.device.uuid !== "undefined"){
120 return window.device.uuid;
13 } else if(typeof navigator.device !== "undefined" && typeof navigator.device.uuid !== "undefined"){
140 return navigator.device.uuid;
15 } else {
160 var _mock_uuid_cookie_name = "mock_uuid";
170 var uuid = cookies.readCookieValue(_mock_uuid_cookie_name);
18 if(!uuid){
190 uuid = uuidModule.createUUID();
200 cookies.createCookie(_mock_uuid_cookie_name, uuid);
21 }
220 return uuid;
23 }
24 },
25
26 //this is for fixing analytics issues when upgrading from io6 to ios7. Probably can be deprecated now
27 "getCuidMap": function(){
28 if(typeof window.fhdevice !== "undefined" && typeof window.fhdevice.cuidMap !== "undefined"){
290 return window.fhdevice.cuidMap;
30 } else if(typeof window.device !== "undefined" && typeof window.device.cuidMap !== "undefined"){
310 return window.device.cuidMap;
32 } else if(typeof navigator.device !== "undefined" && typeof navigator.device.cuidMap !== "undefined"){
330 return navigator.device.cuidMap;
34 }
35
360 return null;
37 },
38
39 "getDestination": function(){
400 var destination = null;
410 var platformsToTest = require("./platformsMap");
42
43
440 var userAgent = navigator.userAgent;
45
460 var dest_override = document.location.search.split("fh_destination_code=");
47 if (dest_override.length > 1) {
480 destination = dest_override[1];
49 } else if (typeof window.fh_destination_code !== 'undefined') {
500 destination = window.fh_destination_code;
51 } else {
520 platformsToTest.forEach(function(testDestination){
530 testDestination.test.forEach(function(destinationTest){
54 if(userAgent.indexOf(destinationTest) > -1){
550 destination = testDestination.destination;
56 }
57 });
58 });
59 }
60
61 if(destination == null){ //No user agents were found, set to default web
620 destination = "web";
63 }
64
650 logger.debug("destination = " + destination);
66
670 return destination;
68 }
69};

src/modules/events.js

100%
4
4
0
LineHitsSource
12var EventEmitter = require('events').EventEmitter;
2
32var emitter = new EventEmitter();
42emitter.setMaxListeners(0);
5
62module.exports = emitter;

src/modules/fhparams.js

26%
41
11
30
LineHitsSource
11var device = require("./device");
21var sdkversion = require("./sdkversion");
31var appProps = require("./appProps");
41var logger = require("./logger");
5
61var defaultParams = null;
71var authSessionToken = null;
8//TODO: review these options, we probably only needs all of them for init calls, but we shouldn't need all of them for act calls
91var buildFHParams = function(){
10 if(defaultParams){
110 return defaultParams;
12 }
130 var fhparams = {};
140 fhparams.cuid = device.getDeviceId();
150 fhparams.cuidMap = device.getCuidMap();
160 fhparams.destination = device.getDestination();
17
18 if(window.device || navigator.device){
190 fhparams.device = window.device || navigator.device;
20 }
21
22 //backward compatible
23 if (typeof window.fh_app_version !== 'undefined'){
240 fhparams.app_version = fh_app_version;
25 }
26 if (typeof window.fh_project_version !== 'undefined'){
270 fhparams.project_version = fh_project_version;
28 }
29 if (typeof window.fh_project_app_version !== 'undefined'){
300 fhparams.project_app_version = fh_project_app_version;
31 }
320 fhparams.sdk_version = sdkversion();
33 if(authSessionToken){
340 fhparams.sessionToken = authSessionToken;
35 }
36
370 var app_props = appProps.getAppProps();
38 if(app_props){
390 fhparams.appid = app_props.appid;
400 fhparams.appkey = app_props.appkey;
410 fhparams.projectid = app_props.projectid;
420 fhparams.analyticsTag = app_props.analyticsTag;
430 fhparams.connectiontag = app_props.connectiontag;
44 if(app_props.init){
450 fhparams.init = typeof(app_props.init) === "string" ? JSON.parse(app_props.init) : app_props.init;
46 }
47 }
48
490 defaultParams = fhparams;
500 logger.debug("fhparams = ", defaultParams);
510 return fhparams;
52};
53
54//TODO: deprecate this. Move to use headers instead
551var addFHParams = function(params){
560 var p = params || {};
570 p.__fh = buildFHParams();
580 return p;
59};
60
611var getFHHeaders = function(){
620 var headers = {};
630 var params = buildFHParams();
64 for(var name in params){
65 if(params.hasOwnProperty(name)){
660 headers['X-FH-' + name] = params[name];
67 }
68 }
690 return headers;
70};
71
721var setAuthSessionToken = function(sessionToken){
730 authSessionToken = sessionToken;
740 defaultParams = null;
75};
76
771module.exports = {
78 "buildFHParams": buildFHParams,
79 "addFHParams": addFHParams,
80 "setAuthSessionToken":setAuthSessionToken,
81 "getFHHeaders": getFHHeaders
82};
83

src/modules/handleError.js

11%
9
1
8
LineHitsSource
11module.exports = function(fail, req, resStatus, error){
20 var errraw;
30 var statusCode = 0;
4 if(req){
5 try{
60 statusCode = req.status;
70 var res = JSON.parse(req.responseText);
80 errraw = res.error || res.msg || res;
9 if (errraw instanceof Array) {
100 errraw = errraw.join('\n');
11 }
12 } catch(e){
130 errraw = req.responseText;
14 }
15 }
16 if(fail){
170 fail(errraw, {
18 status: statusCode,
19 message: resStatus,
20 error: error
21 });
22 }
23};
24

src/modules/hosts.js

45%
44
20
24
LineHitsSource
11var constants = require("./constants");
21var appProps = require("./appProps");
3
4function removeEndSlash(input){
51 var ret = input;
6 if(ret.charAt(ret.length - 1) === "/"){
70 ret = ret.substring(0, ret.length-1);
8 }
91 return ret;
10}
11
12function removeStartSlash(input){
130 var ret = input;
14 if(ret.length > 1 && ret.charAt(0) === "/"){
150 ret = ret.substring(1, ret.length);
16 }
170 return ret;
18}
19
20function CloudHost(cloud_props){
211 this.cloud_props = cloud_props;
221 this.cloud_host = undefined;
231 this.app_env = null;
241 this.isLegacy = false;
25}
26
271CloudHost.prototype.getHost = function(appType){
28 if(this.cloud_host){
290 return this.cloud_host;
30 } else {
311 var url;
321 var app_type;
33 if(this.cloud_props && this.cloud_props.hosts){
341 url = this.cloud_props.hosts.url;
35
36 if (typeof url === 'undefined') {
37 // resolve url the old way i.e. depending on
38 // -burnt in app mode
39 // -returned dev or live url
40 // -returned dev or live type (node or fh(rhino or proxying))
410 var cloud_host = this.cloud_props.hosts.releaseCloudUrl;
420 app_type = this.cloud_props.hosts.releaseCloudType;
43
44 if(typeof appType !== "undefined" && appType.indexOf("dev") > -1){
450 cloud_host = this.cloud_props.hosts.debugCloudUrl;
460 app_type = this.cloud_props.hosts.debugCloudType;
47 }
480 url = cloud_host;
49 }
50 }
511 url = removeEndSlash(url);
521 this.cloud_host = url;
53 if(app_type === "fh"){
540 this.isLegacy = true;
55 }
561 return url;
57 }
58};
59
601CloudHost.prototype.getActUrl = function(act){
610 var app_props = appProps.getAppProps() || {};
62 if(typeof this.cloud_host === "undefined"){
630 this.getHost(app_props.mode);
64 }
65 if(this.isLegacy){
660 return this.cloud_host + constants.boxprefix + "act/" + this.cloud_props.domain + "/" + app_props.appid + "/" + act + "/" + app_props.appid;
67 } else {
680 return this.cloud_host + "/cloud/" + act;
69 }
70};
71
721CloudHost.prototype.getMBAASUrl = function(service){
730 var app_props = appProps.getAppProps() || {};
74 if(typeof this.cloud_host === "undefined"){
750 this.getHost(app_props.mode);
76 }
770 return this.cloud_host + "/mbaas/" + service;
78};
79
801CloudHost.prototype.getCloudUrl = function(path){
810 var app_props = appProps.getAppProps() || {};
82 if(typeof this.cloud_host === "undefined"){
830 this.getHost(app_props.mode);
84 }
850 return this.cloud_host + "/" + removeStartSlash(path);
86};
87
881CloudHost.prototype.getEnv = function(){
89 if(this.app_env){
900 return this.app_env;
91 } else {
92 if(this.cloud_props && this.cloud_props.hosts){
930 this.app_env = this.cloud_props.hosts.environment;
94 }
95 }
960 return this.app_env;
97};
98
991module.exports = CloudHost;

src/modules/initializer.js

39%
48
19
29
LineHitsSource
11var loadScript = require("./loadScript");
21var consts = require("./constants");
31var fhparams = require("./fhparams");
41var ajax = require("./ajax");
51var handleError = require("./handleError");
61var logger = require("./logger");
71var hashFunc = require("./security/hash");
81var appProps = require("./appProps");
91var constants = require("./constants");
101var events = require("./events");
111var data = require('./data');
12
131var init = function(cb) {
141 appProps.load(function(err, data) {
15 if (err) {
160 return cb(err);
17 }
18 // Emit internal config loaded event - SDK will now set appprops
191 events.emit(constants.INTERNAL_CONFIG_LOADED_EVENT, null, data);
201 return loadCloudProps(data, cb);
21 });
22};
23
241var loadCloudProps = function(app_props, callback) {
25 if (app_props.loglevel) {
260 logger.setLevel(app_props.loglevel);
27 }
28 // If local - shortcircuit the init - just return the host
29 if (app_props.local) {
301 var res = {
31 "domain": "local",
32 "firstTime": false,
33 "hosts": {
34 "debugCloudType": "node",
35 "debugCloudUrl": app_props.host,
36 "releaseCloudType": "node",
37 "releaseCloudUrl": app_props.host,
38 "type": "cloud_nodejs",
39 "url": app_props.host
40 },
41 "init": {
42 "trackId": "000000000000000000000000"
43 },
44 "status": "ok"
45 };
46
471 return callback(null, {
48 cloud: res
49 });
50 }
51
52
53 //now we have app props, add the fileStorageAdapter
540 data.addFileStorageAdapter(app_props, hashFunc);
550 var doInit = function(path, appProps, savedHost, storage) {
560 var data = fhparams.buildFHParams();
57
580 ajax({
59 "url": path,
60 "type": "POST",
61 "tryJSONP": true,
62 "dataType": "json",
63 "contentType": "application/json",
64 "data": JSON.stringify(data),
65 "timeout": appProps.timeout,
66 "success": function(initRes) {
67 if (storage) {
680 storage.save({
69 key: "fh_init",
70 value: initRes
71 }, function() {});
72 }
73 if (callback) {
740 callback(null, {
75 cloud: initRes
76 });
77 }
78 },
79 "error": function(req, statusText, error) {
800 var errormsg = "unknown";
81 if (req) {
820 errormsg = req.status + " - " + req.responseText;
83 }
840 logger.error("App init returned error : " + errormsg);
85 //use the cached host if we have a copy
86 if (savedHost && req.status !== 400) {
870 logger.info("Using cached host: " + JSON.stringify(savedHost));
88 if (callback) {
890 callback(null, {
90 cloud: savedHost
91 });
92 }
93 } else {
94 if (req.status === 400) {
950 logger.error(req.responseText);
96 } else {
970 logger.error("No cached host found. Init failed.");
98 }
990 handleError(function(msg, err) {
100 if (callback) {
1010 callback({
102 error: err,
103 message: msg
104 });
105 }
106 }, req, statusText, error);
107 }
108 }
109 });
110 };
111
1120 var storage = null;
1130 var path = app_props.host + consts.boxprefix + "app/init";
114 try {
1150 storage = data.getStorage("fh_init_storage", typeof Titanium !== "undefined"?['titanium']:null);
1160 storage.get('fh_init', function(storage_res) {
1170 var savedHost = null;
118 if (storage_res && storage_res.value !== null && typeof(storage_res.value) !== "undefined" && storage_res !== "") {
1190 storage_res = typeof(storage_res) === "string" ? JSON.parse(storage_res) : storage_res;
1200 storage_res.value = typeof(storage_res.value) === "string" ? JSON.parse(storage_res.value) : storage_res.value;
121 if (storage_res.value.init) {
1220 app_props.init = storage_res.value.init;
123 } else {
124 //keep it backward compatible.
1250 app_props.init = typeof(storage_res.value) === "string" ? JSON.parse(storage_res.value) : storage_res.value;
126 }
127 if (storage_res.value.hosts) {
1280 savedHost = storage_res.value;
129 }
130 }
131
1320 doInit(path, app_props, savedHost, storage);
133 });
134 } catch (e) {
135 //for whatever reason (e.g. localStorage is disabled) Lawnchair is failed to init, just do the init
1360 doInit(path, app_props, null, null);
137 }
138};
139
1401module.exports = {
141 "init": init,
142 "loadCloudProps": loadCloudProps
143};
144

src/modules/lawnchair-ext.js

3%
65
2
63
LineHitsSource
11var fileStorageAdapter = function (app_props, hashFunc) {
2 // private methods
3
4 function doLog(mess){
5 if(console){
60 console.log(mess);
7 }
8 }
9
100 var fail = function (e, i) {
11 if(console) {
120 console.log('error in file system adapter !', e, i);
13 } else {
140 throw e;
15 }
16 };
17
18
19 function filenameForKey(key, cb) {
200 key = app_props.appid + key;
21
220 hashFunc({
23 algorithm: "MD5",
24 text: key
25 }, function(result) {
260 var filename = result.hashvalue + '.txt';
27 if (typeof navigator.externalstorage !== "undefined") {
280 navigator.externalstorage.enable(function handleSuccess(res){
290 var path = filename;
30 if(res.path ) {
310 path = res.path;
32 if(!path.match(/\/$/)) {
330 path += '/';
34 }
350 path += filename;
36 }
370 filename = path;
380 return cb(filename);
39 },function handleError(err){
400 return cb(filename);
41 });
42 } else {
430 doLog('filenameForKey key=' + key+ ' , Filename: ' + filename);
440 return cb(filename);
45 }
46 });
47 }
48
490 return {
50
510 valid: function () { return !!(window.requestFileSystem); },
52
53 init : function (options, callback){
54 //calls the parent function fn and applies this scope
55 if(options && 'function' === typeof options.fail ) {
560 fail = options.fail;
57 }
58 if (callback) {
590 this.fn(this.name, callback).call(this, this);
60 }
61 },
62
63 keys: function (callback){
640 throw "Currently not supported";
65 },
66
67 save : function (obj, callback){
680 var key = obj.key;
690 var value = obj.val||obj.value;
700 filenameForKey(key, function(hash) {
710 window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function gotFS(fileSystem) {
72
730 fileSystem.root.getFile(hash, {
74 create: true
75 }, function gotFileEntry(fileEntry) {
760 fileEntry.createWriter(function gotFileWriter(writer) {
770 writer.onwrite = function() {
780 return callback({
79 key: key,
80 val: value
81 });
82 };
830 writer.write(value);
84 }, function() {
850 fail('[save] Failed to create file writer');
86 });
87 }, function() {
880 fail('[save] Failed to getFile');
89 });
90 }, function() {
910 fail('[save] Failed to requestFileSystem');
92 });
93 });
94 },
95
96 batch : function (records, callback){
970 throw "Currently not supported";
98 },
99
100 get : function (key, callback){
1010 filenameForKey(key, function(hash) {
1020 window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function gotFS(fileSystem) {
1030 fileSystem.root.getFile(hash, {}, function gotFileEntry(fileEntry) {
1040 fileEntry.file(function gotFile(file) {
1050 var reader = new FileReader();
1060 reader.onloadend = function (evt) {
1070 var text = evt.target.result;
108 // Check for URLencoded
109 // PG 2.2 bug in readAsText()
110 try {
1110 text = decodeURIComponent(text);
112 } catch (e) {
113 // Swallow exception if not URLencoded
114 // Just use the result
115 }
1160 return callback({
117 key: key,
118 val: text
119 });
120 };
1210 reader.readAsText(file);
122 }, function() {
1230 fail('[load] Failed to getFile');
124 });
125 }, function() {
126 // Success callback on key load failure
1270 callback({
128 key: key,
129 val: null
130 });
131 });
132 }, function() {
1330 fail('[load] Failed to get fileSystem');
134 });
135 });
136 },
137
138 exists : function (key, callback){
1390 filenameForKey(key,function (hash){
1400 window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function gotFS(fileSystem) {
1410 fileSystem.root.getFile(hash, {},
142 function gotFileEntry(fileEntry) {
1430 return callback(true);
144 }, function (err){
1450 return callback(false);
146 });
147 });
148 });
149 },
150
151 all : function (callback){
1520 throw "Currently not supported";
153 },
154
155 remove : function (key, callback){
1560 filenameForKey(key, function(hash) {
157
1580 window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function gotFS(fileSystem) {
1590 fileSystem.root.getFile(hash, {}, function gotFileEntry(fileEntry) {
160
1610 fileEntry.remove(function() {
1620 return callback({
163 key: key,
164 val: null
165 });
166 }, function() {
1670 fail('[remove] Failed to remove file');
168 });
169 }, function() {
1700 fail('[remove] Failed to getFile');
171 });
172 }, function() {
1730 fail('[remove] Failed to get fileSystem');
174 });
175 });
176 },
177
178 nuke : function (callback){
1790 throw "Currently not supported";
180 }
181
182
183 };
184};
185
1861module.exports = {
187 fileStorageAdapter: fileStorageAdapter
188};

src/modules/loadScript.js

7%
13
1
12
LineHitsSource
11module.exports = function (url, callback) {
20 var script;
30 var head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;
40 script = document.createElement("script");
50 script.async = "async";
60 script.src = url;
70 script.type = "text/javascript";
80 script.onload = script.onreadystatechange = function () {
9 if (!script.readyState || /loaded|complete/.test(script.readyState)) {
100 script.onload = script.onreadystatechange = null;
11 if (head && script.parentNode) {
120 head.removeChild(script);
13 }
140 script = undefined;
15 if (callback && typeof callback === "function") {
160 callback();
17 }
18 }
19 };
200 head.insertBefore(script, head.firstChild);
21};
22

src/modules/logger.js

100%
4
4
0
LineHitsSource
12var console = require('console');
22var log = require('loglevel');
3
42log.setLevel('info');
5
6/**
7 * APIs:
8 * see https://github.com/pimterry/loglevel.
9 * In short, you can use:
10 * log.setLevel(loglevel) - default to info
11 * log.enableAll() - enable all log messages
12 * log.disableAll() - disable all log messages
13 *
14 * log.trace(msg)
15 * log.debug(msg)
16 * log.info(msg)
17 * log.warn(msg)
18 * log.error(msg)
19 *
20 * Available levels: { "TRACE": 0, "DEBUG": 1, "INFO": 2, "WARN": 3, "ERROR": 4, "SILENT": 5}
21 * Use either string or integer value
22 */
232module.exports = log;

src/modules/queryMap.js

100%
10
10
0
LineHitsSource
12module.exports = function(url) {
24 var qmap = {};
34 var i = url.split("?");
4 if (i.length === 2) {
54 var queryString = i[1];
64 var pairs = queryString.split("&");
74 qmap = {};
8 for (var p = 0; p < pairs.length; p++) {
98 var q = pairs[p];
108 var qp = q.split("=");
118 qmap[qp[0]] = qp[1];
12 }
13 }
144 return qmap;
15};

src/modules/sdkversion.js

33%
6
2
4
LineHitsSource
11var constants = require("./constants");
2
31module.exports = function() {
40 var type = "FH_JS_SDK";
5 if (typeof window.fh_destination_code !== 'undefined') {
60 type = "FH_HYBRID_SDK";
7 } else if(window.PhoneGap || window.cordova) {
80 type = "FH_PHONEGAP_SDK";
9 }
100 return type + "/" + constants.sdk_version;
11};
12

src/modules/security/aes-keygen.js

30%
20
6
14
LineHitsSource
11var rsa = require("../../../libs/rsa");
21var SecureRandom = rsa.SecureRandom;
31var byte2Hex = rsa.byte2Hex;
4
51var generateRandomKey = function(keysize){
60 var r = new SecureRandom();
70 var key = new Array(keysize);
80 r.nextBytes(key);
90 var result = "";
10 for(var i=0;i<key.length;i++){
110 result += byte2Hex(key[i]);
12 }
130 return result;
14};
15
161var aes_keygen = function(p, s, f){
17 if (!p.params.keysize) {
180 f('no_params_keysize', {}, p);
190 return;
20 }
21 if (p.params.algorithm.toLowerCase() !== "aes") {
220 f('keygen_bad_algorithm', {}, p);
230 return;
24 }
250 var keysize = parseInt(p.params.keysize, 10);
26 //keysize is in bit, need to convert to bytes to generate random key
27 //but the legacy code has a bug, it doesn't do the convert, so if the keysize is less than 100, don't convert
28 if(keysize > 100){
290 keysize = keysize/8;
30 }
31 if(typeof SecureRandom === "undefined"){
320 return f("security library is not loaded.");
33 }
340 return s({
35 'algorithm': 'AES',
36 'secretkey': generateRandomKey(keysize),
37 'iv': generateRandomKey(keysize)
38 });
39};
40
411module.exports = aes_keygen;

src/modules/security/aes-node.js

20%
20
4
16
LineHitsSource
11var CryptoJS = require("../../../libs/generated/crypto");
2
31var encrypt = function(p, s, f){
40 var fields = ['key', 'plaintext', 'iv'];
5 if(p.params.algorithm.toLowerCase() !== "aes"){
60 return f('encrypt_bad_algorithm', {}, p);
7 }
8 for (var i = 0; i < fields; i++) {
90 var field = fields[i];
10 if (!p.params[field]) {
110 return f('no_params_' + field, {}, p);
12 }
13 }
140 var encrypted = CryptoJS.AES.encrypt(p.params.plaintext, CryptoJS.enc.Hex.parse(p.params.key), {iv: CryptoJS.enc.Hex.parse(p.params.iv)});
150 cipher_text = CryptoJS.enc.Hex.stringify(encrypted.ciphertext);
160 return s({ciphertext: cipher_text});
17};
18
191var decrypt = function(p, s, f){
200 var fields = ['key', 'ciphertext', 'iv'];
21 if(p.params.algorithm.toLowerCase() !== "aes"){
220 return f('decrypt_bad_algorithm', {}, p);
23 }
24 for (var i = 0; i < fields; i++) {
250 var field = fields[i];
26 if (!p.params[field]) {
270 return f('no_params_' + field, {}, p);
28 }
29 }
300 var data = CryptoJS.enc.Hex.parse(p.params.ciphertext);
310 var encodeData = CryptoJS.enc.Base64.stringify(data);
320 var decrypted = CryptoJS.AES.decrypt(encodeData, CryptoJS.enc.Hex.parse(p.params.key), {iv: CryptoJS.enc.Hex.parse(p.params.iv)});
33
34 try {
350 return s({plaintext:decrypted.toString(CryptoJS.enc.Utf8)});
36 } catch (e) {
370 return f(e);
38 }
39};
40
411module.exports = {
42 encrypt: encrypt,
43 decrypt: decrypt
44};
45

src/modules/security/hash.js

25%
12
3
9
LineHitsSource
11var CryptoJS = require("../../../libs/generated/crypto");
2
3
41var hash = function(p, s, f){
5 if (!p.params.text) {
60 f('hash_no_text', {}, p);
70 return;
8 }
90 var hashValue;
10 if (p.params.algorithm.toLowerCase() === "md5") {
110 hashValue = CryptoJS.MD5(p.params.text).toString(CryptoJS.enc.Hex);
12 } else if(p.params.algorithm.toLowerCase() === "sha1"){
130 hashValue = CryptoJS.SHA1(p.params.text).toString(CryptoJS.enc.Hex);
14 } else if(p.params.algorithm.toLowerCase() === "sha256"){
150 hashValue = CryptoJS.SHA256(p.params.text).toString(CryptoJS.enc.Hex);
16 } else if(p.params.algorithm.toLowerCase() === "sha512"){
170 hashValue = CryptoJS.SHA512(p.params.text).toString(CryptoJS.enc.Hex);
18 } else {
190 return f("hash_unsupported_algorithm: " + p.params.algorithm);
20 }
210 return s({"hashvalue": hashValue});
22};
23
241module.exports = hash;

src/modules/security/rsa-node.js

30%
13
4
9
LineHitsSource
11var rsa = require("../../../libs/rsa");
21var RSAKey = rsa.RSAKey;
3
41var encrypt = function(p, s, f){
50 var fields = ['modulu', 'plaintext'];
6 if(p.params.algorithm.toLowerCase() !== "rsa"){
70 return f('encrypt_bad_algorithm', {}, p);
8 }
9 for (var i = 0; i < fields; i++) {
100 var field = fields[i];
11 if (!p.params[field]) {
120 return f('no_params_' + field, {}, p);
13 }
14 }
150 var key = new RSAKey();
160 key.setPublic(p.params.modulu, "10001");
170 var ori_text = p.params.plaintext;
180 cipher_text = key.encrypt(ori_text);
190 return s({ciphertext:cipher_text});
20};
21
221module.exports = {
23 encrypt: encrypt
24};

src/modules/sync_cloud_handler.js

66%
3
2
1
LineHitsSource
11var cloudAPI = require("./api_cloud");
2
31module.exports = function (params, success, failure) {
40 cloudAPI({
5 'path': '/mbaas/sync/' + params.dataset_id,
6 'method': 'post',
7 'data': params.req
8 }, success, failure);
9};

src/modules/uuid.js

12%
8
1
7
LineHitsSource
11module.exports = {
2 createUUID : function () {
3 //from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
4 //based on RFC 4122, section 4.4 (Algorithms for creating UUID from truely random pr pseudo-random number)
50 var s = [];
60 var hexDigitals = "0123456789ABCDEF";
7 for (var i = 0; i < 32; i++) {
80 s[i] = hexDigitals.substr(Math.floor(Math.random() * 0x10), 1);
9 }
100 s[12] = "4";
110 s[16] = hexDigitals.substr((s[16] & 0x3) | 0x8, 1);
120 var uuid = s.join("");
130 return uuid;
14 }
15};
16

src/modules/waitForCloud.js

72%
50
36
14
LineHitsSource
11var initializer = require("./initializer");
21var events = require("./events");
31var CloudHost = require("./hosts");
41var constants = require("./constants");
51var logger = require("./logger");
61var data = require('./data');
71var fhparams = require('./fhparams');
8
9//the cloud configurations
101var cloud_host;
11
121var is_initialising = false;
131var is_cloud_ready = false;
141var init_error = null;
15
16
171var ready = function(cb){
18 if(is_cloud_ready){
190 return cb(null, {host: getCloudHostUrl()});
20 } else {
211 events.once(constants.INIT_EVENT, function(err, host){
221 return cb(err, host);
23 });
24 if(!is_initialising){
251 is_initialising = true;
261 var fhinit = function(){
271 data.sessionManager.read(function(err, session){
28 //load the persisted sessionToken and set it for the session
29 if(session && session.sessionToken){
300 fhparams.setAuthSessionToken(session.sessionToken);
31 }
321 initializer.init(function(err, initRes){
331 is_initialising = false;
34 if(err){
350 init_error = err;
360 return events.emit(constants.INIT_EVENT, err);
37 } else {
381 init_error = null;
391 is_cloud_ready = true;
401 cloud_host = new CloudHost(initRes.cloud);
411 return events.emit(constants.INIT_EVENT, null, {host: getCloudHostUrl()});
42 }
43 });
44 });
45 };
46 if(typeof window.cordova !== "undefined" || typeof window.phonegap !== "undefined"){
47 //if we are running inside cordova/phonegap, only init after device is ready to ensure the device id is the right one
480 document.addEventListener("deviceready", fhinit, false);
49 } else {
501 fhinit();
51 }
52 }
53 }
54};
55
561var getCloudHost = function(){
570 return cloud_host;
58};
59
601var getCloudHostUrl = function(){
61 if(typeof cloud_host !== "undefined"){
621 var appProps = require("./appProps").getAppProps();
631 return cloud_host.getHost(appProps.mode);
64 } else {
650 return undefined;
66 }
67};
68
691var isReady = function(){
701 return is_cloud_ready;
71};
72
731var getInitError = function(){
741 return init_error;
75};
76
77//for test
781var reset = function(){
790 is_cloud_ready = false;
800 is_initialising = false;
810 cloud_host = undefined;
820 init_error = undefined;
830 ready(function(){
84
85 });
86};
87
881ready(function(error, host){
89 if(error){
90 if(error.message !== "app_config_missing"){
910 logger.error("Failed to initialise fh.");
92 } else {
930 logger.info("No fh config file");
94 }
95 } else {
961 logger.info("fh cloud is ready");
97 }
98});
99
1001module.exports = {
101 ready: ready,
102 isReady: isReady,
103 getCloudHost: getCloudHost,
104 getCloudHostUrl: getCloudHostUrl,
105 getInitError: getInitError,
106 reset: reset
107};