No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

functionsEmulatorRuntime.js 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const fs = require("fs");
  4. const express = require("express");
  5. const path = require("path");
  6. const bodyParser = require("body-parser");
  7. const url_1 = require("url");
  8. const _ = require("lodash");
  9. const types_1 = require("./types");
  10. const constants_1 = require("./constants");
  11. const functionsEmulatorShared_1 = require("./functionsEmulatorShared");
  12. const functionsEmulatorUtils_1 = require("./functionsEmulatorUtils");
  13. const types_2 = require("./events/types");
  14. let functionModule;
  15. let FUNCTION_TARGET_NAME;
  16. let FUNCTION_SIGNATURE;
  17. let FUNCTION_DEBUG_MODE;
  18. let developerPkgJSON;
  19. const dynamicImport = new Function("modulePath", "return import(modulePath)");
  20. function noOp() {
  21. return false;
  22. }
  23. function requireAsync(moduleName, opts) {
  24. return new Promise((res, rej) => {
  25. try {
  26. res(require(require.resolve(moduleName, opts)));
  27. }
  28. catch (e) {
  29. rej(e);
  30. }
  31. });
  32. }
  33. function requireResolveAsync(moduleName, opts) {
  34. return new Promise((res, rej) => {
  35. try {
  36. res(require.resolve(moduleName, opts));
  37. }
  38. catch (e) {
  39. rej(e);
  40. }
  41. });
  42. }
  43. class Proxied {
  44. constructor(original) {
  45. this.original = original;
  46. this.rewrites = {};
  47. this.proxy = new Proxy(this.original, {
  48. get: (target, key) => {
  49. key = key.toString();
  50. if (this.rewrites[key]) {
  51. return this.rewrites[key](target, key);
  52. }
  53. if (this.anyValue) {
  54. return this.anyValue(target, key);
  55. }
  56. return Proxied.getOriginal(target, key);
  57. },
  58. apply: (target, thisArg, argArray) => {
  59. if (this.appliedValue) {
  60. return this.appliedValue.apply(thisArg);
  61. }
  62. else {
  63. return Proxied.applyOriginal(target, thisArg, argArray);
  64. }
  65. },
  66. });
  67. }
  68. static getOriginal(target, key) {
  69. const value = target[key];
  70. if (!Proxied.isExists(value)) {
  71. return undefined;
  72. }
  73. else if (Proxied.isConstructor(value) || typeof value !== "function") {
  74. return value;
  75. }
  76. else {
  77. return value.bind(target);
  78. }
  79. }
  80. static applyOriginal(target, thisArg, argArray) {
  81. return target.apply(thisArg, argArray);
  82. }
  83. static isConstructor(obj) {
  84. return !!obj.prototype && !!obj.prototype.constructor.name;
  85. }
  86. static isExists(obj) {
  87. return obj !== undefined;
  88. }
  89. when(key, value) {
  90. this.rewrites[key] = value;
  91. return this;
  92. }
  93. any(value) {
  94. this.anyValue = value;
  95. return this;
  96. }
  97. applied(value) {
  98. this.appliedValue = value;
  99. return this;
  100. }
  101. finalize() {
  102. return this.proxy;
  103. }
  104. }
  105. async function resolveDeveloperNodeModule(name) {
  106. const pkg = requirePackageJson();
  107. if (!pkg) {
  108. new types_1.EmulatorLog("SYSTEM", "missing-package-json", "").log();
  109. throw new Error("Could not find package.json");
  110. }
  111. const dependencies = pkg.dependencies;
  112. const devDependencies = pkg.devDependencies;
  113. const isInPackageJSON = dependencies[name] || devDependencies[name];
  114. if (!isInPackageJSON) {
  115. return { declared: false, installed: false };
  116. }
  117. const resolveResult = await requireResolveAsync(name, { paths: [process.cwd()] }).catch(noOp);
  118. if (!resolveResult) {
  119. return { declared: true, installed: false };
  120. }
  121. const modPackageJSON = require(path.join((0, functionsEmulatorShared_1.findModuleRoot)(name, resolveResult), "package.json"));
  122. const moduleResolution = {
  123. declared: true,
  124. installed: true,
  125. version: modPackageJSON.version,
  126. resolution: resolveResult,
  127. };
  128. logDebug(`Resolved module ${name}`, moduleResolution);
  129. return moduleResolution;
  130. }
  131. async function assertResolveDeveloperNodeModule(name) {
  132. const resolution = await resolveDeveloperNodeModule(name);
  133. if (!(resolution.installed && resolution.declared && resolution.resolution && resolution.version)) {
  134. throw new Error(`Assertion failure: could not fully resolve ${name}: ${JSON.stringify(resolution)}`);
  135. }
  136. return resolution;
  137. }
  138. async function verifyDeveloperNodeModules() {
  139. const modBundles = [
  140. { name: "firebase-admin", isDev: false, minVersion: "8.9.0" },
  141. { name: "firebase-functions", isDev: false, minVersion: "3.13.1" },
  142. ];
  143. for (const modBundle of modBundles) {
  144. const resolution = await resolveDeveloperNodeModule(modBundle.name);
  145. if (!resolution.declared) {
  146. new types_1.EmulatorLog("SYSTEM", "missing-module", "", modBundle).log();
  147. return false;
  148. }
  149. if (!resolution.installed) {
  150. new types_1.EmulatorLog("SYSTEM", "uninstalled-module", "", modBundle).log();
  151. return false;
  152. }
  153. if ((0, functionsEmulatorUtils_1.compareVersionStrings)(resolution.version, modBundle.minVersion) < 0) {
  154. new types_1.EmulatorLog("SYSTEM", "out-of-date-module", "", modBundle).log();
  155. return false;
  156. }
  157. }
  158. return true;
  159. }
  160. function requirePackageJson() {
  161. if (developerPkgJSON) {
  162. return developerPkgJSON;
  163. }
  164. try {
  165. const pkg = require(`${process.cwd()}/package.json`);
  166. developerPkgJSON = {
  167. engines: pkg.engines || {},
  168. dependencies: pkg.dependencies || {},
  169. devDependencies: pkg.devDependencies || {},
  170. };
  171. return developerPkgJSON;
  172. }
  173. catch (err) {
  174. return;
  175. }
  176. }
  177. function initializeNetworkFiltering() {
  178. const networkingModules = [
  179. { name: "http", module: require("http"), path: ["request"] },
  180. { name: "http", module: require("http"), path: ["get"] },
  181. { name: "https", module: require("https"), path: ["request"] },
  182. { name: "https", module: require("https"), path: ["get"] },
  183. { name: "net", module: require("net"), path: ["connect"] },
  184. ];
  185. const history = {};
  186. const results = networkingModules.map((bundle) => {
  187. let obj = bundle.module;
  188. for (const field of bundle.path.slice(0, -1)) {
  189. obj = obj[field];
  190. }
  191. const method = bundle.path.slice(-1)[0];
  192. const original = obj[method].bind(bundle.module);
  193. obj[method] = function (...args) {
  194. const hrefs = args
  195. .map((arg) => {
  196. if (typeof arg === "string") {
  197. try {
  198. new url_1.URL(arg);
  199. return arg;
  200. }
  201. catch (err) {
  202. return;
  203. }
  204. }
  205. else if (typeof arg === "object") {
  206. return arg.href;
  207. }
  208. else {
  209. return;
  210. }
  211. })
  212. .filter((v) => v);
  213. const href = (hrefs.length && hrefs[0]) || "";
  214. if (href && !history[href] && !(0, functionsEmulatorUtils_1.isLocalHost)(href)) {
  215. history[href] = true;
  216. if (href.indexOf("googleapis.com") !== -1) {
  217. new types_1.EmulatorLog("SYSTEM", "googleapis-network-access", "", {
  218. href,
  219. module: bundle.name,
  220. }).log();
  221. }
  222. else {
  223. new types_1.EmulatorLog("SYSTEM", "unidentified-network-access", "", {
  224. href,
  225. module: bundle.name,
  226. }).log();
  227. }
  228. }
  229. try {
  230. return original(...args);
  231. }
  232. catch (e) {
  233. const newed = new original(...args);
  234. return newed;
  235. }
  236. };
  237. return { name: bundle.name, status: "mocked" };
  238. });
  239. logDebug("Outgoing network have been stubbed.", results);
  240. }
  241. async function initializeFirebaseFunctionsStubs() {
  242. const firebaseFunctionsResolution = await assertResolveDeveloperNodeModule("firebase-functions");
  243. const firebaseFunctionsRoot = (0, functionsEmulatorShared_1.findModuleRoot)("firebase-functions", firebaseFunctionsResolution.resolution);
  244. const httpsProviderResolution = path.join(firebaseFunctionsRoot, "lib/providers/https");
  245. const httpsProviderV1Resolution = path.join(firebaseFunctionsRoot, "lib/v1/providers/https");
  246. let httpsProvider;
  247. try {
  248. httpsProvider = require(httpsProviderV1Resolution);
  249. }
  250. catch (e) {
  251. httpsProvider = require(httpsProviderResolution);
  252. }
  253. const onRequestInnerMethodName = "_onRequestWithOptions";
  254. const onRequestMethodOriginal = httpsProvider[onRequestInnerMethodName];
  255. httpsProvider[onRequestInnerMethodName] = (handler, opts) => {
  256. const cf = onRequestMethodOriginal(handler, opts);
  257. cf.__emulator_func = handler;
  258. return cf;
  259. };
  260. httpsProvider.onRequest = (handler) => {
  261. return httpsProvider[onRequestInnerMethodName](handler, {});
  262. };
  263. const onCallInnerMethodName = "_onCallWithOptions";
  264. const onCallMethodOriginal = httpsProvider[onCallInnerMethodName];
  265. if (onCallMethodOriginal.length === 3) {
  266. httpsProvider[onCallInnerMethodName] = (opts, handler, deployOpts) => {
  267. const wrapped = wrapCallableHandler(handler);
  268. const cf = onCallMethodOriginal(opts, wrapped, deployOpts);
  269. return cf;
  270. };
  271. }
  272. else {
  273. httpsProvider[onCallInnerMethodName] = (handler, opts) => {
  274. const wrapped = wrapCallableHandler(handler);
  275. const cf = onCallMethodOriginal(wrapped, opts);
  276. return cf;
  277. };
  278. }
  279. httpsProvider.onCall = function (optsOrHandler, handler) {
  280. if (onCallMethodOriginal.length === 3) {
  281. let opts;
  282. if (arguments.length === 1) {
  283. opts = {};
  284. handler = optsOrHandler;
  285. }
  286. else {
  287. opts = optsOrHandler;
  288. }
  289. return httpsProvider[onCallInnerMethodName](opts, handler, {});
  290. }
  291. else {
  292. return httpsProvider[onCallInnerMethodName](optsOrHandler, {});
  293. }
  294. };
  295. }
  296. function wrapCallableHandler(handler) {
  297. const newHandler = (data, context) => {
  298. if (context.rawRequest) {
  299. const authContext = context.rawRequest.header(functionsEmulatorShared_1.HttpConstants.CALLABLE_AUTH_HEADER);
  300. if (authContext) {
  301. logDebug("Callable functions auth override", {
  302. key: functionsEmulatorShared_1.HttpConstants.CALLABLE_AUTH_HEADER,
  303. value: authContext,
  304. });
  305. context.auth = JSON.parse(decodeURIComponent(authContext));
  306. delete context.rawRequest.headers[functionsEmulatorShared_1.HttpConstants.CALLABLE_AUTH_HEADER];
  307. }
  308. else {
  309. logDebug("No callable functions auth found");
  310. }
  311. const originalAuth = context.rawRequest.header(functionsEmulatorShared_1.HttpConstants.ORIGINAL_AUTH_HEADER);
  312. if (originalAuth) {
  313. context.rawRequest.headers["authorization"] = originalAuth;
  314. delete context.rawRequest.headers[functionsEmulatorShared_1.HttpConstants.ORIGINAL_AUTH_HEADER];
  315. }
  316. }
  317. return handler(data, context);
  318. };
  319. return newHandler;
  320. }
  321. function getDefaultConfig() {
  322. return JSON.parse(process.env.FIREBASE_CONFIG || "{}");
  323. }
  324. function initializeRuntimeConfig() {
  325. if (!process.env.CLOUD_RUNTIME_CONFIG) {
  326. const configPath = `${process.cwd()}/.runtimeconfig.json`;
  327. try {
  328. const configContent = fs.readFileSync(configPath, "utf8");
  329. if (configContent) {
  330. try {
  331. JSON.parse(configContent.toString());
  332. logDebug(`Found local functions config: ${configPath}`);
  333. process.env.CLOUD_RUNTIME_CONFIG = configContent.toString();
  334. }
  335. catch (e) {
  336. new types_1.EmulatorLog("SYSTEM", "function-runtimeconfig-json-invalid", "").log();
  337. }
  338. }
  339. }
  340. catch (e) {
  341. }
  342. }
  343. }
  344. async function initializeFirebaseAdminStubs() {
  345. const adminResolution = await assertResolveDeveloperNodeModule("firebase-admin");
  346. const localAdminModule = require(adminResolution.resolution);
  347. const functionsResolution = await assertResolveDeveloperNodeModule("firebase-functions");
  348. const localFunctionsModule = require(functionsResolution.resolution);
  349. const defaultConfig = getDefaultConfig();
  350. const adminModuleProxy = new Proxied(localAdminModule);
  351. const proxiedAdminModule = adminModuleProxy
  352. .when("initializeApp", (adminModuleTarget) => (opts, appName) => {
  353. if (appName) {
  354. new types_1.EmulatorLog("SYSTEM", "non-default-admin-app-used", "", { appName, opts }).log();
  355. return adminModuleTarget.initializeApp(opts, appName);
  356. }
  357. const defaultAppOptions = opts ? opts : defaultConfig;
  358. new types_1.EmulatorLog("SYSTEM", "default-admin-app-used", `config=${defaultAppOptions}`, {
  359. opts: defaultAppOptions,
  360. }).log();
  361. const defaultApp = makeProxiedFirebaseApp(adminModuleTarget.initializeApp(defaultAppOptions));
  362. logDebug("initializeApp(DEFAULT)", defaultAppOptions);
  363. localFunctionsModule.app.setEmulatedAdminApp(defaultApp);
  364. if (process.env[constants_1.Constants.FIREBASE_AUTH_EMULATOR_HOST]) {
  365. if ((0, functionsEmulatorUtils_1.compareVersionStrings)(adminResolution.version, "9.3.0") < 0) {
  366. new types_1.EmulatorLog("WARN_ONCE", "runtime-status", "The Firebase Authentication emulator is running, but your 'firebase-admin' dependency is below version 9.3.0, so calls to Firebase Authentication will affect production.").log();
  367. }
  368. else if ((0, functionsEmulatorUtils_1.compareVersionStrings)(adminResolution.version, "9.4.2") <= 0) {
  369. const auth = defaultApp.auth();
  370. if (typeof auth.setJwtVerificationEnabled === "function") {
  371. logDebug("auth.setJwtVerificationEnabled(false)", {});
  372. auth.setJwtVerificationEnabled(false);
  373. }
  374. else {
  375. logDebug("auth.setJwtVerificationEnabled not available", {});
  376. }
  377. }
  378. }
  379. return defaultApp;
  380. })
  381. .when("firestore", (target) => {
  382. warnAboutFirestoreProd();
  383. return Proxied.getOriginal(target, "firestore");
  384. })
  385. .when("database", (target) => {
  386. warnAboutDatabaseProd();
  387. return Proxied.getOriginal(target, "database");
  388. })
  389. .when("auth", (target) => {
  390. warnAboutAuthProd();
  391. return Proxied.getOriginal(target, "auth");
  392. })
  393. .when("storage", (target) => {
  394. warnAboutStorageProd();
  395. return Proxied.getOriginal(target, "storage");
  396. })
  397. .finalize();
  398. require.cache[adminResolution.resolution] = Object.assign(require.cache[adminResolution.resolution], {
  399. exports: proxiedAdminModule,
  400. path: path.dirname(adminResolution.resolution),
  401. });
  402. logDebug("firebase-admin has been stubbed.", {
  403. adminResolution,
  404. });
  405. }
  406. function makeProxiedFirebaseApp(original) {
  407. const appProxy = new Proxied(original);
  408. return appProxy
  409. .when("firestore", (target) => {
  410. warnAboutFirestoreProd();
  411. return Proxied.getOriginal(target, "firestore");
  412. })
  413. .when("database", (target) => {
  414. warnAboutDatabaseProd();
  415. return Proxied.getOriginal(target, "database");
  416. })
  417. .when("auth", (target) => {
  418. warnAboutAuthProd();
  419. return Proxied.getOriginal(target, "auth");
  420. })
  421. .when("storage", (target) => {
  422. warnAboutStorageProd();
  423. return Proxied.getOriginal(target, "storage");
  424. })
  425. .finalize();
  426. }
  427. function warnAboutFirestoreProd() {
  428. if (process.env[constants_1.Constants.FIRESTORE_EMULATOR_HOST]) {
  429. return;
  430. }
  431. new types_1.EmulatorLog("WARN_ONCE", "runtime-status", "The Cloud Firestore emulator is not running, so calls to Firestore will affect production.").log();
  432. }
  433. function warnAboutDatabaseProd() {
  434. if (process.env[constants_1.Constants.FIREBASE_DATABASE_EMULATOR_HOST]) {
  435. return;
  436. }
  437. new types_1.EmulatorLog("WARN_ONCE", "runtime-status", "The Realtime Database emulator is not running, so calls to Realtime Database will affect production.").log();
  438. }
  439. function warnAboutAuthProd() {
  440. if (process.env[constants_1.Constants.FIREBASE_AUTH_EMULATOR_HOST]) {
  441. return;
  442. }
  443. new types_1.EmulatorLog("WARN_ONCE", "runtime-status", "The Firebase Authentication emulator is not running, so calls to Firebase Authentication will affect production.").log();
  444. }
  445. function warnAboutStorageProd() {
  446. if (process.env[constants_1.Constants.FIREBASE_STORAGE_EMULATOR_HOST]) {
  447. return;
  448. }
  449. new types_1.EmulatorLog("WARN_ONCE", "runtime-status", "The Firebase Storage emulator is not running, so calls to Firebase Storage will affect production.").log();
  450. }
  451. async function initializeFunctionsConfigHelper() {
  452. const functionsResolution = await assertResolveDeveloperNodeModule("firebase-functions");
  453. const localFunctionsModule = require(functionsResolution.resolution);
  454. logDebug("Checked functions.config()", {
  455. config: localFunctionsModule.config(),
  456. });
  457. const originalConfig = localFunctionsModule.config();
  458. const proxiedConfig = new Proxied(originalConfig)
  459. .any((parentConfig, parentKey) => {
  460. const isInternal = parentKey.startsWith("Symbol(") || parentKey.startsWith("inspect");
  461. if (!parentConfig[parentKey] && !isInternal) {
  462. new types_1.EmulatorLog("SYSTEM", "functions-config-missing-value", "", {
  463. key: parentKey,
  464. }).log();
  465. }
  466. return parentConfig[parentKey];
  467. })
  468. .finalize();
  469. const functionsModuleProxy = new Proxied(localFunctionsModule);
  470. const proxiedFunctionsModule = functionsModuleProxy
  471. .when("config", () => () => {
  472. return proxiedConfig;
  473. })
  474. .finalize();
  475. require.cache[functionsResolution.resolution] = Object.assign(require.cache[functionsResolution.resolution], {
  476. exports: proxiedFunctionsModule,
  477. path: path.dirname(functionsResolution.resolution),
  478. });
  479. logDebug("firebase-functions has been stubbed.", {
  480. functionsResolution,
  481. });
  482. }
  483. function rawBodySaver(req, res, buf) {
  484. req.rawBody = buf;
  485. }
  486. async function processBackground(trigger, reqBody, signature) {
  487. if (signature === "cloudevent") {
  488. return runCloudEvent(trigger, reqBody);
  489. }
  490. const data = reqBody.data;
  491. delete reqBody.data;
  492. const context = reqBody.context ? reqBody.context : reqBody;
  493. if (!reqBody.eventType || !reqBody.eventType.startsWith("google.storage")) {
  494. if (context.resource && context.resource.name) {
  495. logDebug("ProcessBackground: lifting resource.name from resource", context.resource);
  496. context.resource = context.resource.name;
  497. }
  498. }
  499. await runBackground(trigger, { data, context });
  500. }
  501. async function runFunction(func) {
  502. let caughtErr;
  503. try {
  504. await func();
  505. }
  506. catch (err) {
  507. caughtErr = err;
  508. }
  509. if (caughtErr) {
  510. throw caughtErr;
  511. }
  512. }
  513. async function runBackground(trigger, reqBody) {
  514. logDebug("RunBackground", reqBody);
  515. await runFunction(() => {
  516. return trigger(reqBody.data, reqBody.context);
  517. });
  518. }
  519. async function runCloudEvent(trigger, event) {
  520. logDebug("RunCloudEvent", event);
  521. await runFunction(() => {
  522. return trigger(event);
  523. });
  524. }
  525. async function runHTTPS(trigger, args) {
  526. if (args.length < 2) {
  527. throw new Error("Function must be passed 2 args.");
  528. }
  529. await runFunction(() => {
  530. return trigger(args[0], args[1]);
  531. });
  532. }
  533. async function moduleResolutionDetective(error) {
  534. const clues = {
  535. tsconfigJSON: await requireAsync("./tsconfig.json", { paths: [process.cwd()] }).catch(noOp),
  536. packageJSON: await requireAsync("./package.json", { paths: [process.cwd()] }).catch(noOp),
  537. };
  538. const isPotentially = {
  539. typescript: false,
  540. uncompiled: false,
  541. wrong_directory: false,
  542. };
  543. isPotentially.typescript = !!clues.tsconfigJSON;
  544. isPotentially.wrong_directory = !clues.packageJSON;
  545. isPotentially.uncompiled = !!_.get(clues.packageJSON, "scripts.build", false);
  546. new types_1.EmulatorLog("SYSTEM", "function-code-resolution-failed", "", {
  547. isPotentially,
  548. error: error.stack,
  549. }).log();
  550. }
  551. function logDebug(msg, data) {
  552. new types_1.EmulatorLog("DEBUG", "runtime-status", `[${process.pid}] ${msg}`, data).log();
  553. }
  554. async function initializeRuntime() {
  555. FUNCTION_DEBUG_MODE = process.env.FUNCTION_DEBUG_MODE || "";
  556. if (!FUNCTION_DEBUG_MODE) {
  557. FUNCTION_TARGET_NAME = process.env.FUNCTION_TARGET || "";
  558. if (!FUNCTION_TARGET_NAME) {
  559. new types_1.EmulatorLog("FATAL", "runtime-status", `Environment variable FUNCTION_TARGET cannot be empty. This shouldn't happen.`).log();
  560. await flushAndExit(1);
  561. }
  562. FUNCTION_SIGNATURE = process.env.FUNCTION_SIGNATURE_TYPE || "";
  563. if (!FUNCTION_SIGNATURE) {
  564. new types_1.EmulatorLog("FATAL", "runtime-status", `Environment variable FUNCTION_SIGNATURE_TYPE cannot be empty. This shouldn't happen.`).log();
  565. await flushAndExit(1);
  566. }
  567. }
  568. const verified = await verifyDeveloperNodeModules();
  569. if (!verified) {
  570. new types_1.EmulatorLog("INFO", "runtime-status", `Your functions could not be parsed due to an issue with your node_modules (see above)`).log();
  571. return;
  572. }
  573. initializeRuntimeConfig();
  574. initializeNetworkFiltering();
  575. await initializeFunctionsConfigHelper();
  576. await initializeFirebaseFunctionsStubs();
  577. await initializeFirebaseAdminStubs();
  578. }
  579. async function loadTriggers() {
  580. let triggerModule;
  581. try {
  582. triggerModule = require(process.cwd());
  583. }
  584. catch (err) {
  585. if (err.code !== "ERR_REQUIRE_ESM") {
  586. await moduleResolutionDetective(err);
  587. throw err;
  588. }
  589. const modulePath = require.resolve(process.cwd());
  590. const moduleURL = (0, url_1.pathToFileURL)(modulePath).href;
  591. triggerModule = await dynamicImport(moduleURL);
  592. }
  593. return triggerModule;
  594. }
  595. async function flushAndExit(code) {
  596. await types_1.EmulatorLog.waitForFlush();
  597. process.exit(code);
  598. }
  599. async function handleMessage(message) {
  600. let debug;
  601. try {
  602. debug = JSON.parse(message);
  603. }
  604. catch (e) {
  605. new types_1.EmulatorLog("FATAL", "runtime-error", `Got unexpected message body: ${message}`).log();
  606. await flushAndExit(1);
  607. return;
  608. }
  609. if (FUNCTION_DEBUG_MODE) {
  610. if (debug) {
  611. FUNCTION_TARGET_NAME = debug.functionTarget;
  612. FUNCTION_SIGNATURE = debug.functionSignature;
  613. }
  614. else {
  615. new types_1.EmulatorLog("WARN", "runtime-warning", "Expected debug payload while in debug mode.");
  616. }
  617. }
  618. }
  619. async function main() {
  620. let lastSignal = new Date().getTime();
  621. let signalCount = 0;
  622. process.on("SIGINT", () => {
  623. const now = new Date().getTime();
  624. if (now - lastSignal < 100) {
  625. return;
  626. }
  627. signalCount = signalCount + 1;
  628. lastSignal = now;
  629. if (signalCount >= 2) {
  630. process.exit(1);
  631. }
  632. });
  633. await initializeRuntime();
  634. try {
  635. functionModule = await loadTriggers();
  636. }
  637. catch (e) {
  638. new types_1.EmulatorLog("FATAL", "runtime-status", `Failed to initialize and load triggers. This shouldn't happen: ${e.message}`).log();
  639. await flushAndExit(1);
  640. }
  641. const app = express();
  642. app.enable("trust proxy");
  643. app.use(bodyParser.json({
  644. limit: "10mb",
  645. verify: rawBodySaver,
  646. }));
  647. app.use(bodyParser.text({
  648. limit: "10mb",
  649. verify: rawBodySaver,
  650. }));
  651. app.use(bodyParser.urlencoded({
  652. extended: true,
  653. limit: "10mb",
  654. verify: rawBodySaver,
  655. }));
  656. app.use(bodyParser.raw({
  657. type: "*/*",
  658. limit: "10mb",
  659. verify: rawBodySaver,
  660. }));
  661. app.get("/__/health", (req, res) => {
  662. res.status(200).send();
  663. });
  664. app.all("/favicon.ico|/robots.txt", (req, res) => {
  665. res.status(404).send();
  666. });
  667. app.all(`/*`, async (req, res) => {
  668. try {
  669. new types_1.EmulatorLog("INFO", "runtime-status", `Beginning execution of "${FUNCTION_TARGET_NAME}"`).log();
  670. const trigger = FUNCTION_TARGET_NAME.split(".").reduce((mod, functionTargetPart) => {
  671. return mod === null || mod === void 0 ? void 0 : mod[functionTargetPart];
  672. }, functionModule);
  673. if (!trigger) {
  674. throw new Error(`Failed to find function ${FUNCTION_TARGET_NAME} in the loaded module`);
  675. }
  676. const startHrTime = process.hrtime();
  677. res.on("finish", () => {
  678. const elapsedHrTime = process.hrtime(startHrTime);
  679. new types_1.EmulatorLog("INFO", "runtime-status", `Finished "${FUNCTION_TARGET_NAME}" in ${elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1000000}ms`).log();
  680. });
  681. switch (FUNCTION_SIGNATURE) {
  682. case "event":
  683. case "cloudevent":
  684. const rawBody = req.rawBody;
  685. let reqBody = JSON.parse(rawBody.toString());
  686. if (types_2.EventUtils.isBinaryCloudEvent(req)) {
  687. reqBody = types_2.EventUtils.extractBinaryCloudEventContext(req);
  688. reqBody.data = req.body;
  689. }
  690. await processBackground(trigger, reqBody, FUNCTION_SIGNATURE);
  691. res.send({ status: "acknowledged" });
  692. break;
  693. case "http":
  694. await runHTTPS(trigger, [req, res]);
  695. }
  696. }
  697. catch (err) {
  698. new types_1.EmulatorLog("FATAL", "runtime-error", err.stack ? err.stack : err).log();
  699. res.status(500).send(err.message);
  700. }
  701. });
  702. const server = app.listen(process.env.PORT, () => {
  703. logDebug(`Listening to port: ${process.env.PORT}`);
  704. });
  705. if (!FUNCTION_DEBUG_MODE) {
  706. let timeout = process.env.FUNCTIONS_EMULATOR_TIMEOUT_SECONDS || "60";
  707. if (timeout.endsWith("s")) {
  708. timeout = timeout.slice(0, -1);
  709. }
  710. const timeoutMs = parseInt(timeout, 10) * 1000;
  711. server.setTimeout(timeoutMs, () => {
  712. new types_1.EmulatorLog("FATAL", "runtime-error", `Your function timed out after ~${timeout}s. To configure this timeout, see
  713. https://firebase.google.com/docs/functions/manage-functions#set_timeout_and_memory_allocation.`).log();
  714. return flushAndExit(1);
  715. });
  716. }
  717. let messageHandlePromise = Promise.resolve();
  718. process.on("message", (message) => {
  719. messageHandlePromise = messageHandlePromise
  720. .then(() => {
  721. return handleMessage(message);
  722. })
  723. .catch((err) => {
  724. logDebug(`Error in handleMessage: ${message} => ${err}: ${err.stack}`);
  725. new types_1.EmulatorLog("FATAL", "runtime-error", err.message || err, err).log();
  726. return flushAndExit(1);
  727. });
  728. });
  729. }
  730. if (require.main === module) {
  731. main()
  732. .then(() => {
  733. logDebug("Functions runtime initialized.", {
  734. cwd: process.cwd(),
  735. node_version: process.versions.node,
  736. });
  737. })
  738. .catch((err) => {
  739. new types_1.EmulatorLog("FATAL", "runtime-error", err.message || err, err).log();
  740. return flushAndExit(1);
  741. });
  742. }