mapperServer.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. "use strict";
  2. /**
  3. * Copyright 2021 Google LLC.
  4. * Copyright (c) Microsoft Corporation.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. var __importDefault = (this && this.__importDefault) || function (mod) {
  19. return (mod && mod.__esModule) ? mod : { "default": mod };
  20. };
  21. Object.defineProperty(exports, "__esModule", { value: true });
  22. exports.MapperServer = void 0;
  23. const ws_1 = __importDefault(require("ws"));
  24. const debug_1 = __importDefault(require("debug"));
  25. const cdpConnection_js_1 = require("../cdp/cdpConnection.js");
  26. const log_js_1 = require("../utils/log.js");
  27. const websocketTransport_js_1 = require("../utils/websocketTransport.js");
  28. const debugInternal = (0, debug_1.default)('bidi:mapper:internal');
  29. const debugInfo = (0, debug_1.default)('bidi:mapper:info');
  30. const debugMapperDebugOthers = (0, debug_1.default)('bidi:mapper:debug:others');
  31. const debugMapperDebugPrefix = 'bidi:mapper:debug:';
  32. class MapperServer {
  33. #handlers = [];
  34. #cdpConnection;
  35. #mapperCdpClient;
  36. static async create(cdpUrl, mapperContent, verbose) {
  37. const cdpConnection = await this.#establishCdpConnection(cdpUrl);
  38. try {
  39. const mapperCdpClient = await this.#initMapper(cdpConnection, mapperContent, verbose);
  40. return new MapperServer(cdpConnection, mapperCdpClient);
  41. }
  42. catch (e) {
  43. cdpConnection.close();
  44. throw e;
  45. }
  46. }
  47. constructor(cdpConnection, mapperCdpClient) {
  48. this.#cdpConnection = cdpConnection;
  49. this.#mapperCdpClient = mapperCdpClient;
  50. this.#mapperCdpClient.on('Runtime.bindingCalled', this.#onBindingCalled);
  51. this.#mapperCdpClient.on('Runtime.consoleAPICalled', this.#onConsoleAPICalled);
  52. // Catch unhandled exceptions in the mapper.
  53. this.#mapperCdpClient.on('Runtime.exceptionThrown', this.#onRuntimeExceptionThrown);
  54. }
  55. setOnMessage(handler) {
  56. this.#handlers.push(handler);
  57. }
  58. sendMessage(messageJson) {
  59. return this.#sendBidiMessage(messageJson);
  60. }
  61. close() {
  62. this.#cdpConnection.close();
  63. }
  64. static #establishCdpConnection(cdpUrl) {
  65. return new Promise((resolve, reject) => {
  66. debugInternal('Establishing session with cdpUrl: ', cdpUrl);
  67. const ws = new ws_1.default(cdpUrl);
  68. ws.once('error', reject);
  69. ws.on('open', () => {
  70. debugInternal('Session established.');
  71. const transport = new websocketTransport_js_1.WebSocketTransport(ws);
  72. const connection = new cdpConnection_js_1.CdpConnection(transport);
  73. resolve(connection);
  74. });
  75. });
  76. }
  77. async #sendBidiMessage(bidiMessageJson) {
  78. try {
  79. await this.#mapperCdpClient.sendCommand('Runtime.evaluate', {
  80. expression: `onBidiMessage(${JSON.stringify(bidiMessageJson)})`,
  81. });
  82. }
  83. catch (error) {
  84. debugInternal('Call to onBidiMessage failed', error);
  85. }
  86. }
  87. #onBidiMessage(bidiMessage) {
  88. for (const handler of this.#handlers)
  89. handler(bidiMessage);
  90. }
  91. #onBindingCalled = (params) => {
  92. if (params.name === 'sendBidiResponse') {
  93. this.#onBidiMessage(params.payload);
  94. }
  95. if (params.name === 'sendDebugMessage') {
  96. this.#onDebugMessage(params.payload);
  97. }
  98. };
  99. #onDebugMessage = (debugMessageStr) => {
  100. try {
  101. const debugMessage = JSON.parse(debugMessageStr);
  102. // BiDi traffic is logged in `bidi:server:SEND ▸`
  103. if (debugMessage.logType.startsWith(log_js_1.LogType.bidi)) {
  104. return;
  105. }
  106. if (debugMessage.logType !== undefined &&
  107. debugMessage.messages !== undefined) {
  108. (0, debug_1.default)(debugMapperDebugPrefix + debugMessage.logType)(
  109. // No formatter is needed as the messages will be formatted
  110. // automatically.
  111. '', ...debugMessage.messages);
  112. return;
  113. }
  114. }
  115. catch { }
  116. // Fall back to raw log in case of unknown
  117. debugMapperDebugOthers(debugMessageStr);
  118. };
  119. #onConsoleAPICalled = (params) => {
  120. debugInfo('consoleAPICalled %s %O', params.type, params.args.map((arg) => arg.value));
  121. };
  122. #onRuntimeExceptionThrown = (params) => {
  123. debugInfo('exceptionThrown', params);
  124. };
  125. static async #initMapper(cdpConnection, mapperContent, verbose) {
  126. debugInternal('Connection opened.');
  127. const browserClient = cdpConnection.browserClient();
  128. const { targetId } = await browserClient.sendCommand('Target.createTarget', {
  129. url: 'about:blank',
  130. });
  131. const { sessionId: mapperSessionId } = await browserClient.sendCommand('Target.attachToTarget', { targetId, flatten: true });
  132. const mapperCdpClient = cdpConnection.getCdpClient(mapperSessionId);
  133. await mapperCdpClient.sendCommand('Runtime.enable');
  134. await browserClient.sendCommand('Target.exposeDevToolsProtocol', {
  135. bindingName: 'cdp',
  136. targetId,
  137. });
  138. await mapperCdpClient.sendCommand('Runtime.addBinding', {
  139. name: 'sendBidiResponse',
  140. });
  141. if (verbose) {
  142. // Needed to request verbose logs from Mapper.
  143. await mapperCdpClient.sendCommand('Runtime.addBinding', {
  144. name: 'sendDebugMessage',
  145. });
  146. }
  147. const launchedPromise = new Promise((resolve, reject) => {
  148. const onBindingCalled = ({ name, payload, }) => {
  149. // Needed to check when Mapper is launched on the frontend.
  150. if (name === 'sendBidiResponse') {
  151. try {
  152. const parsed = JSON.parse(payload);
  153. if (parsed.launched) {
  154. mapperCdpClient.off('Runtime.bindingCalled', onBindingCalled);
  155. resolve();
  156. }
  157. }
  158. catch (e) {
  159. reject(new Error('Could not parse initial bidi response as JSON'));
  160. }
  161. }
  162. };
  163. mapperCdpClient.on('Runtime.bindingCalled', onBindingCalled);
  164. });
  165. await mapperCdpClient.sendCommand('Runtime.evaluate', {
  166. expression: mapperContent,
  167. });
  168. // Let Mapper know what is it's TargetId to filter out related targets.
  169. await mapperCdpClient.sendCommand('Runtime.evaluate', {
  170. expression: `window.setSelfTargetId(${JSON.stringify(targetId)})`,
  171. });
  172. await launchedPromise;
  173. debugInternal('Launched!');
  174. return mapperCdpClient;
  175. }
  176. }
  177. exports.MapperServer = MapperServer;
  178. //# sourceMappingURL=mapperServer.js.map