| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Copyright (c) Microsoft Corporation. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the 'License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import { LaunchServerOptions } from './client/types'; | 
					
						
							|  |  |  | import { BrowserTypeBase } from '../server/browserType'; | 
					
						
							|  |  |  | import * as ws from 'ws'; | 
					
						
							|  |  |  | import { helper } from '../helper'; | 
					
						
							| 
									
										
										
										
											2020-08-20 01:31:59 +08:00
										 |  |  | import { Browser } from '../browser'; | 
					
						
							| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  | import { ChildProcess } from 'child_process'; | 
					
						
							|  |  |  | import { EventEmitter } from 'ws'; | 
					
						
							|  |  |  | import { DispatcherScope, DispatcherConnection } from './server/dispatcher'; | 
					
						
							|  |  |  | import { BrowserTypeDispatcher } from './server/browserTypeDispatcher'; | 
					
						
							|  |  |  | import { BrowserDispatcher } from './server/browserDispatcher'; | 
					
						
							|  |  |  | import { BrowserContextDispatcher } from './server/browserContextDispatcher'; | 
					
						
							| 
									
										
										
										
											2020-08-22 09:46:11 +08:00
										 |  |  | import { BrowserNewContextParams, BrowserContextChannel } from '../protocol/channels'; | 
					
						
							| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  | import { BrowserServerLauncher, BrowserServer } from './client/browserType'; | 
					
						
							| 
									
										
										
										
											2020-08-19 00:37:40 +08:00
										 |  |  | import { envObjectToArray } from './client/clientHelper'; | 
					
						
							| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export class BrowserServerLauncherImpl implements BrowserServerLauncher { | 
					
						
							|  |  |  |   private _browserType: BrowserTypeBase; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor(browserType: BrowserTypeBase) { | 
					
						
							|  |  |  |     this._browserType = browserType; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServerImpl> { | 
					
						
							| 
									
										
										
										
											2020-08-19 00:37:40 +08:00
										 |  |  |     const browser = await this._browserType.launch({ | 
					
						
							|  |  |  |       ...options, | 
					
						
							|  |  |  |       ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined, | 
					
						
							|  |  |  |       ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs), | 
					
						
							|  |  |  |       env: options.env ? envObjectToArray(options.env) : undefined, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-08-20 01:31:59 +08:00
										 |  |  |     return new BrowserServerImpl(this._browserType, browser, options.port); | 
					
						
							| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class BrowserServerImpl extends EventEmitter implements BrowserServer { | 
					
						
							|  |  |  |   private _server: ws.Server; | 
					
						
							|  |  |  |   private _browserType: BrowserTypeBase; | 
					
						
							| 
									
										
										
										
											2020-08-20 01:31:59 +08:00
										 |  |  |   private _browser: Browser; | 
					
						
							| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  |   private _wsEndpoint: string; | 
					
						
							|  |  |  |   private _process: ChildProcess; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-20 01:31:59 +08:00
										 |  |  |   constructor(browserType: BrowserTypeBase, browser: Browser, port: number = 0) { | 
					
						
							| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  |     super(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._browserType = browserType; | 
					
						
							|  |  |  |     this._browser = browser; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const token = helper.guid(); | 
					
						
							|  |  |  |     this._server = new ws.Server({ port }); | 
					
						
							|  |  |  |     const address = this._server.address(); | 
					
						
							|  |  |  |     this._wsEndpoint = typeof address === 'string' ? `${address}/${token}` : `ws://127.0.0.1:${address.port}/${token}`; | 
					
						
							| 
									
										
										
										
											2020-08-15 04:19:12 +08:00
										 |  |  |     this._process = browser._options.browserProcess.process; | 
					
						
							| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     this._server.on('connection', (socket: ws, req) => { | 
					
						
							|  |  |  |       if (req.url !== '/' + token) { | 
					
						
							|  |  |  |         socket.close(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       this._clientAttached(socket); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-15 04:19:12 +08:00
										 |  |  |     browser._options.browserProcess.onclose = (exitCode, signal) => { | 
					
						
							| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  |       this._server.close(); | 
					
						
							|  |  |  |       this.emit('close', exitCode, signal); | 
					
						
							| 
									
										
										
										
											2020-08-15 04:19:12 +08:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   process(): ChildProcess { | 
					
						
							|  |  |  |     return this._process; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   wsEndpoint(): string { | 
					
						
							|  |  |  |     return this._wsEndpoint; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async close(): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-08-15 04:19:12 +08:00
										 |  |  |     await this._browser._options.browserProcess.close(); | 
					
						
							| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async kill(): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-08-15 04:19:12 +08:00
										 |  |  |     await this._browser._options.browserProcess.kill(); | 
					
						
							| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private _clientAttached(socket: ws) { | 
					
						
							|  |  |  |     const connection = new DispatcherConnection(); | 
					
						
							|  |  |  |     connection.onmessage = message => { | 
					
						
							|  |  |  |       if (socket.readyState !== ws.CLOSING) | 
					
						
							|  |  |  |         socket.send(JSON.stringify(message)); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     socket.on('message', (message: string) => { | 
					
						
							|  |  |  |       connection.dispatch(JSON.parse(Buffer.from(message).toString())); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     socket.on('error', () => {}); | 
					
						
							|  |  |  |     const browserType = new BrowserTypeDispatcher(connection.rootDispatcher(), this._browserType); | 
					
						
							|  |  |  |     const browser = new ConnectedBrowser(browserType._scope, this._browser); | 
					
						
							|  |  |  |     socket.on('close', () => { | 
					
						
							|  |  |  |       // Avoid sending any more messages over closed socket.
 | 
					
						
							|  |  |  |       connection.onmessage = () => {}; | 
					
						
							|  |  |  |       // Cleanup contexts upon disconnect.
 | 
					
						
							|  |  |  |       browser.close().catch(e => {}); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ConnectedBrowser extends BrowserDispatcher { | 
					
						
							|  |  |  |   private _contexts: BrowserContextDispatcher[] = []; | 
					
						
							|  |  |  |   _closed = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-20 01:31:59 +08:00
										 |  |  |   constructor(scope: DispatcherScope, browser: Browser) { | 
					
						
							| 
									
										
										
										
											2020-08-14 04:24:49 +08:00
										 |  |  |     super(scope, browser, 'connectedBrowser'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async newContext(params: BrowserNewContextParams): Promise<{ context: BrowserContextChannel }> { | 
					
						
							|  |  |  |     const result = await super.newContext(params); | 
					
						
							|  |  |  |     this._contexts.push(result.context as BrowserContextDispatcher); | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async close(): Promise<void> { | 
					
						
							|  |  |  |     // Only close our own contexts.
 | 
					
						
							|  |  |  |     await Promise.all(this._contexts.map(context => context.close())); | 
					
						
							|  |  |  |     this._didClose(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _didClose() { | 
					
						
							|  |  |  |     if (!this._closed) { | 
					
						
							|  |  |  |       // We come here multiple times:
 | 
					
						
							|  |  |  |       // - from ConnectedBrowser.close();
 | 
					
						
							|  |  |  |       // - from underlying Browser.on('close').
 | 
					
						
							|  |  |  |       this._closed = true; | 
					
						
							|  |  |  |       super._didClose(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |