Repository: firebase/firebase-tools (ts)

Issue:
TypeError: Cannot convert object to primitive value
<!-- DO NOT DELETE
validate_template=true
template_path=.github/ISSUE_TEMPLATE/bug_report.md
-->

<!--
Thank you for contributing to the Firebase community!

Think you found a bug?
=======================
Yeah, we're definitely not perfect! Please use this template and include a minimal repro when opening the issue. If you know how to solve the issue, please create a Pull Request, and we'd be happy to review it!

Have a feature request?
========================
Great, we love hearing how we can improve our products! However, do not use this template to submit a feature request. Please submit your feature requests to: https://firebase.google.com/support/contact/bugs-features/

Have a usage question?
=======================
We get lots of those and we love helping you, but GitHub is not the best place for them and they will be closed. Please take a look at the guide first: https://firebase.google.com/docs/cli/

If the official documentation doesn't help, try asking through our official support channel: https://firebase.google.com/support/

Additional locations to check for solutions or assistance from the community:
- Stack Overflow: https://stackoverflow.com/
- Firebase Slack Community: https://firebase.community/

*Please avoid duplicate posting across multiple channels!*
-->

### [REQUIRED] Environment info

<!-- Output of `firebase --version` -->

**firebase-tools:**
8.3.0 and later

**Platform:**
Windows

### [REQUIRED] Test case

