UNPKG

6.81 kBJavaScriptView Raw
1var http = require( "http" ),
2 querystring = require( "querystring" ),
3 util = require( "util" ),
4 userAgent = getUA();
5
6function getUA() {
7 var os = require( "os" ),
8 version = require( "../package.json" ).version;
9 return os.platform() + "/" + os.release() + " " +
10 "node/" + process.versions.node + " " +
11 "node-browserstack/" + version;
12}
13
14function extend( a, b ) {
15 for ( var p in b ) {
16 a[ p ] = b[ p ];
17 }
18
19 return a;
20}
21
22function Client( settings ) {
23 if ( !settings.username ) {
24 throw new Error( "Username is required." );
25 }
26 if ( !settings.password ) {
27 throw new Error( "Password is required." );
28 }
29
30 extend( this, settings );
31 this.authHeader = "Basic " +
32 new Buffer( this.username + ":" + this.password ).toString( "base64" );
33
34 this.server = extend({
35 host: "api.browserstack.com",
36 port: 80
37 }, this.server || {});
38}
39
40
41
42// public API
43extend( Client.prototype, {
44 getBrowsers: function( fn ) {
45 this._getBrowsers( function( error, browsers ) {
46 if ( !error ) {
47 this.updateLatest( browsers );
48 }
49
50 fn( error, browsers );
51 }.bind( this ) );
52 },
53
54 createWorker: function( options, fn ) {
55 if ( options[ this.versionField ] === "latest" ) {
56 return this.getLatest( options, function( error, version ) {
57 if ( error ) {
58 return fn( error );
59 }
60
61 options = extend( {}, options );
62 options[ this.versionField ] = version;
63 this.createWorker( options, fn );
64 }.bind( this ) );
65 }
66
67 var data = querystring.stringify( options );
68 this.request({
69 path: this.path( "/worker" ),
70 method: "POST"
71 }, data, fn );
72 },
73
74 getWorker: function( id, fn ) {
75 this.request({
76 path: this.path( "/worker/" + id )
77 }, fn );
78 },
79
80 terminateWorker: function( id, fn ) {
81 this.request({
82 path: this.path( "/worker/" + id ),
83 method: "DELETE"
84 }, fn );
85 },
86
87 getWorkers: function( fn ) {
88 this.request({
89 path: this.path( "/workers" )
90 }, fn );
91 },
92
93 getLatest: function( browser, fn ) {
94 var latest = this.latest,
95 browserId = this.getBrowserId( browser );
96
97 if ( typeof browser === "function" ) {
98 fn = browser;
99 browser = null;
100 }
101
102 // there may be a lot of createWorker() calls with "latest" version
103 // so minimize the number of calls to getBrowsers()
104 if ( this.latestPending ) {
105 return setTimeout(function() {
106 this.getLatest( browser, fn );
107 }.bind( this ), 50 );
108 }
109
110 // only cache browsers for one day
111 if ( !latest || this.latestUpdate < (new Date() - 864e5) ) {
112 this.latestPending = true;
113 return this.getBrowsers(function( error ) {
114 this.latestPending = false;
115
116 if ( error ) {
117 return fn( error );
118 }
119
120 this.getLatest( browser, fn );
121 }.bind( this ) );
122 }
123
124 process.nextTick(function() {
125 fn( null, browser ? latest[ browserId ] : extend( {}, latest ) );
126 });
127 },
128
129 takeScreenshot: function( id, fn ) {
130 this.request({
131 path: this.path( "/worker/" + id + "/screenshot.json" )
132 }, fn );
133 }
134});
135
136
137
138// internal API
139extend( Client.prototype, {
140 latest: null,
141 latestUpdate: 0,
142 latestPending: false,
143
144 path: function( path ) {
145 return "/" + this.version + path;
146 },
147
148 request: function( options, data, fn ) {
149 if ( typeof data === "function" ) {
150 fn = data;
151 data = null;
152 }
153 fn = fn || function() {};
154
155 var req = http.request( extend({
156 host: this.server.host,
157 port: this.server.port,
158 method: "GET",
159 headers: {
160 authorization: this.authHeader,
161 "user-agent": userAgent,
162 "content-length": typeof data === "string" ? data.length : 0
163 }
164 }, options ), function( res ) {
165 var response = "";
166 res.setEncoding( "utf8" );
167 res.on( "data", function( chunk ) {
168 response += chunk;
169 });
170 res.on( "end", function() {
171 if ( res.statusCode !== 200 ) {
172 var message;
173 if ( res.headers[ "content-type" ].indexOf( "json" ) !== -1 ) {
174 response = JSON.parse( response );
175 message = response.message;
176 message += " - " + response.errors.map(function( error ) {
177 return "`" + error.field + "` " + error.code;
178 }).join( ", " );
179 } else {
180 message = response;
181 }
182 if ( !message && res.statusCode === 403 ) {
183 message = "Forbidden";
184 }
185 fn( new Error( message ) );
186 } else {
187 fn( null, JSON.parse( response ) );
188 }
189 });
190 });
191
192 if ( data ) {
193 req.write( data );
194 }
195 req.end();
196 },
197
198 updateLatest: function( browsers ) {
199 var latest = this.latest = {},
200 getBrowserId = this.getBrowserId,
201 versionField = this.versionField;
202
203 this.latestUpdate = new Date();
204 browsers.forEach(function( browser ) {
205 var version = browser[ versionField ],
206 browserId = getBrowserId( browser );
207
208 // ignore devices that don't have versions
209 if ( !version ) {
210 return;
211 }
212
213 // ignore pre-release versions
214 if ( /\s/.test( version ) ) {
215 return;
216 }
217
218 if ( parseFloat( version ) >
219 (parseFloat( latest[ browserId ] ) || 0) ) {
220 latest[ browserId ] = version;
221 }
222 });
223 }
224});
225
226
227
228// Versions
229
230Client.versions = {};
231Client.latestVersion = 0;
232Client.createVersion = function( version, prototype ) {
233 function Version( settings ) {
234 Client.call( this, settings );
235 }
236 util.inherits( Version, Client );
237
238 Version.prototype.version = version;
239 extend( Version.prototype, prototype );
240
241 Client.versions[ version ] = Version;
242 Client.latestVersion = Math.max( Client.latestVersion, version );
243};
244
245Client.createVersion( 1, {
246 versionField: "version",
247
248 _getBrowsers: function( fn ) {
249 this.request({
250 path: this.path( "/browsers" )
251 }, fn );
252 },
253
254 getBrowserId: function( browser ) {
255 return browser.browser;
256 }
257});
258
259Client.createVersion( 2, {
260 versionField: "version",
261
262 _getBrowsers: function( fn ) {
263 this.request({
264 path: this.path( "/browsers" )
265 }, function( error, osBrowsers ) {
266 if ( error ) {
267 return fn( error );
268 }
269
270 fn( null, [].concat.apply( [],
271 Object.keys( osBrowsers ).map(function( os ) {
272 return osBrowsers[ os ].map(function( browser ) {
273 browser.os = os;
274 return browser;
275 });
276 })
277 ));
278 });
279 },
280
281 getBrowserId: function( browser ) {
282 return browser.os + ":" + (browser.browser || browser.device);
283 }
284});
285
286Client.createVersion( 3, {
287 versionField: "browser_version",
288
289 _getBrowsers: function( fn ) {
290 this.request({
291 path: this.path( "/browsers?flat=true" )
292 }, fn );
293 },
294
295 getBrowserId: function( browser ) {
296 var id = browser.os + ":" + browser.os_version + ":" + browser.browser;
297 if ( browser.device ) {
298 id += ":" + browser.device;
299 }
300
301 return id;
302 },
303
304 getApiStatus: function( fn ) {
305 this.request({
306 path: this.path( "/status" )
307 }, fn );
308 }
309});
310
311
312module.exports = {
313 createClient: function( settings ) {
314 var Version = Client.versions[ settings.version || Client.latestVersion ];
315 if ( !Version ) {
316 throw new Error( "Invalid version" );
317 }
318
319 return new Version( settings );
320 }
321};