Home > Mobile >  CORS error when calling Firebase Callable Functions from Angular?
CORS error when calling Firebase Callable Functions from Angular?

Time:12-12

My code compiles but when I call the function I get a CORS error. Reading this answer, it seems that Angular is sending a fetch request to Firebase Cloud Functions, but Firebase Cloud Functions is rejecting the request, and that there's some way to tell Firebase Cloud Functions to accept requests from Angular (http://localhost:4200/)?

I see storage.rules but I don't see a functions.rules file. In my Firebase Console I see rules for Firestore and Storage but not for Functions.

I added hosting to firebase.json:

  "hosting": {
    "rewrites": [
      {
        "source": "/callMe",
        "function": "callMe"
      },
    ]
  }

I tried httpsCallableFromURL instead of httpsCallable:

const addMessage = httpsCallableFromURL(this.functions, 'http://localhost:5001/triggerable-functions-project/us-central1/addMessage');

Here's the documentation for calling Firebase Cloud Functions from an app.

Here's the error message.

Access to fetch at 'https://us-central1-triggerable-functions-project.cloudfunctions.net/addMessage' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
POST https://us-central1-triggerable-functions-project.cloudfunctions.net/addMessage net::ERR_FAILED
...hundreds of frames...
ERROR Error: Uncaught (in promise): FirebaseError: internal
FirebaseError: internal
    at resolvePromise (zone.js:1214:31)
    at resolvePromise (zone.js:1168:17)
    at zone.js:1281:17
    at _ZoneDelegate.invokeTask (zone.js:409:31)
    at core.mjs:25298:55
    at AsyncStackTaggingZoneSpec.onInvokeTask (core.mjs:25298:36)
    at _ZoneDelegate.invokeTask (zone.js:408:60)
    at Object.onInvokeTask (core.mjs:25606:33)
    at _ZoneDelegate.invokeTask (zone.js:408:60)
    at Zone.runTask (zone.js:178:47)

I looked at AngularFire but gave up. The code seems to be for AngularFire 6 and Firebase 8, the documentation was last updated August 21, 2021. AngularFire 7 uses Firebase 9, which is substantially different.

Here's my code:

app.component.ts The error throws on line 30.

import { Component } from '@angular/core';
import { getFunctions, httpsCallable, httpsCallableFromURL } from "firebase/functions";
import { initializeApp } from 'firebase/app';
import { getFirestore, setDoc, doc } from "firebase/firestore";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  firebaseConfig = {
    apiKey: "...",
    authDomain: "triggerable-functions-project.firebaseapp.com",
    projectId: "triggerable-functions-project",
    storageBucket: "triggerable-functions-project.appspot.com",
    messagingSenderId: "...",
    appId: "..."
  };

  firebaseApp = initializeApp(this.firebaseConfig);
  db = getFirestore(this.firebaseApp);

  messageText: string | null = null;
  functions = getFunctions(this.firebaseApp);

  callMe(messageText: string | null) {
    console.log("Calling Cloud Function: "   messageText);
    const addMessage = httpsCallable(this.functions, 'addMessage');
    // const addMessage = httpsCallableFromURL(this.functions, 'http://localhost:5001/triggerable-functions-project/us-central1/addMessage');

    addMessage({ text: messageText }) // throws error here
      .then((result) => {
        console.log(result.data)
      });
  };
}

app.component.html makes a form you can type a message and then click Submit.

<form (ngSubmit)="callMe(messageText)">
    <input type="text" [(ngModel)]="messageText" name="message" placeholder="message" required>
    <button type="submit" value="Submit">Submit</button>
</form>

index.js is the Cloud Function:

import { initializeApp } from "firebase/app";
import * as functions from "firebase-functions";
import { getFirestore, connectFirestoreEmulator, setDoc, doc } from "firebase/firestore";

const firebaseConfig = {
    apiKey: "...",
    authDomain: "triggerable-functions-project.firebaseapp.com",
    projectId: "triggerable-functions-project",
    storageBucket: "triggerable-functions-project.appspot.com",
    messagingSenderId: "...",
    appId: "..."
};

const firebaseApp = initializeApp(firebaseConfig);
const db = getFirestore(firebaseApp);
connectFirestoreEmulator(db, 'localhost', 8080);

export const addMessage = functions.https.onCall((data, context) => {
  try {
    const original = snap.data().original;
    const uppercase = original.toUpperCase();
    return snap.ref.set({ uppercase }, { merge: true });
  } catch (error) {
    console.error(error);
  }
});

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { environment } from '../environments/environment';

// Angular
import { FormsModule } from '@angular/forms';

// AngularFire
import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
import { provideFirestore, getFirestore, connectFirestoreEmulator } from '@angular/fire/firestore';
import { provideFunctions,getFunctions, connectFunctionsEmulator, httpsCallable, httpsCallableData, httpsCallableFromURL } from '@angular/fire/functions';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    provideFirebaseApp(() => initializeApp(environment.firebaseConfig)),
    provideFirestore(() => {
      const firestore = getFirestore();
      if (!environment.production) {
        connectFirestoreEmulator(firestore, 'localhost', 8080);
      }
      return firestore;
    }),
    provideFunctions(() => {
      const functions = getFunctions();
      if (!environment.production) {
        connectFunctionsEmulator(functions, 'localhost', 5001);
      }
      return functions;
    }),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

CodePudding user response:

That fixed it. I changed httpsCallable to httpsCallableFromURL. Then GitHub Copilot wrote the rest of the line. Copilot is amazing!

 callMe(messageText: string | null) {
    const addMessage = httpsCallableFromURL(this.functions, 'http://localhost:5001/triggerable-functions-project/us-central1/addMessage');
    addMessage({ text: messageText })
      .then((result) => {
        console.log(result.data)
      });
  };

Note that the URL includes the server location. This documentation says that that optimizes performance.

In the function I changed snap to data. I also got rid of the config and initialization stuff, the code runs without it.

index.js

import * as functions from "firebase-functions";

export const addMessage = functions.https.onCall((data, context) => {
  try {
    const original = data.text;
    const uppercase = original.toUpperCase();
    return uppercase;
  } catch (error) {
    console.error(error);
  }
});

Adding hosting to firebase.json was unnecessary, I took that out.

app.component.ts has too much boilerplate. Let's reference environment instead.

app.component.ts

import { Component } from '@angular/core';
import { getFunctions, httpsCallableFromURL } from "firebase/functions";
import { initializeApp } from 'firebase/app';
import { getFirestore } from "firebase/firestore";
import { environment } from '../environments/environment';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  firebaseConfig = environment.firebaseConfig;
  firebaseApp = initializeApp(this.firebaseConfig);
  db = getFirestore(this.firebaseApp);

  messageText: string | null = null;
  functions = getFunctions(this.firebaseApp);

  callMe(messageText: string | null) {
    const addMessage = httpsCallableFromURL(this.functions, 'http://localhost:5001/triggerable-functions-project/us-central1/addMessage');
    addMessage({ text: messageText })
      .then((result) => {
        console.log(result.data)
      });
  };
}
  • Related