Search code examples
reactjsfirebasegoogle-cloud-firestorecorsvite

CORS error uploading images to firebase using Vite, react


The problem is using the uploadBytes function, when I comment it out (as well as the imageRef as seen below) everything runs fine (except image upload obviously), but with it I get all these errors like this:

Access to XMLHttpRequest at [ firebase storage api url ] from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

// New info here:

I Think i've mostly wrangled this thing down, what i need to do is update the cors policy of the firestore bucket. it seems you use a json like cors.json below, and then use this command: gsutil cors set cors.json gs://<your app name, at top of firestore >.appspot.com

Unfortunately for me, when i run this, it cannot find the bucket, i'll let y'all know if i find something,

// end new info

The Vite config and firebase files are included, then the create.jsx file, where the error occurs is below.

vite.config.js


import { resolve } from 'path';
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'


const root = resolve(__dirname, 'src');
const outDir = resolve(__dirname, 'dist');

// https://vitejs.dev/config/
export default defineConfig({
  root,
  plugins: [react()],
  build: {
    outDir,
    emptyOutDir: true,
    rollupOptions: {
      input: {
        main: resolve(root, "index.html"),
        login: resolve(root, 'auth', "login.html"),
      }
    }
  }
})

firebase.js

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: import.meta.env.VITE_PUBLIC_API_KEY,
  authDomain: import.meta.env.VITE_PUBLIC_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_PUBLIC_PROJECT_ID,
  storageBucket: import.meta.env.VITE_PUBLIC_STORAGE_BUCKET,
  messagingSenderId: import.meta.env.VITE_PUBLIC_MESSAGING_SENDER_ID,
  appId: import.meta.env.VITE_PUBLIC_APP_ID,
  measurementId: import.meta.env.VITE_PUBLIC_MEASUREMENT_ID
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
//const analytics = getAnalytics(app);

export const auth = getAuth();
export const db = getFirestore(app);


// for image:
import { getStorage, ref, uploadBytes } from "firebase/storage"; // new
export const storage = getStorage(app)

create.jsx

import { auth, db } from "../utils/firebase"
import { useAuthState } from "react-firebase-hooks/auth"
import { useEffect, useState } from "react"
import { AiOutlineCloseCircle } from "react-icons/Ai";
import { addDoc, collection, serverTimestamp } from "firebase/firestore";


// I need to upload the photo
// create reference
// create post, including reference
import { getStorage, ref, uploadBytes } from "firebase/storage"; // new
import { nanoid } from 'nanoid'
import { storage } from '../utils/firebase'


export default function Create(props){
    const [post, setPost] = useState({imageSrc: undefined, title: "", description: ""})//maybe user id too?, probably fine without
    const [preview, setPreview] = useState();
    const [user,loading] = useAuthState(auth);


    useEffect(() => {
        if (!post.imageSrc) {
            setPost({...post, imageSrc: undefined})
            return
        }
        const objectUrl = URL.createObjectURL(post.imageSrc)
        setPreview(objectUrl)
        return () => URL.revokeObjectURL(objectUrl)
    }, [post.imageSrc])


    const handleFile = async (e) => {
        if (!e.target.files || e.target.files.length === 0) {
            setPost({...post, imageSrc: undefined})
            return
        }
        setPost({...post, imageSrc: e.target.files[0]})
    }
    

    const submitPost = async (e) => {
        e.preventDefault();
        const collectionRef = collection(db, "posts");


        // uploading image first:
        const photoID = nanoid()
        // const storage = getStorage();
        const storageRef = ref(storage, `images/${photoID}`);
        
        
        /////////////// PROBLEM IS HERE://///////////////////////////////////////////////////////


        uploadBytes(storageRef, post.imageSrc).then(() => {
            console.log("successful upload!")
        })// this is the problem, idk if the imageSrc is the prob....

        ////////////// END PROBLEM HERE /////////////////////////////////////////////////////////


        // creating image reference:


        
        // normal stuff:
        await addDoc(collectionRef, {
            title: post.title,
            description: post.description,
            //imageRef: storageRef, // just connect the image reference here, but trough the firebase bucket// why no imageref...
            timestamp: serverTimestamp(),
            user: user.uid,
            avatar: user.photoURL,
            username: user.displayName,
        });
        

        props.handleCreating() // closes this when done
    }

    return (
        <div className="mainCreate">
            <AiOutlineCloseCircle onClick={() => props.handleCreating()} className="closeIcon"/>
            <form onSubmit={submitPost}>
                <div className="imageBox">
                    {post.imageSrc ? <img className="imagePreview" src={preview}/> : <h2>No Image</h2>}
                    {!post.imageSrc && <input type="file" onChange={handleFile}/>}
                </div>
                <textarea className="title" placeholder="Title" onChange={(e) => setPost({...post, title: e.target.value})} value={post.title} />
                <p className={`lengthIndicator${post.title.length > 100 ? "Red" : ""}`}>{post.title.length}/100</p>
                <textarea className="description" placeholder="Description" onChange={(e) => setPost({...post, description: e.target.value})} value={post.description} />
                <p className={`lengthIndicator${post.description.length > 300 ? "Red" : ""}`}>{post.description.length}/300</p>
                <button className="createButton" type="submit">Create</button>
            </form>
        </div>
    )
}

