api.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. var util = require("util");
  2. var BaseClient = require("./client");
  3. var extend = require("./extend");
  4. function ApiBaseClient(settings) {
  5. this.server = {
  6. host: "api.browserstack.com"
  7. };
  8. BaseClient.call(this, settings);
  9. }
  10. util.inherits(ApiBaseClient, BaseClient);
  11. // public API
  12. extend(ApiBaseClient.prototype, {
  13. getBrowsers: function(fn) {
  14. this._getBrowsers(function(error, browsers) {
  15. if (!error) {
  16. this.updateLatest(browsers);
  17. }
  18. fn(error, browsers);
  19. }.bind(this));
  20. },
  21. createWorker: function(options, fn) {
  22. if (options[this.versionField] === "latest") {
  23. return this.getLatest(options, function(error, version) {
  24. if (error) {
  25. return fn(error);
  26. }
  27. options = extend({}, options);
  28. options[this.versionField] = version;
  29. this.createWorker(options, fn);
  30. }.bind(this));
  31. }
  32. var data = JSON.stringify(options);
  33. this.request({
  34. path: this.path("/worker"),
  35. method: "POST"
  36. }, data, fn);
  37. },
  38. getWorker: function(id, fn) {
  39. this.request({
  40. path: this.path("/worker/" + id)
  41. }, fn);
  42. },
  43. changeUrl: function(id, options, fn) {
  44. var data = JSON.stringify(options);
  45. this.request({
  46. path: this.path("/worker/" + id + "/url.json"),
  47. method: "PUT"
  48. }, data, fn);
  49. },
  50. terminateWorker: function(id, fn) {
  51. this.request({
  52. path: this.path("/worker/" + id),
  53. method: "DELETE"
  54. }, fn);
  55. },
  56. getWorkers: function(fn) {
  57. this.request({
  58. path: this.path("/workers")
  59. }, fn);
  60. },
  61. getLatest: function(browser, fn) {
  62. var latest = this.latest;
  63. if (typeof browser === "function") {
  64. fn = browser;
  65. browser = null;
  66. }
  67. // there may be a lot of createWorker() calls with "latest" version
  68. // so minimize the number of calls to getBrowsers()
  69. if (this.latestPending) {
  70. return setTimeout(function() {
  71. this.getLatest(browser, fn);
  72. }.bind(this), 50);
  73. }
  74. // only cache browsers for one day
  75. if (!latest || this.latestUpdate < (new Date() - 864e5)) {
  76. this.latestPending = true;
  77. return this.getBrowsers(function(error) {
  78. this.latestPending = false;
  79. if (error) {
  80. return fn(error);
  81. }
  82. this.getLatest(browser, fn);
  83. }.bind(this));
  84. }
  85. process.nextTick(function() {
  86. fn(null, browser ? latest[this.getBrowserId(browser)] : extend({}, latest));
  87. }.bind(this));
  88. },
  89. takeScreenshot: function(id, fn) {
  90. this.request({
  91. path: this.path("/worker/" + id + "/screenshot.json")
  92. }, fn);
  93. }
  94. });
  95. // internal API
  96. extend(ApiBaseClient.prototype, {
  97. latest: null,
  98. latestUpdate: 0,
  99. latestPending: false,
  100. path: function(path) {
  101. return "/" + this.version + path;
  102. },
  103. updateLatest: function(browsers) {
  104. var latest = this.latest = {};
  105. var getBrowserId = this.getBrowserId.bind(this);
  106. var versionField = this.versionField;
  107. this.latestUpdate = new Date();
  108. browsers.forEach(function(browser) {
  109. var version = browser[versionField];
  110. var browserId = getBrowserId(browser);
  111. // ignore devices that don't have versions
  112. if (!version) {
  113. return;
  114. }
  115. // ignore pre-release versions
  116. if (/\s/.test(version)) {
  117. return;
  118. }
  119. if (parseFloat(version) > (parseFloat(latest[browserId]) || 0)) {
  120. latest[browserId] = version;
  121. }
  122. });
  123. },
  124. getBrowserId: function(browser) {
  125. return this._getBrowserId(browser).toLowerCase();
  126. }
  127. });
  128. // Versions
  129. ApiBaseClient.versions = {};
  130. ApiBaseClient.latestVersion = 0;
  131. ApiBaseClient.createVersion = function(version, prototype) {
  132. function ApiClient(settings) {
  133. ApiBaseClient.call(this, settings);
  134. }
  135. util.inherits(ApiClient, ApiBaseClient);
  136. ApiClient.prototype.version = version;
  137. extend(ApiClient.prototype, prototype);
  138. ApiBaseClient.versions[version] = ApiClient;
  139. ApiBaseClient.latestVersion = Math.max(ApiBaseClient.latestVersion, version);
  140. };
  141. ApiBaseClient.createVersion(1, {
  142. useHttp: true,
  143. versionField: "version",
  144. _getBrowsers: function(fn) {
  145. this.request({
  146. path: this.path("/browsers")
  147. }, fn);
  148. },
  149. _getBrowserId: function(browser) {
  150. return browser.browser;
  151. }
  152. });
  153. ApiBaseClient.createVersion(2, {
  154. useHttp: true,
  155. versionField: "version",
  156. _getBrowsers: function(fn) {
  157. this.request({
  158. path: this.path("/browsers")
  159. }, function(error, osBrowsers) {
  160. if (error) {
  161. return fn(error);
  162. }
  163. fn(null, [].concat.apply([],
  164. Object.keys(osBrowsers).map(function(os) {
  165. return osBrowsers[os].map(function(browser) {
  166. browser.os = os;
  167. return browser;
  168. });
  169. })
  170. ));
  171. });
  172. },
  173. _getBrowserId: function(browser) {
  174. return browser.os + ":" + (browser.browser || browser.device);
  175. }
  176. });
  177. ApiBaseClient.createVersion(3, {
  178. useHttp: true,
  179. versionField: "browser_version",
  180. _getBrowsers: function(fn) {
  181. this.request({
  182. path: this.path("/browsers?flat=true")
  183. }, fn);
  184. },
  185. _getBrowserId: function(browser) {
  186. var id = browser.os + ":" + browser.os_version + ":" + browser.browser;
  187. if (browser.device) {
  188. id += ":" + browser.device;
  189. }
  190. return id;
  191. },
  192. getApiStatus: function(fn) {
  193. this.request({
  194. path: this.path("/status")
  195. }, fn);
  196. }
  197. });
  198. ApiBaseClient.createVersion(4, {
  199. versionField: "browser_version",
  200. _getBrowsers: function(fn) {
  201. this.request({
  202. path: this.path("/browsers?flat=true")
  203. }, fn);
  204. },
  205. _getBrowserId: function(browser) {
  206. var id = browser.os + ":" + browser.os_version + ":" + browser.browser;
  207. if (browser.device) {
  208. id += ":" + browser.device;
  209. }
  210. return id;
  211. },
  212. getApiStatus: function(fn) {
  213. this.request({
  214. path: this.path("/status")
  215. }, fn);
  216. }
  217. });
  218. module.exports = {
  219. createClient: function(settings) {
  220. var ApiClient = ApiBaseClient.versions[settings.version || ApiBaseClient.latestVersion];
  221. if (!ApiClient) {
  222. throw new Error("Invalid version");
  223. }
  224. return new ApiClient(settings);
  225. }
  226. };