1import { spawnPromisified } from '../common/index.mjs'; 2import * as fixtures from '../common/fixtures.mjs'; 3import assert from 'node:assert'; 4import { execPath } from 'node:process'; 5import { describe, it } from 'node:test'; 6 7describe('Loader hooks', { concurrency: true }, () => { 8 it('are called with all expected arguments', async () => { 9 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 10 '--no-warnings', 11 '--experimental-loader', 12 fixtures.fileURL('es-module-loaders/hooks-input.mjs'), 13 fixtures.path('es-modules/json-modules.mjs'), 14 ]); 15 16 assert.strictEqual(stderr, ''); 17 assert.strictEqual(code, 0); 18 assert.strictEqual(signal, null); 19 20 const lines = stdout.split('\n'); 21 assert.match(lines[0], /{"url":"file:\/\/\/.*\/json-modules\.mjs","format":"test","shortCircuit":true}/); 22 assert.match(lines[1], /{"source":{"type":"Buffer","data":\[.*\]},"format":"module","shortCircuit":true}/); 23 assert.match(lines[2], /{"url":"file:\/\/\/.*\/experimental\.json","format":"test","shortCircuit":true}/); 24 assert.match(lines[3], /{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/); 25 assert.strictEqual(lines[4], ''); 26 assert.strictEqual(lines.length, 5); 27 }); 28 29 it('are called with all expected arguments using register function', async () => { 30 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 31 '--no-warnings', 32 '--experimental-loader=data:text/javascript,', 33 '--input-type=module', 34 '--eval', 35 "import { register } from 'node:module';" + 36 `register(${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-input.mjs'))});` + 37 `await import(${JSON.stringify(fixtures.fileURL('es-modules/json-modules.mjs'))});`, 38 ]); 39 40 assert.strictEqual(stderr, ''); 41 assert.strictEqual(code, 0); 42 assert.strictEqual(signal, null); 43 44 const lines = stdout.split('\n'); 45 assert.match(lines[0], /{"url":"file:\/\/\/.*\/json-modules\.mjs","format":"test","shortCircuit":true}/); 46 assert.match(lines[1], /{"source":{"type":"Buffer","data":\[.*\]},"format":"module","shortCircuit":true}/); 47 assert.match(lines[2], /{"url":"file:\/\/\/.*\/experimental\.json","format":"test","shortCircuit":true}/); 48 assert.match(lines[3], /{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/); 49 assert.strictEqual(lines[4], ''); 50 assert.strictEqual(lines.length, 5); 51 }); 52 53 describe('should handle never-settling hooks in ESM files', { concurrency: true }, () => { 54 it('top-level await of a never-settling resolve', async () => { 55 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 56 '--no-warnings', 57 '--experimental-loader', 58 fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), 59 fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.mjs'), 60 ]); 61 62 assert.strictEqual(stderr, ''); 63 assert.match(stdout, /^should be output\r?\n$/); 64 assert.strictEqual(code, 13); 65 assert.strictEqual(signal, null); 66 }); 67 68 it('top-level await of a never-settling load', async () => { 69 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 70 '--no-warnings', 71 '--experimental-loader', 72 fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), 73 fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.mjs'), 74 ]); 75 76 assert.strictEqual(stderr, ''); 77 assert.match(stdout, /^should be output\r?\n$/); 78 assert.strictEqual(code, 13); 79 assert.strictEqual(signal, null); 80 }); 81 82 83 it('top-level await of a race of never-settling hooks', async () => { 84 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 85 '--no-warnings', 86 '--experimental-loader', 87 fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), 88 fixtures.path('es-module-loaders/never-settling-resolve-step/race.mjs'), 89 ]); 90 91 assert.strictEqual(stderr, ''); 92 assert.match(stdout, /^true\r?\n$/); 93 assert.strictEqual(code, 0); 94 assert.strictEqual(signal, null); 95 }); 96 97 it('import.meta.resolve of a never-settling resolve', async () => { 98 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 99 '--no-warnings', 100 '--experimental-loader', 101 fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), 102 fixtures.path('es-module-loaders/never-settling-resolve-step/import.meta.never-resolve.mjs'), 103 ]); 104 105 assert.strictEqual(stderr, ''); 106 assert.match(stdout, /^should be output\r?\n$/); 107 assert.strictEqual(code, 13); 108 assert.strictEqual(signal, null); 109 }); 110 }); 111 112 describe('should handle never-settling hooks in CJS files', { concurrency: true }, () => { 113 it('never-settling resolve', async () => { 114 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 115 '--no-warnings', 116 '--experimental-loader', 117 fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), 118 fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.cjs'), 119 ]); 120 121 assert.strictEqual(stderr, ''); 122 assert.match(stdout, /^should be output\r?\n$/); 123 assert.strictEqual(code, 0); 124 assert.strictEqual(signal, null); 125 }); 126 127 128 it('never-settling load', async () => { 129 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 130 '--no-warnings', 131 '--experimental-loader', 132 fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), 133 fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.cjs'), 134 ]); 135 136 assert.strictEqual(stderr, ''); 137 assert.match(stdout, /^should be output\r?\n$/); 138 assert.strictEqual(code, 0); 139 assert.strictEqual(signal, null); 140 }); 141 142 it('race of never-settling hooks', async () => { 143 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 144 '--no-warnings', 145 '--experimental-loader', 146 fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), 147 fixtures.path('es-module-loaders/never-settling-resolve-step/race.cjs'), 148 ]); 149 150 assert.strictEqual(stderr, ''); 151 assert.match(stdout, /^true\r?\n$/); 152 assert.strictEqual(code, 0); 153 assert.strictEqual(signal, null); 154 }); 155 }); 156 157 it('should not leak internals or expose import.meta.resolve', async () => { 158 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 159 '--no-warnings', 160 '--experimental-loader', 161 fixtures.fileURL('es-module-loaders/loader-edge-cases.mjs'), 162 fixtures.path('empty.js'), 163 ]); 164 165 assert.strictEqual(stderr, ''); 166 assert.strictEqual(stdout, ''); 167 assert.strictEqual(code, 0); 168 assert.strictEqual(signal, null); 169 }); 170 171 it('should be fine to call `process.exit` from a custom async hook', async () => { 172 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 173 '--no-warnings', 174 '--experimental-loader', 175 'data:text/javascript,export function load(a,b,next){if(a==="data:exit")process.exit(42);return next(a,b)}', 176 '--input-type=module', 177 '--eval', 178 'import "data:exit"', 179 ]); 180 181 assert.strictEqual(stderr, ''); 182 assert.strictEqual(stdout, ''); 183 assert.strictEqual(code, 42); 184 assert.strictEqual(signal, null); 185 }); 186 187 it('should be fine to call `process.exit` from a custom sync hook', async () => { 188 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 189 '--no-warnings', 190 '--experimental-loader', 191 'data:text/javascript,export function resolve(a,b,next){if(a==="exit:")process.exit(42);return next(a,b)}', 192 '--input-type=module', 193 '--eval', 194 'import "data:text/javascript,import.meta.resolve(%22exit:%22)"', 195 ]); 196 197 assert.strictEqual(stderr, ''); 198 assert.strictEqual(stdout, ''); 199 assert.strictEqual(code, 42); 200 assert.strictEqual(signal, null); 201 }); 202 203 it('should be fine to call `process.exit` from the loader thread top-level', async () => { 204 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 205 '--no-warnings', 206 '--experimental-loader', 207 'data:text/javascript,process.exit(42)', 208 fixtures.path('empty.js'), 209 ]); 210 211 assert.strictEqual(stderr, ''); 212 assert.strictEqual(stdout, ''); 213 assert.strictEqual(code, 42); 214 assert.strictEqual(signal, null); 215 }); 216 217 describe('should handle a throwing top-level body', () => { 218 it('should handle regular Error object', async () => { 219 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 220 '--no-warnings', 221 '--experimental-loader', 222 'data:text/javascript,throw new Error("error message")', 223 fixtures.path('empty.js'), 224 ]); 225 226 assert.match(stderr, /Error: error message\r?\n/); 227 assert.strictEqual(stdout, ''); 228 assert.strictEqual(code, 1); 229 assert.strictEqual(signal, null); 230 }); 231 232 it('should handle null', async () => { 233 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 234 '--no-warnings', 235 '--experimental-loader', 236 'data:text/javascript,throw null', 237 fixtures.path('empty.js'), 238 ]); 239 240 assert.match(stderr, /\nnull\r?\n/); 241 assert.strictEqual(stdout, ''); 242 assert.strictEqual(code, 1); 243 assert.strictEqual(signal, null); 244 }); 245 246 it('should handle undefined', async () => { 247 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 248 '--no-warnings', 249 '--experimental-loader', 250 'data:text/javascript,throw undefined', 251 fixtures.path('empty.js'), 252 ]); 253 254 assert.match(stderr, /\nundefined\r?\n/); 255 assert.strictEqual(stdout, ''); 256 assert.strictEqual(code, 1); 257 assert.strictEqual(signal, null); 258 }); 259 260 it('should handle boolean', async () => { 261 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 262 '--no-warnings', 263 '--experimental-loader', 264 'data:text/javascript,throw true', 265 fixtures.path('empty.js'), 266 ]); 267 268 assert.match(stderr, /\ntrue\r?\n/); 269 assert.strictEqual(stdout, ''); 270 assert.strictEqual(code, 1); 271 assert.strictEqual(signal, null); 272 }); 273 274 it('should handle empty plain object', async () => { 275 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 276 '--no-warnings', 277 '--experimental-loader', 278 'data:text/javascript,throw {}', 279 fixtures.path('empty.js'), 280 ]); 281 282 assert.match(stderr, /\n\{\}\r?\n/); 283 assert.strictEqual(stdout, ''); 284 assert.strictEqual(code, 1); 285 assert.strictEqual(signal, null); 286 }); 287 288 it('should handle plain object', async () => { 289 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 290 '--no-warnings', 291 '--experimental-loader', 292 'data:text/javascript,throw {fn(){},symbol:Symbol("symbol"),u:undefined}', 293 fixtures.path('empty.js'), 294 ]); 295 296 assert.match(stderr, /\n\{ fn: \[Function: fn\], symbol: Symbol\(symbol\), u: undefined \}\r?\n/); 297 assert.strictEqual(stdout, ''); 298 assert.strictEqual(code, 1); 299 assert.strictEqual(signal, null); 300 }); 301 302 it('should handle number', async () => { 303 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 304 '--no-warnings', 305 '--experimental-loader', 306 'data:text/javascript,throw 1', 307 fixtures.path('empty.js'), 308 ]); 309 310 assert.match(stderr, /\n1\r?\n/); 311 assert.strictEqual(stdout, ''); 312 assert.strictEqual(code, 1); 313 assert.strictEqual(signal, null); 314 }); 315 316 it('should handle bigint', async () => { 317 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 318 '--no-warnings', 319 '--experimental-loader', 320 'data:text/javascript,throw 1n', 321 fixtures.path('empty.js'), 322 ]); 323 324 assert.match(stderr, /\n1\r?\n/); 325 assert.strictEqual(stdout, ''); 326 assert.strictEqual(code, 1); 327 assert.strictEqual(signal, null); 328 }); 329 330 it('should handle string', async () => { 331 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 332 '--no-warnings', 333 '--experimental-loader', 334 'data:text/javascript,throw "literal string"', 335 fixtures.path('empty.js'), 336 ]); 337 338 assert.match(stderr, /\nliteral string\r?\n/); 339 assert.strictEqual(stdout, ''); 340 assert.strictEqual(code, 1); 341 assert.strictEqual(signal, null); 342 }); 343 344 it('should handle symbol', async () => { 345 const { code, signal, stdout } = await spawnPromisified(execPath, [ 346 '--experimental-loader', 347 'data:text/javascript,throw Symbol("symbol descriptor")', 348 fixtures.path('empty.js'), 349 ]); 350 351 // Throwing a symbol doesn't produce any output 352 assert.strictEqual(stdout, ''); 353 assert.strictEqual(code, 1); 354 assert.strictEqual(signal, null); 355 }); 356 357 it('should handle function', async () => { 358 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 359 '--no-warnings', 360 '--experimental-loader', 361 'data:text/javascript,throw function fnName(){}', 362 fixtures.path('empty.js'), 363 ]); 364 365 assert.match(stderr, /\n\[Function: fnName\]\r?\n/); 366 assert.strictEqual(stdout, ''); 367 assert.strictEqual(code, 1); 368 assert.strictEqual(signal, null); 369 }); 370 }); 371 372 describe('globalPreload', () => { 373 it('should emit warning', async () => { 374 const { stderr } = await spawnPromisified(execPath, [ 375 '--experimental-loader', 376 'data:text/javascript,export function globalPreload(){}', 377 '--experimental-loader', 378 'data:text/javascript,export function globalPreload(){return""}', 379 fixtures.path('empty.js'), 380 ]); 381 382 assert.strictEqual(stderr.match(/`globalPreload` is an experimental feature/g).length, 1); 383 }); 384 }); 385 386 it('should be fine to call `process.removeAllListeners("beforeExit")` from the main thread', async () => { 387 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 388 '--no-warnings', 389 '--experimental-loader', 390 'data:text/javascript,export function load(a,b,c){return new Promise(d=>setTimeout(()=>d(c(a,b)),99))}', 391 '--input-type=module', 392 '--eval', 393 'setInterval(() => process.removeAllListeners("beforeExit"),1).unref();await import("data:text/javascript,")', 394 ]); 395 396 assert.strictEqual(stderr, ''); 397 assert.strictEqual(stdout, ''); 398 assert.strictEqual(code, 0); 399 assert.strictEqual(signal, null); 400 }); 401 402 describe('`initialize`/`register`', () => { 403 it('should invoke `initialize` correctly', async () => { 404 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 405 '--no-warnings', 406 '--experimental-loader', 407 fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'), 408 '--input-type=module', 409 '--eval', 410 'import os from "node:os";', 411 ]); 412 413 assert.strictEqual(stderr, ''); 414 assert.deepStrictEqual(stdout.split('\n'), ['hooks initialize 1', '']); 415 assert.strictEqual(code, 0); 416 assert.strictEqual(signal, null); 417 }); 418 419 it('should allow communicating with loader via `register` ports', async () => { 420 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 421 '--no-warnings', 422 '--input-type=module', 423 '--eval', 424 ` 425 import {MessageChannel} from 'node:worker_threads'; 426 import {register} from 'node:module'; 427 import {once} from 'node:events'; 428 const {port1, port2} = new MessageChannel(); 429 port1.on('message', (msg) => { 430 console.log('message', msg); 431 }); 432 const result = register( 433 ${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-initialize-port.mjs'))}, 434 {data: port2, transferList: [port2]}, 435 ); 436 console.log('register', result); 437 438 const timeout = setTimeout(() => {}, 2**31 - 1); // to keep the process alive. 439 await Promise.all([ 440 once(port1, 'message').then(() => once(port1, 'message')), 441 import('node:os'), 442 ]); 443 clearTimeout(timeout); 444 port1.close(); 445 `, 446 ]); 447 448 assert.strictEqual(stderr, ''); 449 assert.deepStrictEqual(stdout.split('\n'), [ 'register undefined', 450 'message initialize', 451 'message resolve node:os', 452 '' ]); 453 454 assert.strictEqual(code, 0); 455 assert.strictEqual(signal, null); 456 }); 457 458 it('should have `register` accept URL objects as `parentURL`', async () => { 459 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 460 '--no-warnings', 461 '--import', 462 `data:text/javascript,${encodeURIComponent( 463 'import{ register } from "node:module";' + 464 'import { pathToFileURL } from "node:url";' + 465 'register("./hooks-initialize.mjs", pathToFileURL("./"));' 466 )}`, 467 '--input-type=module', 468 '--eval', 469 ` 470 import {register} from 'node:module'; 471 register( 472 ${JSON.stringify(fixtures.fileURL('es-module-loaders/loader-load-foo-or-42.mjs'))}, 473 new URL('data:'), 474 ); 475 476 import('node:os').then((result) => { 477 console.log(JSON.stringify(result)); 478 }); 479 `, 480 ], { cwd: fixtures.fileURL('es-module-loaders/') }); 481 482 assert.strictEqual(stderr, ''); 483 assert.deepStrictEqual(stdout.split('\n').sort(), ['hooks initialize 1', '{"default":"foo"}', ''].sort()); 484 485 assert.strictEqual(code, 0); 486 assert.strictEqual(signal, null); 487 }); 488 489 it('should have `register` work with cjs', async () => { 490 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 491 '--no-warnings', 492 '--input-type=commonjs', 493 '--eval', 494 ` 495 'use strict'; 496 const {register} = require('node:module'); 497 register( 498 ${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'))}, 499 ); 500 register( 501 ${JSON.stringify(fixtures.fileURL('es-module-loaders/loader-load-foo-or-42.mjs'))}, 502 ); 503 504 import('node:os').then((result) => { 505 console.log(JSON.stringify(result)); 506 }); 507 `, 508 ]); 509 510 assert.strictEqual(stderr, ''); 511 assert.deepStrictEqual(stdout.split('\n').sort(), ['hooks initialize 1', '{"default":"foo"}', ''].sort()); 512 513 assert.strictEqual(code, 0); 514 assert.strictEqual(signal, null); 515 }); 516 517 it('`register` should work with `require`', async () => { 518 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 519 '--no-warnings', 520 '--require', 521 fixtures.path('es-module-loaders/register-loader.cjs'), 522 '--input-type=module', 523 '--eval', 524 'import "node:os";', 525 ]); 526 527 assert.strictEqual(stderr, ''); 528 assert.deepStrictEqual(stdout.split('\n'), ['resolve passthru', 'resolve passthru', '']); 529 assert.strictEqual(code, 0); 530 assert.strictEqual(signal, null); 531 }); 532 533 it('`register` should work with `import`', async () => { 534 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 535 '--no-warnings', 536 '--import', 537 fixtures.fileURL('es-module-loaders/register-loader.mjs'), 538 '--input-type=module', 539 '--eval', 540 'import "node:os"', 541 ]); 542 543 assert.strictEqual(stderr, ''); 544 assert.deepStrictEqual(stdout.split('\n'), ['resolve passthru', '']); 545 assert.strictEqual(code, 0); 546 assert.strictEqual(signal, null); 547 }); 548 549 it('should execute `initialize` in sequence', async () => { 550 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 551 '--no-warnings', 552 '--input-type=module', 553 '--eval', 554 ` 555 import {register} from 'node:module'; 556 console.log('result 1', register( 557 ${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'))} 558 )); 559 console.log('result 2', register( 560 ${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'))} 561 )); 562 563 await import('node:os'); 564 `, 565 ]); 566 567 assert.strictEqual(stderr, ''); 568 assert.deepStrictEqual(stdout.split('\n'), [ 'hooks initialize 1', 569 'result 1 undefined', 570 'hooks initialize 2', 571 'result 2 undefined', 572 '' ]); 573 assert.strictEqual(code, 0); 574 assert.strictEqual(signal, null); 575 }); 576 577 it('should handle `initialize` returning never-settling promise', async () => { 578 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 579 '--no-warnings', 580 '--input-type=module', 581 '--eval', 582 ` 583 import {register} from 'node:module'; 584 register('data:text/javascript,export function initialize(){return new Promise(()=>{})}'); 585 `, 586 ]); 587 588 assert.strictEqual(stderr, ''); 589 assert.strictEqual(stdout, ''); 590 assert.strictEqual(code, 13); 591 assert.strictEqual(signal, null); 592 }); 593 594 it('should handle `initialize` returning rejecting promise', async () => { 595 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 596 '--no-warnings', 597 '--input-type=module', 598 '--eval', 599 ` 600 import {register} from 'node:module'; 601 register('data:text/javascript,export function initialize(){return Promise.reject()}'); 602 `, 603 ]); 604 605 assert.match(stderr, /undefined\r?\n/); 606 assert.strictEqual(stdout, ''); 607 assert.strictEqual(code, 1); 608 assert.strictEqual(signal, null); 609 }); 610 611 it('should handle `initialize` throwing null', async () => { 612 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 613 '--no-warnings', 614 '--input-type=module', 615 '--eval', 616 ` 617 import {register} from 'node:module'; 618 register('data:text/javascript,export function initialize(){throw null}'); 619 `, 620 ]); 621 622 assert.match(stderr, /null\r?\n/); 623 assert.strictEqual(stdout, ''); 624 assert.strictEqual(code, 1); 625 assert.strictEqual(signal, null); 626 }); 627 628 it('should be fine to call `process.exit` from a initialize hook', async () => { 629 const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ 630 '--no-warnings', 631 '--input-type=module', 632 '--eval', 633 ` 634 import {register} from 'node:module'; 635 register('data:text/javascript,export function initialize(){process.exit(42);}'); 636 `, 637 ]); 638 639 assert.strictEqual(stderr, ''); 640 assert.strictEqual(stdout, ''); 641 assert.strictEqual(code, 42); 642 assert.strictEqual(signal, null); 643 }); 644 }); 645}); 646