@@ -12,13 +12,7 @@ import { Account } from "../auth";
12
12
import { logger } from "../logger" ;
13
13
import { track , trackEmulator } from "../track" ;
14
14
import { Constants } from "./constants" ;
15
- import {
16
- EmulatorInfo ,
17
- EmulatorInstance ,
18
- EmulatorLog ,
19
- Emulators ,
20
- FunctionsExecutionMode ,
21
- } from "./types" ;
15
+ import { EmulatorInfo , EmulatorInstance , Emulators , FunctionsExecutionMode } from "./types" ;
22
16
import * as chokidar from "chokidar" ;
23
17
24
18
import * as spawn from "cross-spawn" ;
@@ -29,7 +23,6 @@ import {
29
23
EventSchedule ,
30
24
EventTrigger ,
31
25
formatHost ,
32
- FunctionsRuntimeBundle ,
33
26
FunctionsRuntimeFeatures ,
34
27
getFunctionService ,
35
28
getSignatureType ,
@@ -331,7 +324,13 @@ export class FunctionsEmulator implements EmulatorInstance {
331
324
return hub ;
332
325
}
333
326
334
- async sendRequest ( worker : RuntimeWorker , body ?: any ) {
327
+ async sendRequest ( trigger : EmulatedTriggerDefinition , body ?: any ) {
328
+ const record = this . getTriggerRecordByKey ( this . getTriggerKey ( trigger ) ) ;
329
+ const pool = this . workerPools [ record . backend . codebase ] ;
330
+ if ( ! pool . readyForWork ( trigger . id ) ) {
331
+ await this . startRuntime ( record . backend , trigger ) ;
332
+ }
333
+ const worker = pool . getIdleWorker ( trigger . id ) ! ;
335
334
const reqBody = JSON . stringify ( body ) ;
336
335
const headers = {
337
336
"Content-Type" : "application/json" ,
@@ -352,35 +351,6 @@ export class FunctionsEmulator implements EmulatorInstance {
352
351
} ) ;
353
352
}
354
353
355
- async invokeTrigger (
356
- trigger : EmulatedTriggerDefinition ,
357
- proto ?: any ,
358
- runtimeOpts ?: InvokeRuntimeOpts
359
- ) : Promise < RuntimeWorker > {
360
- const record = this . getTriggerRecordByKey ( this . getTriggerKey ( trigger ) ) ;
361
- const backend = record . backend ;
362
- const bundleTemplate = this . getBaseBundle ( ) ;
363
- const runtimeBundle : FunctionsRuntimeBundle = {
364
- ...bundleTemplate ,
365
- proto,
366
- } ;
367
- if ( this . args . debugPort ) {
368
- runtimeBundle . debug = {
369
- functionTarget : trigger . entryPoint ,
370
- functionSignature : getSignatureType ( trigger ) ,
371
- } ;
372
- }
373
- if ( ! backend . nodeBinary ) {
374
- throw new FirebaseError ( `No node binary for ${ trigger . id } . This should never happen.` ) ;
375
- }
376
- const opts = runtimeOpts || {
377
- nodeBinary : backend . nodeBinary ,
378
- extensionTriggers : backend . predefinedTriggers ,
379
- } ;
380
- const worker = await this . invokeRuntime ( backend , trigger , runtimeBundle , opts ) ;
381
- return worker ;
382
- }
383
-
384
354
async start ( ) : Promise < void > {
385
355
for ( const backend of this . args . emulatableBackends ) {
386
356
backend . nodeBinary = this . getNodeBinary ( backend ) ;
@@ -680,7 +650,7 @@ export class FunctionsEmulator implements EmulatorInstance {
680
650
// In debug mode, we eagerly start a runtime process to allow debuggers to attach
681
651
// before invoking a function.
682
652
if ( this . args . debugPort ) {
683
- await this . startRuntime ( emulatableBackend , { nodeBinary : emulatableBackend . nodeBinary } ) ;
653
+ await this . startRuntime ( emulatableBackend ) ;
684
654
}
685
655
}
686
656
@@ -998,13 +968,6 @@ export class FunctionsEmulator implements EmulatorInstance {
998
968
triggers . forEach ( ( def ) => this . addTriggerRecord ( def , { backend, ignored : false } ) ) ;
999
969
}
1000
970
1001
- getBaseBundle ( ) : FunctionsRuntimeBundle {
1002
- return {
1003
- proto : { } ,
1004
- disabled_features : this . args . disabledRuntimeFeatures ,
1005
- } ;
1006
- }
1007
-
1008
971
getNodeBinary ( backend : EmulatableBackend ) : string {
1009
972
const pkg = require ( path . join ( backend . functionsDir , "package.json" ) ) ;
1010
973
// If the developer hasn't specified a Node to use, inform them that it's an option and use default
@@ -1275,37 +1238,18 @@ export class FunctionsEmulator implements EmulatorInstance {
1275
1238
return secretEnvs ;
1276
1239
}
1277
1240
1278
- async invokeRuntime (
1279
- backend : EmulatableBackend ,
1280
- trigger : EmulatedTriggerDefinition ,
1281
- frb : FunctionsRuntimeBundle ,
1282
- opts : InvokeRuntimeOpts
1283
- ) : Promise < RuntimeWorker > {
1284
- const pool = this . workerPools [ backend . codebase ] ;
1285
- if ( ! pool . readyForWork ( trigger . id ) ) {
1286
- await this . startRuntime ( backend , opts , trigger ) ;
1287
- }
1288
- return pool . submitWork ( trigger . id , frb , opts ) ;
1289
- }
1290
-
1291
1241
async startRuntime (
1292
1242
backend : EmulatableBackend ,
1293
- opts : InvokeRuntimeOpts ,
1294
1243
trigger ?: EmulatedTriggerDefinition
1295
- ) {
1244
+ ) : Promise < RuntimeWorker > {
1296
1245
const emitter = new EventEmitter ( ) ;
1297
1246
const args = [ path . join ( __dirname , "functionsEmulatorRuntime" ) ] ;
1298
1247
1299
- if ( opts . ignore_warnings ) {
1300
- args . unshift ( "--no-warnings" ) ;
1301
- }
1302
-
1303
1248
if ( this . args . debugPort ) {
1304
- if ( process . env . FIREPIT_VERSION && process . execPath === opts . nodeBinary ) {
1305
- const requestedMajorNodeVersion = this . getNodeBinary ( backend ) ;
1249
+ if ( process . env . FIREPIT_VERSION && process . execPath === backend . nodeBinary ) {
1306
1250
this . logger . log (
1307
1251
"WARN" ,
1308
- `To enable function inspection, please run "${ process . execPath } is:npm i node@${ requestedMajorNodeVersion } --save-dev" in your functions directory`
1252
+ `To enable function inspection, please run "${ process . execPath } is:npm i node@${ backend . nodeMajorVersion } --save-dev" in your functions directory`
1309
1253
) ;
1310
1254
} else {
1311
1255
const { host } = this . getInfo ( ) ;
@@ -1332,10 +1276,10 @@ export class FunctionsEmulator implements EmulatorInstance {
1332
1276
const secretEnvs = await this . resolveSecretEnvs ( backend , trigger ) ;
1333
1277
const socketPath = getTemporarySocketPath ( ) ;
1334
1278
1335
- const childProcess = spawn ( opts . nodeBinary , args , {
1279
+ const childProcess = spawn ( backend . nodeBinary ! , args , {
1336
1280
cwd : backend . functionsDir ,
1337
1281
env : {
1338
- node : opts . nodeBinary ,
1282
+ node : backend . nodeBinary ,
1339
1283
...process . env ,
1340
1284
...runtimeEnv ,
1341
1285
...secretEnvs ,
@@ -1357,7 +1301,7 @@ export class FunctionsEmulator implements EmulatorInstance {
1357
1301
const pool = this . workerPools [ backend . codebase ] ;
1358
1302
const worker = pool . addWorker ( trigger ?. id , runtime , extensionLogInfo ) ;
1359
1303
await worker . waitForSocketReady ( ) ;
1360
- return ;
1304
+ return worker ;
1361
1305
}
1362
1306
1363
1307
async disableBackgroundTriggers ( ) {
@@ -1480,20 +1424,12 @@ export class FunctionsEmulator implements EmulatorInstance {
1480
1424
) ;
1481
1425
}
1482
1426
}
1483
- const worker = await this . invokeTrigger ( trigger ) ;
1484
-
1485
1427
// For analytics, track the invoked service
1486
1428
void track ( EVENT_INVOKE , getFunctionService ( trigger ) ) ;
1487
1429
void trackEmulator ( EVENT_INVOKE_GA4 , {
1488
1430
function_service : getFunctionService ( trigger ) ,
1489
1431
} ) ;
1490
1432
1491
- worker . onLogs ( ( el : EmulatorLog ) => {
1492
- if ( el . level === "FATAL" ) {
1493
- res . status ( 500 ) . send ( el . text ) ;
1494
- }
1495
- } ) ;
1496
-
1497
1433
this . logger . log ( "DEBUG" , `[functions] Runtime ready! Sending request!` ) ;
1498
1434
1499
1435
// To match production behavior we need to drop the path prefix
@@ -1508,59 +1444,27 @@ export class FunctionsEmulator implements EmulatorInstance {
1508
1444
// cause unexpected situations - not to mention CORS troubles and this enables us to use
1509
1445
// a socketPath (IPC socket) instead of consuming yet another port which is probably faster as well.
1510
1446
this . logger . log ( "DEBUG" , `[functions] Got req.url=${ req . url } , mapping to path=${ path } ` ) ;
1511
- const runtimeReq = http . request (
1447
+
1448
+ const pool = this . workerPools [ record . backend . codebase ] ;
1449
+ if ( ! pool . readyForWork ( trigger . id ) ) {
1450
+ await this . startRuntime ( record . backend , trigger ) ;
1451
+ }
1452
+ const debugBundle = this . args . debugPort
1453
+ ? {
1454
+ functionTarget : trigger . entryPoint ,
1455
+ functionSignature : getSignatureType ( trigger ) ,
1456
+ }
1457
+ : undefined ;
1458
+ await pool . submitRequest (
1459
+ trigger . id ,
1512
1460
{
1513
1461
method,
1514
1462
path,
1515
1463
headers : req . headers ,
1516
- socketPath : worker . runtime . socketPath ,
1517
1464
} ,
1518
- ( runtimeRes : http . IncomingMessage ) => {
1519
- function forwardStatusAndHeaders ( ) : void {
1520
- res . status ( runtimeRes . statusCode || 200 ) ;
1521
- if ( ! res . headersSent ) {
1522
- Object . keys ( runtimeRes . headers ) . forEach ( ( key ) => {
1523
- const val = runtimeRes . headers [ key ] ;
1524
- if ( val ) {
1525
- res . setHeader ( key , val ) ;
1526
- }
1527
- } ) ;
1528
- }
1529
- }
1530
-
1531
- runtimeRes . on ( "data" , ( buf ) => {
1532
- forwardStatusAndHeaders ( ) ;
1533
- res . write ( buf ) ;
1534
- } ) ;
1535
-
1536
- runtimeRes . on ( "close" , ( ) => {
1537
- forwardStatusAndHeaders ( ) ;
1538
- res . end ( ) ;
1539
- } ) ;
1540
-
1541
- runtimeRes . on ( "end" , ( ) => {
1542
- forwardStatusAndHeaders ( ) ;
1543
- res . end ( ) ;
1544
- } ) ;
1545
- }
1465
+ res ,
1466
+ reqBody ,
1467
+ debugBundle
1546
1468
) ;
1547
-
1548
- runtimeReq . on ( "error" , ( ) => {
1549
- res . end ( ) ;
1550
- } ) ;
1551
-
1552
- // If the original request had a body, forward that over the connection.
1553
- // TODO: Why is this not handled by the pipe?
1554
- if ( reqBody ) {
1555
- runtimeReq . write ( reqBody ) ;
1556
- runtimeReq . end ( ) ;
1557
- }
1558
-
1559
- // Pipe the incoming request over the socket.
1560
- req . pipe ( runtimeReq , { end : true } ) . on ( "error" , ( ) => {
1561
- res . end ( ) ;
1562
- } ) ;
1563
-
1564
- await worker . waitForDone ( ) ;
1565
1469
}
1566
1470
}
0 commit comments