the problem should be somewhere in the above, i've tried adding a proxy middleware, but i think i didn't figure it out, not sure, let me know if you need anythings else


Solution

  • not even sure exactly what the problem was, but all the cors and preflights stuff was a wild goose chase. Firebase handles all of this for us, my problems were in not having things represented where i thought they were. I also moved upload up earlier, to where image is selected.

    firebase.js:

    // Import the functions you need from the SDKs you need
    import { initializeApp } from "firebase/app";
    import { getAnalytics } from "firebase/analytics";
    import { getAuth } from "firebase/auth";
    import { getFirestore } from "firebase/firestore";
    // for image:
    import { getStorage} from "firebase/storage"; //, ref, uploadBytes } from "firebase/storage"; // new
    
    
    // TODO: Add SDKs for Firebase products that you want to use
    // https://firebase.google.com/docs/web/setup#available-libraries
    
    // Your web app's Firebase configuration
    // For Firebase JS SDK v7.20.0 and later, measurementId is optional
    const firebaseConfig = {
      apiKey: import.meta.env.VITE_PUBLIC_API_KEY,
      authDomain: import.meta.env.VITE_PUBLIC_AUTH_DOMAIN,
      projectId: import.meta.env.VITE_PUBLIC_PROJECT_ID,
      storageBucket: import.meta.env.VITE_PUBLIC_STORAGE_BUCKET,
      messagingSenderId: import.meta.env.VITE_PUBLIC_MESSAGING_SENDER_ID,
      appId: import.meta.env.VITE_PUBLIC_APP_ID,
      measurementId: import.meta.env.VITE_PUBLIC_MEASUREMENT_ID
    };
    
    // Initialize Firebase
    const app = initializeApp(firebaseConfig);
    // other way too, export..
    //const analytics = getAnalytics(app);
    
    
    const auth = getAuth();
    // export const db = getFirestore(app);
    const db = getFirestore(app); // firebase.firestore(); if do the other way
    const storage = getStorage(app);// firebase.storage();
    
    export { auth, db, storage };
    

    create.jsx:

    import { auth, db } from "../utils/firebase"
    import { useAuthState } from "react-firebase-hooks/auth"
    import { useEffect, useState } from "react"
    import { AiOutlineCloseCircle } from "react-icons/Ai";
    import { addDoc, collection, serverTimestamp } from "firebase/firestore";
    
    
    // I need to upload the photo
    // create reference
    // create post, including reference
    import { getStorage, ref, uploadBytes } from "firebase/storage"; // new
    import { nanoid } from 'nanoid'
    import { storage } from '../utils/firebase'
    
    
    export default function Create(props){
        const [post, setPost] = useState({imageSrc: undefined, title: "", description: ""})//maybe user id too?, probably fine without
        const [preview, setPreview] = useState();
        const [user,loading] = useAuthState(auth);
    
    
        useEffect(() => {
            if (!post.imageSrc) {
                setPost({...post, imageSrc: undefined})
                return
            }
            const objectUrl = URL.createObjectURL(post.imageSrc)
            setPreview(objectUrl)
            return () => URL.revokeObjectURL(objectUrl)
        }, [post.imageSrc])
    
    
        const handleFile = async (e) => {
            if (!e.target.files || e.target.files.length === 0) {
                setPost({...post, imageSrc: undefined})
                return
            }
    
            ///////// new
            const storageRef = ref(storage);
            const fileRef = ref(storageRef, `images/${nanoid()}`)
            uploadBytes(fileRef, e.target.files[0]).then(() => {
                console.log("successful upload!")
            })
            // setPost({...post, imageSrc: fileRef})
            setPost({...post, imageSrc: e.target.files[0], imageRef: fileRef})
            //console.log(post.imageSrc)
        }
        
    
        const submitPost = async (e) => {
            if (!post.imageSrc) {
                return;
            }
            e.preventDefault();
            const collectionRef = collection(db, "posts");
    
            await addDoc(collectionRef, {
                title: post.title,
                description: post.description,
                imageRef: post.imageRef.fullPath, // this one is newer
                timestamp: serverTimestamp(),
                user: user.uid,
                avatar: user.photoURL,
                username: user.displayName,
            });
    
            props.handleCreating() // closes this when done
        }
    
        return (
            <div className="mainCreate">
                <AiOutlineCloseCircle onClick={() => props.handleCreating()} className="closeIcon"/>
                <form onSubmit={submitPost}>
                    <div className="imageBox">
                        {post.imageSrc ? <img className="imagePreview" src={preview}/> : <h2>No Image</h2>}
                        {!post.imageSrc && <input type="file" onChange={handleFile}/>}
                    </div>
                    <textarea className="title" placeholder="Title" onChange={(e) => setPost({...post, title: e.target.value})} value={post.title} />
                    <p className={`lengthIndicator${post.title.length > 100 ? "Red" : ""}`}>{post.title.length}/100</p>
                    <textarea className="description" placeholder="Description" onChange={(e) => setPost({...post, description: e.target.value})} value={post.description} />
                    <p className={`lengthIndicator${post.description.length > 300 ? "Red" : ""}`}>{post.description.length}/300</p>
                    <button className="createButton" type="submit">Create</button>
                </form>
            </div>
        )
    }
    

    hopefully this helps someone :)