OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. | |
2 // | |
3 // Use of this source code is governed by a BSD-style license | |
4 // that can be found in the LICENSE file in the root of the source | |
5 // tree. An additional intellectual property rights grant can be found | |
6 // in the file PATENTS. All contributing project authors may | |
7 // be found in the AUTHORS file in the root of the source tree. | |
8 // | |
9 // botmanager.js module allows a test to spawn bots that expose an RPC API | |
10 // to be controlled by tests. | |
11 var https = require('https'); | |
12 var fs = require('fs'); | |
13 var child = require('child_process'); | |
14 var Browserify = require('browserify'); | |
15 var Dnode = require('dnode'); | |
16 var Express = require('express'); | |
17 var WebSocketServer = require('ws').Server; | |
18 var WebSocketStream = require('websocket-stream'); | |
19 | |
20 // BotManager runs a HttpsServer that serves bots assets and and WebSocketServer | |
21 // that listens to incoming connections. Once a connection is available it | |
22 // connects it to bots pending endpoints. | |
23 // | |
24 // TODO(andresp): There should be a way to control which bot was spawned | |
25 // and what bot instance it gets connected to. | |
26 BotManager = function () { | |
27 this.webSocketServer_ = null; | |
28 this.bots_ = []; | |
29 this.pendingConnections_ = []; | |
30 this.androidDeviceManager_ = new AndroidDeviceManager(); | |
31 } | |
32 | |
33 BotManager.BotTypes = { | |
34 CHROME : 'chrome', | |
35 ANDROID_CHROME : 'android-chrome', | |
36 }; | |
37 | |
38 BotManager.prototype = { | |
39 createBot_: function (name, botType, callback) { | |
40 switch(botType) { | |
41 case BotManager.BotTypes.CHROME: | |
42 return new BrowserBot(name, callback); | |
43 case BotManager.BotTypes.ANDROID_CHROME: | |
44 return new AndroidChromeBot(name, this.androidDeviceManager_, | |
45 callback); | |
46 default: | |
47 console.log('Error: Type ' + botType + ' not supported by rtc-Bot!'); | |
48 process.exit(1); | |
49 } | |
50 }, | |
51 | |
52 spawnNewBot: function (name, botType, callback) { | |
53 this.startWebSocketServer_(); | |
54 var bot = this.createBot_(name, botType, callback); | |
55 this.bots_.push(bot); | |
56 this.pendingConnections_.push(bot.onBotConnected.bind(bot)); | |
57 }, | |
58 | |
59 startWebSocketServer_: function () { | |
60 if (this.webSocketServer_) return; | |
61 | |
62 this.app_ = new Express(); | |
63 | |
64 this.app_.use('/bot/api.js', | |
65 this.serveBrowserifyFile_.bind(this, | |
66 __dirname + '/bot/api.js')); | |
67 | |
68 this.app_.use('/bot/', Express.static(__dirname + '/bot')); | |
69 | |
70 var options = options = { | |
71 key: fs.readFileSync('configurations/priv.pem', 'utf8'), | |
72 cert: fs.readFileSync('configurations/cert.crt', 'utf8') | |
73 }; | |
74 this.server_ = https.createServer(options, this.app_); | |
75 | |
76 this.webSocketServer_ = new WebSocketServer({ server: this.server_ }); | |
77 this.webSocketServer_.on('connection', this.onConnection_.bind(this)); | |
78 | |
79 this.server_.listen(8080); | |
80 }, | |
81 | |
82 onConnection_: function (ws) { | |
83 var callback = this.pendingConnections_.shift(); | |
84 callback(new WebSocketStream(ws)); | |
85 }, | |
86 | |
87 serveBrowserifyFile_: function (file, request, result) { | |
88 // TODO(andresp): Cache browserify result for future serves. | |
89 var browserify = new Browserify(); | |
90 browserify.add(file); | |
91 browserify.bundle().pipe(result); | |
92 } | |
93 } | |
94 | |
95 // A basic bot waits for onBotConnected to be called with a stream to the actual | |
96 // endpoint with the bot. Once that stream is available it establishes a dnode | |
97 // connection and calls the callback with the other endpoint interface so the | |
98 // test can interact with it. | |
99 Bot = function (name, callback) { | |
100 this.name_ = name; | |
101 this.onbotready_ = callback; | |
102 } | |
103 | |
104 Bot.prototype = { | |
105 log: function (msg) { | |
106 console.log("bot:" + this.name_ + " > " + msg); | |
107 }, | |
108 | |
109 name: function () { return this.name_; }, | |
110 | |
111 onBotConnected: function (stream) { | |
112 this.log('Connected'); | |
113 this.stream_ = stream; | |
114 this.dnode_ = new Dnode(); | |
115 this.dnode_.on('remote', this.onRemoteFromDnode_.bind(this)); | |
116 this.dnode_.pipe(this.stream_).pipe(this.dnode_); | |
117 }, | |
118 | |
119 onRemoteFromDnode_: function (remote) { | |
120 this.onbotready_(remote); | |
121 } | |
122 } | |
123 | |
124 // BrowserBot spawns a process to open "https://localhost:8080/bot/browser". | |
125 // | |
126 // That page once loaded, connects to the websocket server run by BotManager | |
127 // and exposes the bot api. | |
128 BrowserBot = function (name, callback) { | |
129 Bot.call(this, name, callback); | |
130 this.spawnBotProcess_(); | |
131 } | |
132 | |
133 BrowserBot.prototype = { | |
134 spawnBotProcess_: function () { | |
135 this.log('Spawning browser'); | |
136 child.exec('google-chrome "https://localhost:8080/bot/browser/"'); | |
137 }, | |
138 | |
139 __proto__: Bot.prototype | |
140 } | |
141 | |
142 // AndroidChromeBot spawns a process to open | |
143 // "https://localhost:8080/bot/browser/" on chrome for Android. | |
144 AndroidChromeBot = function (name, androidDeviceManager, callback) { | |
145 Bot.call(this, name, callback); | |
146 androidDeviceManager.getNewDevice(function (serialNumber) { | |
147 this.serialNumber_ = serialNumber; | |
148 this.spawnBotProcess_(); | |
149 }.bind(this)); | |
150 } | |
151 | |
152 AndroidChromeBot.prototype = { | |
153 spawnBotProcess_: function () { | |
154 this.log('Spawning Android device with serial ' + this.serialNumber_); | |
155 var runChrome = 'adb -s ' + this.serialNumber_ + ' shell am start ' + | |
156 '-n com.android.chrome/com.google.android.apps.chrome.Main ' + | |
157 '-d https://localhost:8080/bot/browser/'; | |
158 child.exec(runChrome, function (error, stdout, stderr) { | |
159 if (error) { | |
160 this.log(error); | |
161 process.exit(1); | |
162 } | |
163 this.log('Opening Chrome for Android...'); | |
164 this.log(stdout); | |
165 }.bind(this)); | |
166 }, | |
167 | |
168 __proto__: Bot.prototype | |
169 } | |
170 | |
171 AndroidDeviceManager = function () { | |
172 this.connectedDevices_ = []; | |
173 } | |
174 | |
175 AndroidDeviceManager.prototype = { | |
176 getNewDevice: function (callback) { | |
177 this.listDevices_(function (devices) { | |
178 for (var i = 0; i < devices.length; i++) { | |
179 if (!this.connectedDevices_[devices[i]]) { | |
180 this.connectedDevices_[devices[i]] = devices[i]; | |
181 callback(this.connectedDevices_[devices[i]]); | |
182 return; | |
183 } | |
184 } | |
185 if (devices.length == 0) { | |
186 console.log('Error: No connected devices!'); | |
187 } else { | |
188 console.log('Error: There is no enough connected devices.'); | |
189 } | |
190 process.exit(1); | |
191 }.bind(this)); | |
192 }, | |
193 | |
194 listDevices_: function (callback) { | |
195 child.exec('adb devices' , function (error, stdout, stderr) { | |
196 var devices = []; | |
197 if (error || stderr) { | |
198 console.log(error || stderr); | |
199 } | |
200 if (stdout) { | |
201 // The first line is "List of devices attached" | |
202 // and the following lines: | |
203 // <serial number> <device/emulator> | |
204 var tempList = stdout.split("\n").slice(1); | |
205 for (var i = 0; i < tempList.length; i++) { | |
206 if (tempList[i] == "") { | |
207 continue; | |
208 } | |
209 devices.push(tempList[i].split("\t")[0]); | |
210 } | |
211 } | |
212 callback(devices); | |
213 }); | |
214 }, | |
215 } | |
216 module.exports = BotManager; | |
OLD | NEW |