<!-- Provide a minimal, complete, and verifiable example (http://stackoverflow.com/help/mcve) -->
firebase emulators:start

### [REQUIRED] Steps to reproduce
npm i firebase-tools@8.3.0 -g
firebase emulators:start

### [REQUIRED] Expected behavior

```
D:\workspace\firebasetest>npm i firebase-tools@8.2.0 -g
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
D:\Users\yang\AppData\Roaming\npm\firebase -> D:\Users\yang\AppData\Roaming\npm\node_modules\firebase-tools\lib\bin\firebase.js
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@~2.1.2 (node_modules\firebase-tools\node_modules\chokidar\node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.1.3: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ firebase-tools@8.2.0
added 1 package from 1 contributor, removed 1 package and updated 1 package in 11.003s

D:\workspace\firebasetest>firebase emulators:start
i  emulators: Starting emulators: functions, firestore, pubsub
+  hub: emulator hub started at http://localhost:4400
!  Your requested "node" version "10" doesn't match your global version "12"
+  functions: functions emulator started at http://localhost:5001
i  firestore: firestore emulator logging to firestore-debug.log
+  firestore: firestore emulator started at http://localhost:8080
i  firestore: For testing set FIRESTORE_EMULATOR_HOST=localhost:8080
i  pubsub: pubsub emulator logging to pubsub-debug.log
+  pubsub: pubsub emulator started at http://localhost:8085
i  functions: Watching "D:\workspace\firebasetest\functions" for Cloud Functions...
>  [functions] Start functions
+  functions[updateRestaurantRate]: firestore function initialized.
+  functions[pubsubFn]: pubsub function initialized.
+  functions[cronRestaurantRanking]: pubsub function initialized.
+  functions[scheduledFunctionCrontab]: pubsub function initialized.
+  emulators: All emulators started, it is now safe to connect.
```

<!-- What is the expected behavior? -->

### [REQUIRED] Actual behavior
```
D:\workspace\firebasetest>npm i firebase-tools@8.3.0 -g
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
D:\Users\yang\AppData\Roaming\npm\firebase -> D:\Users\yang\AppData\Roaming\npm\node_modules\firebase-tools\lib\bin\firebase.js
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@~2.1.2 (node_modules\firebase-tools\node_modules\chokidar\node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.1.3: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ firebase-tools@8.3.0
added 1 package from 1 contributor, removed 1 package and updated 1 package in 9.576s

D:\workspace\firebasetest>firebase emulators:start
i  emulators: Starting emulators: functions, firestore, pubsub
!  Your requested "node" version "10" doesn't match your global version "12"
i  firestore: Firestore Emulator logging to firestore-debug.log
!  hosting: The hosting emulator is configured but there is no hosting configuration. Have you run firebase init hosting?
i  pubsub: Pub/Sub Emulator logging to pubsub-debug.log
i  functions: Watching "D:\workspace\firebasetest\functions" for Cloud Functions...
!  TypeError: Cannot convert object to primitive value
    at Proxy.<anonymous> (D:\Users\yang\AppData\Roaming\npm\node_modules\firebase-tools\lib\emulator\functionsEmulatorRuntime.js:311:83)
    at Object.<anonymous> (D:\workspace\firebasetest\functions\lib\index.js:13:7)
    at Module._compile (internal/modules/cjs/loader.js:1158:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
    at Module.load (internal/modules/cjs/loader.js:1002:32)
    at Function.Module._load (internal/modules/cjs/loader.js:901:14)
    at Module.require (internal/modules/cjs/loader.js:1044:19)
    at require (internal/modules/cjs/helpers.js:77:18)
    at D:\Users\yang\AppData\Roaming\npm\node_modules\firebase-tools\lib\emulator\functionsEmulatorRuntime.js:660:33
    at Generator.next (<anonymous>)
!  We were unable to load your functions code. (see above)
   - You may be able to run "npm run build" in your functions directory to resolve this.
```

<!-- Run the command with --debug flag, and include the logs below. -->


I tired "npm run build" in my functions, it does not work.
```
D:\workspace\firebasetest>cd functions

D:\workspace\firebasetest\functions>npm run build

> functions@ build D:\workspace\firebasetest\functions
> tsc
```

Pull request description:
Remove deep functions.config() proxying
<!--

Thank you for contributing to the Firebase community! Please fill out the form below.

Run the linter and test suite
==============================
Run `npm test` to make sure your changes compile properly and the tests all pass on your local machine. We've hooked up this repo with continuous integration to double check those things for you.

-->

### Description

Fixes #2273

Since #2041 we now correctly support `.runtimeconfig.json` inside the Functions emulator.  Our proxying of config() was only for warnings but it can mess things up (see fixed bug) because a `Proxy` is not the same as a plain object in many ways.

I remove almost all the functionality but leave the warning

### Scenarios Tested

Here is my config:
```json
{
  "foo": {
    "hello": "world"
  }
}
```

I wrote this function and called it twice in a row:
```js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.checkConfig = functions.https.onRequest(async (request, response) => {
  const fooVal = functions.config().foo;
  console.log("Foo config", fooVal);

  const barVal = functions.config().bar;
  console.log("Bar config", barVal);

  response.send("Hello from Firebase!");
});

```

Here are the logs:
```
i  functions: Beginning execution of "checkConfig"
>  Foo config { hello: 'world' }
>  Bar config undefined
⚠  It looks like you're trying to access functions.config().bar but there is no value there. You can learn more about setting up config here: https://firebase.google.com/docs/functions/local-emulator
i  functions: Finished "checkConfig" in ~1s
i  functions: Beginning execution of "checkConfig"
>  Foo config { hello: 'world' }
>  Bar config undefined
i  functions: Finished "checkConfig" in ~1s
```

### Sample Commands

N/A

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b6c957670..19194cf0d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,3 +3,4 @@
 - Skip app selection in `firebase apps:sdkconfig` if there is a single matching app.
 - Fixes incorrect warning about emulator configuration (#2290).
 - Improves error messages for `firebase init` with new projects (#2266).
+- Remove deep proxying of `functions.config()` inside the Functions emulator.
diff --git a/src/emulator/emulatorLogger.ts b/src/emulator/emulatorLogger.ts
index 4d08d3d4c..ddfde6308 100644
--- a/src/emulator/emulatorLogger.ts
+++ b/src/emulator/emulatorLogger.ts
@@ -175,8 +175,8 @@ export class EmulatorLogger {
         break;
       case "functions-config-missing-value":
         this.log(
-          "WARN",
-          `Non-existent functions.config() value requested!\n   - Path: "${systemLog.data.valuePath}"\n   - Learn more at https://firebase.google.com/docs/functions/local-emulator`
+          "WARN_ONCE",
+          `It looks like you're trying to access functions.config().${systemLog.data.key} but there is no value there. You can learn more about setting up config here: https://firebase.google.com/docs/functions/local-emulator`
         );
         break;
       case "non-default-admin-app-used":
diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts
index 393f1d9fe..40a2fdd94 100644
--- a/src/emulator/functionsEmulatorRuntime.ts
+++ b/src/emulator/functionsEmulatorRuntime.ts
@@ -664,33 +664,13 @@ async function initializeFunctionsConfigHelper(frb: FunctionsRuntimeBundle): Pro
   const originalConfig = ff.config();
   const proxiedConfig = new Proxied(originalConfig)
     .any((parentConfig, parentKey) => {
-      logDebug("config() parent accessed!", {
-        parentKey,
-        parentConfig,
-      });
+      if (!parentConfig[parentKey]) {
+        new EmulatorLog("SYSTEM", "functions-config-missing-value", "", {
+          key: parentKey,
+        }).log();
+      }
 
-      return new Proxied(parentConfig[parentKey] || ({} as { [key: string]: any }))
-        .any((childConfig, childKey) => {
-          const value = childConfig[childKey];
-          if (value) {
-            return value;
-          } else {
-            const valuePath = [parentKey, childKey].join(".");
-
-            // Calling console.log() or util.inspect() on a config value can cause spurious logging
-            // if we don't ignore certain known-bad paths.
-            const ignore =
-              valuePath.endsWith(".inspect") ||
-              valuePath.endsWith(".toJSON") ||
-              valuePath.includes("Symbol(") ||
-              valuePath.includes("Symbol.iterator");
-            if (!ignore) {
-              new EmulatorLog("SYSTEM", "functions-config-missing-value", "", { valuePath }).log();
-            }
-            return undefined;
-          }
-        })
-        .finalize();
+      return parentConfig[parentKey];
     })
     .finalize();
 


Test patch:
diff --git a/src/test/emulators/functionsEmulatorRuntime.spec.ts b/src/test/emulators/functionsEmulatorRuntime.spec.ts
index e0b03202b..61d6b9cc3 100644
--- a/src/test/emulators/functionsEmulatorRuntime.spec.ts
+++ b/src/test/emulators/functionsEmulatorRuntime.spec.ts
@@ -434,38 +434,42 @@ describe("FunctionsEmulator-Runtime", () => {
         await worker.runtime.exit;
       }).timeout(TIMEOUT_MED);
     });
+  });
 
