PuppeteerConnectionDelegate.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. "use strict";
  2. const { ConnectionDelegate } = require("@zoon/rialto"),
  3. Logger = require("@zoon/rialto/src/node-process/Logger"),
  4. ConsoleInterceptor = require("@zoon/rialto/src/node-process/NodeInterceptors/ConsoleInterceptor"),
  5. StandardStreamsInterceptor = require("@zoon/rialto/src/node-process/NodeInterceptors/StandardStreamsInterceptor");
  6. /**
  7. * Handle the requests of a connection to control Puppeteer.
  8. */
  9. class PuppeteerConnectionDelegate extends ConnectionDelegate {
  10. /**
  11. * Constructor.
  12. *
  13. * @param {Object} options
  14. */
  15. constructor(options) {
  16. super(options);
  17. this.browsers = new Set();
  18. this.addSignalEventListeners();
  19. }
  20. /**
  21. * @inheritdoc
  22. */
  23. async handleInstruction(instruction, responseHandler, errorHandler) {
  24. if (this.options.js_extra) {
  25. eval(this.options.js_extra);
  26. } else {
  27. const puppeteer = require("puppeteer");
  28. instruction.setDefaultResource(puppeteer);
  29. }
  30. let value = null;
  31. try {
  32. value = await instruction.execute();
  33. } catch (error) {
  34. if (instruction.shouldCatchErrors()) {
  35. return errorHandler(error);
  36. }
  37. throw error;
  38. }
  39. if (this.isInstanceOf(value, "Browser")) {
  40. this.browsers.add(value);
  41. if (this.options.log_browser_console === true) {
  42. const initialPages = await value.pages();
  43. initialPages.forEach((page) =>
  44. page.on("console", this.logConsoleMessage)
  45. );
  46. }
  47. }
  48. if (
  49. this.options.log_browser_console === true &&
  50. this.isInstanceOf(value, "Page")
  51. ) {
  52. value.on("console", this.logConsoleMessage);
  53. }
  54. responseHandler(value);
  55. }
  56. /**
  57. * Checks if a value is an instance of a class. The check must be done with the `[object].constructor.name`
  58. * property because relying on Puppeteer's constructors isn't viable since the exports aren't constrained by semver.
  59. *
  60. * @protected
  61. * @param {*} value
  62. * @param {string} className
  63. *
  64. * @see {@link https://github.com/GoogleChrome/puppeteer/issues/3067|Puppeteer's issue about semver on exports}
  65. */
  66. isInstanceOf(value, className) {
  67. const nonObjectValues = [undefined, null];
  68. return (
  69. !nonObjectValues.includes(value) &&
  70. !nonObjectValues.includes(value.constructor) &&
  71. (value.constructor.name === className ||
  72. value.constructor.name === "CDP" + className)
  73. );
  74. }
  75. /**
  76. * Log the console message.
  77. *
  78. * @param {ConsoleMessage} consoleMessage
  79. */
  80. async logConsoleMessage(consoleMessage) {
  81. const type = consoleMessage.type();
  82. if (!ConsoleInterceptor.typeIsSupported(type)) {
  83. return;
  84. }
  85. const level = ConsoleInterceptor.getLevelFromType(type);
  86. const args = await Promise.all(
  87. consoleMessage.args().map((arg) => arg.jsonValue())
  88. );
  89. StandardStreamsInterceptor.startInterceptingStrings((message) => {
  90. Logger.log("Browser", level, ConsoleInterceptor.formatMessage(message));
  91. });
  92. ConsoleInterceptor.originalConsole[type](...args);
  93. StandardStreamsInterceptor.stopInterceptingStrings();
  94. }
  95. /**
  96. * Listen for process signal events.
  97. *
  98. * @protected
  99. */
  100. addSignalEventListeners() {
  101. for (let eventName of ["SIGINT", "SIGTERM", "SIGHUP"]) {
  102. process.on(eventName, () => {
  103. this.closeAllBrowsers();
  104. process.exit();
  105. });
  106. }
  107. }
  108. /**
  109. * Close all the browser instances when the process exits.
  110. *
  111. * Calling this method before exiting Node is mandatory since Puppeteer doesn't seem to handle that properly.
  112. *
  113. * @protected
  114. */
  115. closeAllBrowsers() {
  116. for (let browser of this.browsers.values()) {
  117. browser.close();
  118. }
  119. }
  120. }
  121. module.exports = PuppeteerConnectionDelegate;