| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 define('serial_service', [ | |
| 6 'content/public/renderer/frame_interfaces', | |
| 7 'data_receiver', | |
| 8 'data_sender', | |
| 9 'device/serial/serial.mojom', | |
| 10 'device/serial/serial_serialization.mojom', | |
| 11 'mojo/public/js/core', | |
| 12 'mojo/public/js/router', | |
| 13 'stash_client', | |
| 14 ], function (frameInterfaces, | |
| 15 dataReceiver, | |
| 16 dataSender, | |
| 17 serialMojom, | |
| 18 serialization, | |
| 19 core, | |
| 20 routerModule, | |
| 21 stashClient) { | |
| 22 /** | |
| 23 * A Javascript client for the serial service and connection Mojo services. | |
| 24 * | |
| 25 * This provides a thick client around the Mojo services, exposing a JS-style | |
| 26 * interface to serial connections and information about serial devices. This | |
| 27 * converts parameters and result between the Apps serial API types and the | |
| 28 * Mojo types. | |
| 29 */ | |
| 30 | |
| 31 var service = new serialMojom.SerialService.proxyClass( | |
| 32 new routerModule.Router( | |
| 33 frameInterfaces.getInterface(serialMojom.SerialService.name))); | |
| 34 | |
| 35 function getDevices() { | |
| 36 return service.getDevices().then(function(response) { | |
| 37 return $Array.map(response.devices, function(device) { | |
| 38 var result = {path: device.path}; | |
| 39 if (device.has_vendor_id) | |
| 40 result.vendorId = device.vendor_id; | |
| 41 if (device.has_product_id) | |
| 42 result.productId = device.product_id; | |
| 43 if (device.display_name) | |
| 44 result.displayName = device.display_name; | |
| 45 return result; | |
| 46 }); | |
| 47 }); | |
| 48 } | |
| 49 | |
| 50 var DATA_BITS_TO_MOJO = { | |
| 51 undefined: serialMojom.DataBits.NONE, | |
| 52 'seven': serialMojom.DataBits.SEVEN, | |
| 53 'eight': serialMojom.DataBits.EIGHT, | |
| 54 }; | |
| 55 var STOP_BITS_TO_MOJO = { | |
| 56 undefined: serialMojom.StopBits.NONE, | |
| 57 'one': serialMojom.StopBits.ONE, | |
| 58 'two': serialMojom.StopBits.TWO, | |
| 59 }; | |
| 60 var PARITY_BIT_TO_MOJO = { | |
| 61 undefined: serialMojom.ParityBit.NONE, | |
| 62 'no': serialMojom.ParityBit.NO, | |
| 63 'odd': serialMojom.ParityBit.ODD, | |
| 64 'even': serialMojom.ParityBit.EVEN, | |
| 65 }; | |
| 66 var SEND_ERROR_TO_MOJO = { | |
| 67 undefined: serialMojom.SendError.NONE, | |
| 68 'disconnected': serialMojom.SendError.DISCONNECTED, | |
| 69 'pending': serialMojom.SendError.PENDING, | |
| 70 'timeout': serialMojom.SendError.TIMEOUT, | |
| 71 'system_error': serialMojom.SendError.SYSTEM_ERROR, | |
| 72 }; | |
| 73 var RECEIVE_ERROR_TO_MOJO = { | |
| 74 undefined: serialMojom.ReceiveError.NONE, | |
| 75 'disconnected': serialMojom.ReceiveError.DISCONNECTED, | |
| 76 'device_lost': serialMojom.ReceiveError.DEVICE_LOST, | |
| 77 'timeout': serialMojom.ReceiveError.TIMEOUT, | |
| 78 'break': serialMojom.ReceiveError.BREAK, | |
| 79 'frame_error': serialMojom.ReceiveError.FRAME_ERROR, | |
| 80 'overrun': serialMojom.ReceiveError.OVERRUN, | |
| 81 'buffer_overflow': serialMojom.ReceiveError.BUFFER_OVERFLOW, | |
| 82 'parity_error': serialMojom.ReceiveError.PARITY_ERROR, | |
| 83 'system_error': serialMojom.ReceiveError.SYSTEM_ERROR, | |
| 84 }; | |
| 85 | |
| 86 function invertMap(input) { | |
| 87 var output = {}; | |
| 88 for (var key in input) { | |
| 89 if (key == 'undefined') | |
| 90 output[input[key]] = undefined; | |
| 91 else | |
| 92 output[input[key]] = key; | |
| 93 } | |
| 94 return output; | |
| 95 } | |
| 96 var DATA_BITS_FROM_MOJO = invertMap(DATA_BITS_TO_MOJO); | |
| 97 var STOP_BITS_FROM_MOJO = invertMap(STOP_BITS_TO_MOJO); | |
| 98 var PARITY_BIT_FROM_MOJO = invertMap(PARITY_BIT_TO_MOJO); | |
| 99 var SEND_ERROR_FROM_MOJO = invertMap(SEND_ERROR_TO_MOJO); | |
| 100 var RECEIVE_ERROR_FROM_MOJO = invertMap(RECEIVE_ERROR_TO_MOJO); | |
| 101 | |
| 102 function getServiceOptions(options) { | |
| 103 var out = {}; | |
| 104 if (options.dataBits) | |
| 105 out.data_bits = DATA_BITS_TO_MOJO[options.dataBits]; | |
| 106 if (options.stopBits) | |
| 107 out.stop_bits = STOP_BITS_TO_MOJO[options.stopBits]; | |
| 108 if (options.parityBit) | |
| 109 out.parity_bit = PARITY_BIT_TO_MOJO[options.parityBit]; | |
| 110 if ('ctsFlowControl' in options) { | |
| 111 out.has_cts_flow_control = true; | |
| 112 out.cts_flow_control = options.ctsFlowControl; | |
| 113 } | |
| 114 if ('bitrate' in options) | |
| 115 out.bitrate = options.bitrate; | |
| 116 return out; | |
| 117 } | |
| 118 | |
| 119 function convertServiceInfo(result) { | |
| 120 if (!result.info) | |
| 121 throw new Error('Failed to get ConnectionInfo.'); | |
| 122 return { | |
| 123 ctsFlowControl: !!result.info.cts_flow_control, | |
| 124 bitrate: result.info.bitrate || undefined, | |
| 125 dataBits: DATA_BITS_FROM_MOJO[result.info.data_bits], | |
| 126 stopBits: STOP_BITS_FROM_MOJO[result.info.stop_bits], | |
| 127 parityBit: PARITY_BIT_FROM_MOJO[result.info.parity_bit], | |
| 128 }; | |
| 129 } | |
| 130 | |
| 131 // Update client-side options |clientOptions| from the user-provided | |
| 132 // |options|. | |
| 133 function updateClientOptions(clientOptions, options) { | |
| 134 if ('name' in options) | |
| 135 clientOptions.name = options.name; | |
| 136 if ('receiveTimeout' in options) | |
| 137 clientOptions.receiveTimeout = options.receiveTimeout; | |
| 138 if ('sendTimeout' in options) | |
| 139 clientOptions.sendTimeout = options.sendTimeout; | |
| 140 if ('bufferSize' in options) | |
| 141 clientOptions.bufferSize = options.bufferSize; | |
| 142 if ('persistent' in options) | |
| 143 clientOptions.persistent = options.persistent; | |
| 144 }; | |
| 145 | |
| 146 function Connection(connection, router, receivePipe, receiveClientPipe, | |
| 147 sendPipe, id, options) { | |
| 148 var state = new serialization.ConnectionState(); | |
| 149 state.connectionId = id; | |
| 150 updateClientOptions(state, options); | |
| 151 var receiver = new dataReceiver.DataReceiver( | |
| 152 receivePipe, receiveClientPipe, state.bufferSize, | |
| 153 serialMojom.ReceiveError.DISCONNECTED); | |
| 154 var sender = new dataSender.DataSender(sendPipe, state.bufferSize, | |
| 155 serialMojom.SendError.DISCONNECTED); | |
| 156 this.init_(state, | |
| 157 connection, | |
| 158 router, | |
| 159 receiver, | |
| 160 sender, | |
| 161 null, | |
| 162 serialMojom.ReceiveError.NONE); | |
| 163 connections_.set(id, this); | |
| 164 this.startReceive_(); | |
| 165 } | |
| 166 | |
| 167 // Initializes this Connection from the provided args. | |
| 168 Connection.prototype.init_ = function(state, | |
| 169 connection, | |
| 170 router, | |
| 171 receiver, | |
| 172 sender, | |
| 173 queuedReceiveData, | |
| 174 queuedReceiveError) { | |
| 175 this.state_ = state; | |
| 176 | |
| 177 // queuedReceiveData_ or queuedReceiveError_ will store the receive result | |
| 178 // or error, respectively, if a receive completes or fails while this | |
| 179 // connection is paused. At most one of the the two may be non-null: a | |
| 180 // receive completed while paused will only set one of them, no further | |
| 181 // receives will be performed while paused and a queued result is dispatched | |
| 182 // before any further receives are initiated when unpausing. | |
| 183 if (queuedReceiveError != serialMojom.ReceiveError.NONE) | |
| 184 this.queuedReceiveError_ = {error: queuedReceiveError}; | |
| 185 if (queuedReceiveData) { | |
| 186 this.queuedReceiveData_ = new ArrayBuffer(queuedReceiveData.length); | |
| 187 new Int8Array(this.queuedReceiveData_).set(queuedReceiveData); | |
| 188 } | |
| 189 this.router_ = router; | |
| 190 this.remoteConnection_ = connection; | |
| 191 this.receivePipe_ = receiver; | |
| 192 this.sendPipe_ = sender; | |
| 193 this.sendInProgress_ = false; | |
| 194 }; | |
| 195 | |
| 196 Connection.create = function(path, options) { | |
| 197 options = options || {}; | |
| 198 var serviceOptions = getServiceOptions(options); | |
| 199 var pipe = core.createMessagePipe(); | |
| 200 var sendPipe = core.createMessagePipe(); | |
| 201 var receivePipe = core.createMessagePipe(); | |
| 202 var receivePipeClient = core.createMessagePipe(); | |
| 203 service.connect(path, | |
| 204 serviceOptions, | |
| 205 pipe.handle0, | |
| 206 sendPipe.handle0, | |
| 207 receivePipe.handle0, | |
| 208 receivePipeClient.handle0); | |
| 209 var router = new routerModule.Router(pipe.handle1); | |
| 210 var connection = new serialMojom.Connection.proxyClass(router); | |
| 211 return connection.getInfo().then(convertServiceInfo).then(function(info) { | |
| 212 return Promise.all([info, allocateConnectionId()]); | |
| 213 }).catch(function(e) { | |
| 214 router.close(); | |
| 215 core.close(sendPipe.handle1); | |
| 216 core.close(receivePipe.handle1); | |
| 217 core.close(receivePipeClient.handle1); | |
| 218 throw e; | |
| 219 }).then(function(results) { | |
| 220 var info = results[0]; | |
| 221 var id = results[1]; | |
| 222 var serialConnectionClient = new Connection(connection, | |
| 223 router, | |
| 224 receivePipe.handle1, | |
| 225 receivePipeClient.handle1, | |
| 226 sendPipe.handle1, | |
| 227 id, | |
| 228 options); | |
| 229 var clientInfo = serialConnectionClient.getClientInfo_(); | |
| 230 for (var key in clientInfo) { | |
| 231 info[key] = clientInfo[key]; | |
| 232 } | |
| 233 return { | |
| 234 connection: serialConnectionClient, | |
| 235 info: info, | |
| 236 }; | |
| 237 }); | |
| 238 }; | |
| 239 | |
| 240 Connection.prototype.close = function() { | |
| 241 this.router_.close(); | |
| 242 this.receivePipe_.close(); | |
| 243 this.sendPipe_.close(); | |
| 244 clearTimeout(this.receiveTimeoutId_); | |
| 245 clearTimeout(this.sendTimeoutId_); | |
| 246 connections_.delete(this.state_.connectionId); | |
| 247 return true; | |
| 248 }; | |
| 249 | |
| 250 Connection.prototype.getClientInfo_ = function() { | |
| 251 return { | |
| 252 connectionId: this.state_.connectionId, | |
| 253 paused: this.state_.paused, | |
| 254 persistent: this.state_.persistent, | |
| 255 name: this.state_.name, | |
| 256 receiveTimeout: this.state_.receiveTimeout, | |
| 257 sendTimeout: this.state_.sendTimeout, | |
| 258 bufferSize: this.state_.bufferSize, | |
| 259 }; | |
| 260 }; | |
| 261 | |
| 262 Connection.prototype.getInfo = function() { | |
| 263 var info = this.getClientInfo_(); | |
| 264 return this.remoteConnection_.getInfo().then(convertServiceInfo).then( | |
| 265 function(result) { | |
| 266 for (var key in result) { | |
| 267 info[key] = result[key]; | |
| 268 } | |
| 269 return info; | |
| 270 }).catch(function() { | |
| 271 return info; | |
| 272 }); | |
| 273 }; | |
| 274 | |
| 275 Connection.prototype.setOptions = function(options) { | |
| 276 updateClientOptions(this.state_, options); | |
| 277 var serviceOptions = getServiceOptions(options); | |
| 278 if ($Object.keys(serviceOptions).length == 0) | |
| 279 return true; | |
| 280 return this.remoteConnection_.setOptions(serviceOptions).then( | |
| 281 function(result) { | |
| 282 return !!result.success; | |
| 283 }).catch(function() { | |
| 284 return false; | |
| 285 }); | |
| 286 }; | |
| 287 | |
| 288 Connection.prototype.getControlSignals = function() { | |
| 289 return this.remoteConnection_.getControlSignals().then(function(result) { | |
| 290 if (!result.signals) | |
| 291 throw new Error('Failed to get control signals.'); | |
| 292 var signals = result.signals; | |
| 293 return { | |
| 294 dcd: !!signals.dcd, | |
| 295 cts: !!signals.cts, | |
| 296 ri: !!signals.ri, | |
| 297 dsr: !!signals.dsr, | |
| 298 }; | |
| 299 }); | |
| 300 }; | |
| 301 | |
| 302 Connection.prototype.setControlSignals = function(signals) { | |
| 303 var controlSignals = {}; | |
| 304 if ('dtr' in signals) { | |
| 305 controlSignals.has_dtr = true; | |
| 306 controlSignals.dtr = signals.dtr; | |
| 307 } | |
| 308 if ('rts' in signals) { | |
| 309 controlSignals.has_rts = true; | |
| 310 controlSignals.rts = signals.rts; | |
| 311 } | |
| 312 return this.remoteConnection_.setControlSignals(controlSignals).then( | |
| 313 function(result) { | |
| 314 return !!result.success; | |
| 315 }); | |
| 316 }; | |
| 317 | |
| 318 Connection.prototype.flush = function() { | |
| 319 return this.remoteConnection_.flush().then(function(result) { | |
| 320 return !!result.success; | |
| 321 }); | |
| 322 }; | |
| 323 | |
| 324 Connection.prototype.setPaused = function(paused) { | |
| 325 this.state_.paused = paused; | |
| 326 if (paused) { | |
| 327 clearTimeout(this.receiveTimeoutId_); | |
| 328 this.receiveTimeoutId_ = null; | |
| 329 } else if (!this.receiveInProgress_) { | |
| 330 this.startReceive_(); | |
| 331 } | |
| 332 }; | |
| 333 | |
| 334 Connection.prototype.send = function(data) { | |
| 335 if (this.sendInProgress_) | |
| 336 return Promise.resolve({bytesSent: 0, error: 'pending'}); | |
| 337 | |
| 338 if (this.state_.sendTimeout) { | |
| 339 this.sendTimeoutId_ = setTimeout(function() { | |
| 340 this.sendPipe_.cancel(serialMojom.SendError.TIMEOUT); | |
| 341 }.bind(this), this.state_.sendTimeout); | |
| 342 } | |
| 343 this.sendInProgress_ = true; | |
| 344 return this.sendPipe_.send(data).then(function(bytesSent) { | |
| 345 return {bytesSent: bytesSent}; | |
| 346 }).catch(function(e) { | |
| 347 return { | |
| 348 bytesSent: e.bytesSent, | |
| 349 error: SEND_ERROR_FROM_MOJO[e.error], | |
| 350 }; | |
| 351 }).then(function(result) { | |
| 352 if (this.sendTimeoutId_) | |
| 353 clearTimeout(this.sendTimeoutId_); | |
| 354 this.sendTimeoutId_ = null; | |
| 355 this.sendInProgress_ = false; | |
| 356 return result; | |
| 357 }.bind(this)); | |
| 358 }; | |
| 359 | |
| 360 Connection.prototype.startReceive_ = function() { | |
| 361 this.receiveInProgress_ = true; | |
| 362 var receivePromise = null; | |
| 363 // If we have a queued receive result, dispatch it immediately instead of | |
| 364 // starting a new receive. | |
| 365 if (this.queuedReceiveData_) { | |
| 366 receivePromise = Promise.resolve(this.queuedReceiveData_); | |
| 367 this.queuedReceiveData_ = null; | |
| 368 } else if (this.queuedReceiveError_) { | |
| 369 receivePromise = Promise.reject(this.queuedReceiveError_); | |
| 370 this.queuedReceiveError_ = null; | |
| 371 } else { | |
| 372 receivePromise = this.receivePipe_.receive(); | |
| 373 } | |
| 374 receivePromise.then(this.onDataReceived_.bind(this)).catch( | |
| 375 this.onReceiveError_.bind(this)); | |
| 376 this.startReceiveTimeoutTimer_(); | |
| 377 }; | |
| 378 | |
| 379 Connection.prototype.onDataReceived_ = function(data) { | |
| 380 this.startReceiveTimeoutTimer_(); | |
| 381 this.receiveInProgress_ = false; | |
| 382 if (this.state_.paused) { | |
| 383 this.queuedReceiveData_ = data; | |
| 384 return; | |
| 385 } | |
| 386 if (this.onData) { | |
| 387 this.onData(data); | |
| 388 } | |
| 389 if (!this.state_.paused) { | |
| 390 this.startReceive_(); | |
| 391 } | |
| 392 }; | |
| 393 | |
| 394 Connection.prototype.onReceiveError_ = function(e) { | |
| 395 clearTimeout(this.receiveTimeoutId_); | |
| 396 this.receiveInProgress_ = false; | |
| 397 if (this.state_.paused) { | |
| 398 this.queuedReceiveError_ = e; | |
| 399 return; | |
| 400 } | |
| 401 var error = e.error; | |
| 402 this.state_.paused = true; | |
| 403 if (this.onError) | |
| 404 this.onError(RECEIVE_ERROR_FROM_MOJO[error]); | |
| 405 }; | |
| 406 | |
| 407 Connection.prototype.startReceiveTimeoutTimer_ = function() { | |
| 408 clearTimeout(this.receiveTimeoutId_); | |
| 409 if (this.state_.receiveTimeout && !this.state_.paused) { | |
| 410 this.receiveTimeoutId_ = setTimeout(this.onReceiveTimeout_.bind(this), | |
| 411 this.state_.receiveTimeout); | |
| 412 } | |
| 413 }; | |
| 414 | |
| 415 Connection.prototype.onReceiveTimeout_ = function() { | |
| 416 if (this.onError) | |
| 417 this.onError('timeout'); | |
| 418 this.startReceiveTimeoutTimer_(); | |
| 419 }; | |
| 420 | |
| 421 Connection.prototype.serialize = function() { | |
| 422 connections_.delete(this.state_.connectionId); | |
| 423 this.onData = null; | |
| 424 this.onError = null; | |
| 425 var handle = this.router_.connector_.handle_; | |
| 426 this.router_.connector_.handle_ = null; | |
| 427 this.router_.close(); | |
| 428 clearTimeout(this.receiveTimeoutId_); | |
| 429 clearTimeout(this.sendTimeoutId_); | |
| 430 | |
| 431 // Serializing receivePipe_ will cancel an in-progress receive, which would | |
| 432 // pause the connection, so save it ahead of time. | |
| 433 var paused = this.state_.paused; | |
| 434 return Promise.all([ | |
| 435 this.receivePipe_.serialize(), | |
| 436 this.sendPipe_.serialize(), | |
| 437 ]).then(function(serializedComponents) { | |
| 438 var queuedReceiveError = serialMojom.ReceiveError.NONE; | |
| 439 if (this.queuedReceiveError_) | |
| 440 queuedReceiveError = this.queuedReceiveError_.error; | |
| 441 this.state_.paused = paused; | |
| 442 var serialized = new serialization.SerializedConnection(); | |
| 443 serialized.state = this.state_; | |
| 444 serialized.queuedReceiveError = queuedReceiveError; | |
| 445 serialized.queuedReceiveData = | |
| 446 this.queuedReceiveData_ ? new Int8Array(this.queuedReceiveData_) : | |
| 447 null; | |
| 448 serialized.connection = handle; | |
| 449 serialized.receiver = serializedComponents[0]; | |
| 450 serialized.sender = serializedComponents[1]; | |
| 451 return serialized; | |
| 452 }.bind(this)); | |
| 453 }; | |
| 454 | |
| 455 Connection.deserialize = function(serialized) { | |
| 456 var serialConnection = $Object.create(Connection.prototype); | |
| 457 var router = new routerModule.Router(serialized.connection); | |
| 458 var connection = new serialMojom.Connection.proxyClass(router); | |
| 459 var receiver = dataReceiver.DataReceiver.deserialize(serialized.receiver); | |
| 460 var sender = dataSender.DataSender.deserialize(serialized.sender); | |
| 461 | |
| 462 // Ensure that paused and persistent are booleans. | |
| 463 serialized.state.paused = !!serialized.state.paused; | |
| 464 serialized.state.persistent = !!serialized.state.persistent; | |
| 465 serialConnection.init_(serialized.state, | |
| 466 connection, | |
| 467 router, | |
| 468 receiver, | |
| 469 sender, | |
| 470 serialized.queuedReceiveData, | |
| 471 serialized.queuedReceiveError); | |
| 472 serialConnection.awaitingResume_ = true; | |
| 473 var connectionId = serialized.state.connectionId; | |
| 474 connections_.set(connectionId, serialConnection); | |
| 475 if (connectionId >= nextConnectionId_) | |
| 476 nextConnectionId_ = connectionId + 1; | |
| 477 return serialConnection; | |
| 478 }; | |
| 479 | |
| 480 // Resume receives on a deserialized connection. | |
| 481 Connection.prototype.resumeReceives = function() { | |
| 482 if (!this.awaitingResume_) | |
| 483 return; | |
| 484 this.awaitingResume_ = false; | |
| 485 if (!this.state_.paused) | |
| 486 this.startReceive_(); | |
| 487 }; | |
| 488 | |
| 489 // All accesses to connections_ and nextConnectionId_ other than those | |
| 490 // involved in deserialization should ensure that | |
| 491 // connectionDeserializationComplete_ has resolved first. | |
| 492 var connectionDeserializationComplete_ = stashClient.retrieve( | |
| 493 'serial', serialization.SerializedConnection).then(function(decoded) { | |
| 494 if (!decoded) | |
| 495 return; | |
| 496 return Promise.all($Array.map(decoded, Connection.deserialize)); | |
| 497 }); | |
| 498 | |
| 499 // The map of connection ID to connection object. | |
| 500 var connections_ = new Map(); | |
| 501 | |
| 502 // The next connection ID to be allocated. | |
| 503 var nextConnectionId_ = 0; | |
| 504 | |
| 505 function getConnections() { | |
| 506 return connectionDeserializationComplete_.then(function() { | |
| 507 return new Map(connections_); | |
| 508 }); | |
| 509 } | |
| 510 | |
| 511 function getConnection(id) { | |
| 512 return getConnections().then(function(connections) { | |
| 513 if (!connections.has(id)) | |
| 514 throw new Error('Serial connection not found.'); | |
| 515 return connections.get(id); | |
| 516 }); | |
| 517 } | |
| 518 | |
| 519 function allocateConnectionId() { | |
| 520 return connectionDeserializationComplete_.then(function() { | |
| 521 return nextConnectionId_++; | |
| 522 }); | |
| 523 } | |
| 524 | |
| 525 stashClient.registerClient( | |
| 526 'serial', serialization.SerializedConnection, function() { | |
| 527 return connectionDeserializationComplete_.then(function() { | |
| 528 var clientPromises = []; | |
| 529 for (var connection of connections_.values()) { | |
| 530 if (connection.state_.persistent) | |
| 531 clientPromises.push(connection.serialize()); | |
| 532 else | |
| 533 connection.close(); | |
| 534 } | |
| 535 return Promise.all($Array.map(clientPromises, function(promise) { | |
| 536 return promise.then(function(serialization) { | |
| 537 return { | |
| 538 serialization: serialization, | |
| 539 monitorHandles: !serialization.paused, | |
| 540 }; | |
| 541 }); | |
| 542 })); | |
| 543 }); | |
| 544 }); | |
| 545 | |
| 546 return { | |
| 547 getDevices: getDevices, | |
| 548 createConnection: Connection.create, | |
| 549 getConnection: getConnection, | |
| 550 getConnections: getConnections, | |
| 551 // For testing. | |
| 552 Connection: Connection, | |
| 553 }; | |
| 554 }); | |
| OLD | NEW |