-    describe("_InitializeFunctionsConfigHelper()", () => {
-      it("should tell the user if they've accessed a non-existent function field", async () => {
-        const worker = InvokeRuntimeWithFunctions(
-          FunctionRuntimeBundles.onCreate,
-          () => {
-            require("firebase-admin").initializeApp();
-            return {
-              function_id: require("firebase-functions")
-                .firestore.document("test/test")
-                .onCreate(async () => {
-                  /* tslint:disable:no-console */
-                  console.log(require("firebase-functions").config().doesnt.exist);
-                  console.log(require("firebase-functions").config().does.exist);
-                  console.log(require("firebase-functions").config().also_doesnt.exist);
-                }),
-            };
-          },
-          {
-            nodeBinary: process.execPath,
-            env: {
-              CLOUD_RUNTIME_CONFIG: JSON.stringify({
-                does: { exist: "already exists" },
+  describe("_InitializeFunctionsConfigHelper()", () => {
+    it("should tell the user if they've accessed a non-existent function field", async () => {
+      const worker = InvokeRuntimeWithFunctions(
+        FunctionRuntimeBundles.onCreate,
+        () => {
+          require("firebase-admin").initializeApp();
+          return {
+            function_id: require("firebase-functions")
+              .firestore.document("test/test")
+              .onCreate(async () => {
+                /* tslint:disable:no-console */
+
+                // Exists
+                console.log(require("firebase-functions").config().real);
+
+                // Does not exist
+                console.log(require("firebase-functions").config().foo);
+                console.log(require("firebase-functions").config().bar);
               }),
-            },
-          }
-        );
+          };
+        },
+        {
+          nodeBinary: process.execPath,
+          env: {
+            CLOUD_RUNTIME_CONFIG: JSON.stringify({
+              real: { exist: "already exists" },
+            }),
+          },
+        }
+      );
 
-        const logs = await _countLogEntries(worker);
-        expect(logs["functions-config-missing-value"]).to.eq(2);
-      }).timeout(TIMEOUT_MED);
-    });
+      const logs = await _countLogEntries(worker);
+      expect(logs["functions-config-missing-value"]).to.eq(2);
+    }).timeout(TIMEOUT_MED);
   });
 
   describe("Runtime", () => {