diff --git a/changelog.txt b/changelog.txt index 097ecb61f26..fb0a160cc22 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,7 @@ fixed - Functions emulator no longer watches node_modules files. fixed - Functions emulator fails to route HTTPS functions to user-provided Express app fixed - Functions emulator fails to provide correct req.path fixed - Functions emulator fails on various malformed body requests +fixed - Functions emulator fails on Windows with EACCESS error fixed - Fixed race condition where downloading emulators would sometimes resolve too early and fail. fixed - Fixed a number of issues that broke functions:shell. fixed - Fixed an issue where Firestore emulator would not start if rules file can't be found. \ No newline at end of file diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index d9698b6269a..cb071c3a0af 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -17,6 +17,8 @@ import * as path from "path"; import * as admin from "firebase-admin"; import * as bodyParser from "body-parser"; import { EventUtils } from "./events/types"; +import * as fs from "fs"; +import { URL } from "url"; let app: admin.app.App; let adminModuleProxy: typeof admin; @@ -535,6 +537,12 @@ async function ProcessHTTPS(frb: FunctionsRuntimeBundle, trigger: EmulatedTrigge res.on("finish", () => { instance.close(); resolveEphemeralServer(); + + // If we're on a Unix platform, then the pipe is not cleaned up automatically so... + if (process.platform !== "win32") { + // We manually remove the pipe file + fs.unlinkSync(socketPath); + } }); await RunHTTPS([req, res], func); diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index 064ba4278c5..5a11cf851e2 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -127,7 +127,13 @@ export function getEmulatedTriggersFromDefinitions( } export function getTemporarySocketPath(pid: number): string { - return path.join(os.tmpdir(), `firebase_emulator_invocation_${pid}.sock`); + // See "net" package docs for information about IPC pipes on Windows + // https://nodejs.org/api/net.html#net_identifying_paths_for_ipc_connections + if (process.platform === "win32") { + return path.join("\\\\?\\pipe", process.cwd(), pid.toString()); + } else { + return path.join(os.tmpdir(), `firebase_emulator_invocation_${pid}.sock`); + } } export function getFunctionRegion(def: EmulatedTriggerDefinition): string { diff --git a/src/test/emulators/functionsEmulatorRuntime.spec.ts b/src/test/emulators/functionsEmulatorRuntime.spec.ts index d43c06d592e..c29703ccf49 100644 --- a/src/test/emulators/functionsEmulatorRuntime.spec.ts +++ b/src/test/emulators/functionsEmulatorRuntime.spec.ts @@ -47,34 +47,61 @@ function _is_verbose(runtime: FunctionsRuntimeInstance): void { describe("FunctionsEmulator-Runtime", () => { describe("Stubs, Mocks, and Helpers (aka Magic, Glee, and Awesomeness)", () => { describe("_InitializeNetworkFiltering(...)", () => { - it("should log outgoing HTTPS requests", async () => { + it("should log outgoing unknown HTTP requests via 'http'", async () => { const runtime = InvokeRuntimeWithFunctions(FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); return { function_id: require("firebase-functions") .firestore.document("test/test") .onCreate(async () => { - await Promise.all([ - require("node-fetch")("https://httpstat.us/302"), - require("node-fetch")("https://storage.googleapis.com/"), - new Promise((resolve) => { - require("http").get("http://example.com", resolve); - }), - new Promise((resolve) => { - require("https").get("https://example.com", resolve); - }), - ]); + await new Promise((resolve) => { + // tslint:disable-next-line:no-console + console.log(require("http").get.toString()); + require("http").get("http://example.com", resolve); + }); + }), + }; + }); + + const logs = await _countLogEntries(runtime); + expect(logs["unidentified-network-access"]).to.gte(1); + }).timeout(TIMEOUT_LONG); + + it("should log outgoing unknown HTTP requests via 'https'", async () => { + const runtime = InvokeRuntimeWithFunctions(FunctionRuntimeBundles.onCreate, () => { + require("firebase-admin").initializeApp(); + return { + function_id: require("firebase-functions") + .firestore.document("test/test") + .onCreate(async () => { + await new Promise((resolve) => { + require("https").get("https://example.com", resolve); + }); }), }; }); const logs = await _countLogEntries(runtime); - // In Node 6 we get >=5 events here, Node 8+ gets >=4 because of changes to - // HTTP libraries, either is fine because we'll whitelist / deny the request - // after the first prompt. + expect(logs["unidentified-network-access"]).to.gte(1); + }).timeout(TIMEOUT_LONG); + + it("should log outgoing Google API requests", async () => { + const runtime = InvokeRuntimeWithFunctions(FunctionRuntimeBundles.onCreate, () => { + require("firebase-admin").initializeApp(); + return { + function_id: require("firebase-functions") + .firestore.document("test/test") + .onCreate(async () => { + await new Promise((resolve) => { + require("https").get("https://storage.googleapis.com", resolve); + }); + }), + }; + }); + + const logs = await _countLogEntries(runtime); - expect(logs["unidentified-network-access"]).to.gte(3); expect(logs["googleapis-network-access"]).to.gte(1); }).timeout(TIMEOUT_LONG); });