sixpack.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. (function () {
  2. // Object.assign polyfill from https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
  3. Object.assign||Object.defineProperty(Object,"assign",{enumerable:!1,configurable:!0,writable:!0,value:function(e){"use strict";if(void 0===e||null===e)throw new TypeError("Cannot convert first argument to object");for(var r=Object(e),t=1;t<arguments.length;t++){var n=arguments[t];if(void 0!==n&&null!==n){n=Object(n);for(var o=Object.keys(Object(n)),a=0,c=o.length;c>a;a++){var i=o[a],b=Object.getOwnPropertyDescriptor(n,i);void 0!==b&&b.enumerable&&(r[i]=n[i])}}}return r}});
  4. var sixpack = {base_url: "http://localhost:5000", ip_address: null, user_agent: null, timeout: 1000};
  5. // check for node module loader
  6. var on_node = false;
  7. if (typeof module !== "undefined" && typeof require !== "undefined") {
  8. on_node = true;
  9. module.exports = sixpack;
  10. } else {
  11. window["sixpack"] = sixpack;
  12. }
  13. sixpack.generate_client_id = function () {
  14. // from http://stackoverflow.com/questions/105034
  15. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  16. var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
  17. return v.toString(16);
  18. });
  19. };
  20. sixpack.Session = function (options) {
  21. Object.assign(this, sixpack, options);
  22. if (!this.client_id) {
  23. this.client_id = this.generate_client_id();
  24. }
  25. if (!on_node) {
  26. this.user_agent = this.user_agent || (window && window.navigator && window.navigator.userAgent);
  27. }
  28. };
  29. sixpack.Session.prototype = {
  30. participate: function(experiment_name, alternatives, traffic_fraction, force, callback) {
  31. if (typeof traffic_fraction === "function") {
  32. callback = traffic_fraction;
  33. traffic_fraction = null;
  34. force = null;
  35. }
  36. else if (typeof traffic_fraction === "string") {
  37. callback = force;
  38. force = traffic_fraction;
  39. traffic_fraction = null;
  40. }
  41. if (typeof force === "function") {
  42. callback = force;
  43. force = null;
  44. }
  45. if (!(/^[a-z0-9][a-z0-9\-_ ]*$/).test(experiment_name)) {
  46. return callback(new Error("Bad experiment_name"));
  47. }
  48. if (alternatives.length < 2) {
  49. return callback(new Error("Must specify at least 2 alternatives"));
  50. }
  51. for (var i = 0; i < alternatives.length; i += 1) {
  52. if (!(/^[a-z0-9][a-z0-9\-_ ]*$/).test(alternatives[i])) {
  53. return callback(new Error("Bad alternative name: " + alternatives[i]));
  54. }
  55. }
  56. var params = {client_id: this.client_id,
  57. experiment: experiment_name,
  58. alternatives: alternatives};
  59. if (!on_node && force == null) {
  60. var regex = new RegExp("[\\?&]sixpack-force-" + experiment_name + "=([^&#]*)");
  61. var results = regex.exec(window.location.search);
  62. if(results != null) {
  63. force = decodeURIComponent(results[1].replace(/\+/g, " "));
  64. }
  65. }
  66. if (traffic_fraction !== null && !isNaN(traffic_fraction)) {
  67. params.traffic_fraction = traffic_fraction;
  68. }
  69. if (force != null && _in_array(alternatives, force)) {
  70. return callback(null, {"status": "ok", "alternative": {"name": force}, "experiment": {"version": 0, "name": experiment_name}, "client_id": this.client_id});
  71. }
  72. if (this.ip_address) {
  73. params.ip_address = this.ip_address;
  74. }
  75. if (this.user_agent) {
  76. params.user_agent = this.user_agent;
  77. }
  78. return _request(this.base_url + "/participate", params, this.timeout, function(err, res) {
  79. if (err) {
  80. res = {status: "failed",
  81. error: err,
  82. alternative: {name: alternatives[0]}};
  83. }
  84. return callback(null, res);
  85. });
  86. },
  87. convert: function(experiment_name, kpi, callback) {
  88. if (typeof kpi === 'function') {
  89. callback = kpi;
  90. kpi = null;
  91. }
  92. if (!(/^[a-z0-9][a-z0-9\-_ ]*$/).test(experiment_name)) {
  93. return callback(new Error("Bad experiment_name"));
  94. }
  95. var params = {client_id: this.client_id,
  96. experiment: experiment_name};
  97. if (this.ip_address) {
  98. params.ip_address = this.ip_address;
  99. }
  100. if (this.user_agent) {
  101. params.user_agent = this.user_agent;
  102. }
  103. if (kpi) {
  104. params.kpi = kpi;
  105. }
  106. return _request(this.base_url + "/convert", params, this.timeout, function(err, res) {
  107. if (err) {
  108. res = {status: "failed",
  109. error: err,};
  110. }
  111. return callback(null, res);
  112. });
  113. }
  114. };
  115. var counter = 0;
  116. var _request = function(uri, params, timeout, callback) {
  117. var timed_out = false;
  118. var timeout_handle = setTimeout(function () {
  119. timed_out = true;
  120. return callback(new Error("request timed out"));
  121. }, timeout);
  122. if (!on_node) {
  123. var cb = "callback" + (++counter);
  124. params.callback = "sixpack." + cb
  125. sixpack[cb] = function (res) {
  126. if (!timed_out) {
  127. clearTimeout(timeout_handle);
  128. return callback(null, res);
  129. }
  130. }
  131. }
  132. var url = _request_uri(uri, params);
  133. if (!on_node) {
  134. script = document.createElement('script');
  135. script.type = 'text/javascript';
  136. script.src = url;
  137. script.async = true;
  138. document.body.appendChild(script);
  139. } else {
  140. var http = require('http');
  141. var req = http.get(url, function(res) {
  142. var body = "";
  143. res.on('data', function(chunk) {
  144. return body += chunk;
  145. });
  146. return res.on('end', function() {
  147. var data;
  148. if (res.statusCode == 500) {
  149. data = {status: "failed", response: body};
  150. } else {
  151. data = JSON.parse(body);
  152. }
  153. if (!timed_out) {
  154. clearTimeout(timeout_handle);
  155. return callback(null, data);
  156. }
  157. });
  158. });
  159. req.on('error', function(err) {
  160. if (!timed_out) {
  161. clearTimeout(timeout_handle);
  162. return callback(err);
  163. }
  164. });
  165. }
  166. };
  167. var _request_uri = function(endpoint, params) {
  168. var query_string = [];
  169. var e = encodeURIComponent;
  170. for (var key in params) {
  171. if (params.hasOwnProperty(key)) {
  172. var vals = params[key];
  173. if (Object.prototype.toString.call(vals) !== '[object Array]') {
  174. vals = [vals];
  175. }
  176. for (var i = 0; i < vals.length; i += 1) {
  177. query_string.push(e(key) + '=' + e(vals[i]));
  178. }
  179. }
  180. }
  181. if (query_string.length) {
  182. endpoint += '?' + query_string.join('&');
  183. }
  184. return endpoint;
  185. };
  186. var _in_array = function(a, v) {
  187. for(var i = 0; i < a.length; i++) {
  188. if(a[i] === v) {
  189. return true;
  190. }
  191. }
  192. return false;
  193. };
  194. })();