utility-process.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import { EventEmitter } from 'events';
  2. import { Duplex, PassThrough } from 'stream';
  3. import { Socket } from 'net';
  4. import { MessagePortMain } from '@electron/internal/browser/message-port-main';
  5. const { _fork } = process._linkedBinding('electron_browser_utility_process');
  6. class ForkUtilityProcess extends EventEmitter implements Electron.UtilityProcess {
  7. #handle: ElectronInternal.UtilityProcessWrapper | null;
  8. #stdout: Duplex | null = null;
  9. #stderr: Duplex | null = null;
  10. constructor (modulePath: string, args?: string[], options?: Electron.ForkOptions) {
  11. super();
  12. if (!modulePath) {
  13. throw new Error('Missing UtilityProcess entry script.');
  14. }
  15. if (args == null) {
  16. args = [];
  17. } else if (typeof args === 'object' && !Array.isArray(args)) {
  18. options = args;
  19. args = [];
  20. }
  21. if (options == null) {
  22. options = {};
  23. } else {
  24. options = { ...options };
  25. }
  26. if (!options) {
  27. throw new Error('Options cannot be undefined.');
  28. }
  29. if (options.execArgv != null) {
  30. if (!Array.isArray(options.execArgv)) {
  31. throw new TypeError('execArgv must be an array of strings.');
  32. }
  33. }
  34. if (options.serviceName != null) {
  35. if (typeof options.serviceName !== 'string') {
  36. throw new TypeError('serviceName must be a string.');
  37. }
  38. }
  39. if (options.cwd != null) {
  40. if (typeof options.cwd !== 'string') {
  41. throw new TypeError('cwd path must be a string.');
  42. }
  43. }
  44. if (typeof options.stdio === 'string') {
  45. const stdio : Array<'pipe' | 'ignore' | 'inherit'> = [];
  46. switch (options.stdio) {
  47. case 'inherit':
  48. case 'ignore':
  49. stdio.push('ignore', options.stdio, options.stdio);
  50. break;
  51. case 'pipe':
  52. this.#stderr = new PassThrough();
  53. this.#stdout = new PassThrough();
  54. stdio.push('ignore', options.stdio, options.stdio);
  55. break;
  56. default:
  57. throw new Error('stdio must be of the following values: inherit, pipe, ignore');
  58. }
  59. options.stdio = stdio;
  60. } else if (Array.isArray(options.stdio)) {
  61. if (options.stdio.length >= 3) {
  62. if (options.stdio[0] !== 'ignore') {
  63. throw new Error('stdin value other than ignore is not supported.');
  64. }
  65. if (options.stdio[1] === 'pipe') {
  66. this.#stdout = new PassThrough();
  67. } else if (options.stdio[1] !== 'ignore' && options.stdio[1] !== 'inherit') {
  68. throw new Error('stdout configuration must be of the following values: inherit, pipe, ignore');
  69. }
  70. if (options.stdio[2] === 'pipe') {
  71. this.#stderr = new PassThrough();
  72. } else if (options.stdio[2] !== 'ignore' && options.stdio[2] !== 'inherit') {
  73. throw new Error('stderr configuration must be of the following values: inherit, pipe, ignore');
  74. }
  75. } else {
  76. throw new Error('configuration missing for stdin, stdout or stderr.');
  77. }
  78. }
  79. this.#handle = _fork({ options, modulePath, args });
  80. this.#handle!.emit = (channel: string | symbol, ...args: any[]) => {
  81. if (channel === 'exit') {
  82. try {
  83. this.emit('exit', ...args);
  84. } finally {
  85. this.#handle = null;
  86. if (this.#stdout) {
  87. this.#stdout.removeAllListeners();
  88. this.#stdout = null;
  89. }
  90. if (this.#stderr) {
  91. this.#stderr.removeAllListeners();
  92. this.#stderr = null;
  93. }
  94. }
  95. return false;
  96. } else if (channel === 'stdout' && this.#stdout) {
  97. new Socket({ fd: args[0], readable: true }).pipe(this.#stdout);
  98. return true;
  99. } else if (channel === 'stderr' && this.#stderr) {
  100. new Socket({ fd: args[0], readable: true }).pipe(this.#stderr);
  101. return true;
  102. } else {
  103. return this.emit(channel, ...args);
  104. }
  105. };
  106. }
  107. get pid () {
  108. return this.#handle?.pid;
  109. }
  110. get stdout () {
  111. return this.#stdout;
  112. }
  113. get stderr () {
  114. return this.#stderr;
  115. }
  116. postMessage (message: any, transfer?: MessagePortMain[]) {
  117. if (Array.isArray(transfer)) {
  118. transfer = transfer.map((o: any) => o instanceof MessagePortMain ? o._internalPort : o);
  119. return this.#handle?.postMessage(message, transfer);
  120. }
  121. return this.#handle?.postMessage(message);
  122. }
  123. kill () : boolean {
  124. if (this.#handle === null) {
  125. return false;
  126. }
  127. return this.#handle.kill();
  128. }
  129. }
  130. export function fork (modulePath: string, args?: string[], options?: Electron.ForkOptions) {
  131. return new ForkUtilityProcess(modulePath, args, options);
  132. }