I am trying to fetch a csv file via URL (nextjs api route) and while it works locally, the deployed version on vercel seems to block it. To be precise I am using d3 ( d3.csv() ) in the api route, but after deploying on vercel the d3 fetch fails:
api/xx (using d3:)
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
...
const key = push(setRef).key;
if (key) {
const storageURL: string =
'https://firebasestorage.googleapis...'; // --> public
await csv(storageURL) // --> this fails
.then(async data => {
...
called like this:
const refreshData = () => {
replace(asPath);
};
const addGuess = async () => {
await fetch(`${server}/api/xx?id=${currentUser.uid}`, {
method: 'POST',
});
refreshData(); // --> probably not relevant
};
Thanks in advance!
Related
I have an app made with React, Node.js and Socket.io
I deployed Node backend to heroku , frontend to Netlify
I know that CORS errors is related to server but no matter what I add, it just cant go through that error in the picture below.
I also added proxy script to React's package.json as "proxy": "https://googledocs-clone-sbayrak.herokuapp.com/"
And here is my server.js file;
const mongoose = require('mongoose');
const Document = require('./Document');
const dotenv = require('dotenv');
const path = require('path');
const express = require('express');
const http = require('http');
const socketio = require('socket.io');
dotenv.config();
const app = express();
app.use(cors());
const server = http.createServer(app);
const io = socketio(server, {
cors: {
origin: 'https://googledocs-clone-sbayrak.netlify.app/',
methods: ['GET', 'POST'],
},
});
app.get('/', (req, res) => {
res.status(200).send('hello!!');
});
const connectDB = async () => {
try {
const connect = await mongoose.connect(process.env.MONGODB_URI, {
useUnifiedTopology: true,
useNewUrlParser: true,
});
console.log('MongoDB Connected...');
} catch (error) {
console.error(`Error : ${error.message}`);
process.exit(1);
}
};
connectDB();
let defaultValue = '';
const findOrCreateDocument = async (id) => {
if (id === null) return;
const document = await Document.findById({ _id: id });
if (document) return document;
const result = await Document.create({ _id: id, data: defaultValue });
return result;
};
io.on('connection', (socket) => {
socket.on('get-document', async (documentId) => {
const document = await findOrCreateDocument(documentId);
socket.join(documentId);
socket.emit('load-document', document.data);
socket.on('send-changes', (delta) => {
socket.broadcast.to(documentId).emit('receive-changes', delta);
});
socket.on('save-document', async (data) => {
await Document.findByIdAndUpdate(documentId, { data });
});
});
console.log('connected');
});
server.listen(process.env.PORT || 5000, () =>
console.log(`Server has started.`)
);
and this is where I make request from frontend;
import Quill from 'quill';
import 'quill/dist/quill.snow.css';
import { useParams } from 'react-router-dom';
import { io } from 'socket.io-client';
const SAVE_INTERVAL_MS = 2000;
const TextEditor = () => {
const [socket, setSocket] = useState();
const [quill, setQuill] = useState();
const { id: documentId } = useParams();
useEffect(() => {
const s = io('https://googledocs-clone-sbayrak.herokuapp.com/');
setSocket(s);
return () => {
s.disconnect();
};
}, []);
/* below other functions */
/* below other functions */
/* below other functions */
}
TL;DR
https://googledocs-clone-sbayrak.netlify.app/ is not an origin. Drop that trailing slash.
More details about the problem
No trailing slash allowed in the value of the Origin header
According to the CORS protocol (specified in the Fetch standard), browsers never set the Origin request header to a value with a trailing slash. Therefore, if a page at https://googledocs-clone-sbayrak.netlify.app/whatever issues a cross-origin request, that request's Origin header will contain
https://googledocs-clone-sbayrak.netlify.app
without any trailing slash.
Byte-by-byte comparison on the server side
You're using Socket.IO, which relies on the Node.js cors package. That package won't set any Access-Control-Allow-Origin in the response if the request's origin doesn't exactly match your CORS configuration's origin value (https://googledocs-clone-sbayrak.netlify.app/).
Putting it all together
Obviously,
'https://googledocs-clone-sbayrak.netlify.app' ===
'https://googledocs-clone-sbayrak.netlify.app/'
evaluates to false, which causes the cors package not to set any Access-Control-Allow-Origin header in the response, which causes the CORS check to fail in your browser, hence the CORS error you observed.
Example from the Fetch Standard
Section 3.2.5 of the Fetch Standard even provides an enlightening example of this mistake,
Access-Control-Allow-Origin: https://rabbit.invalid/
and explains why it causes the CORS check to fail:
A serialized origin has no trailing slash.
Looks like you haven't imported the cors package. Is it imported anywhere else?
var cors = require('cors') // is missing
I am fetching all posts from the backend from the API call http://localhost:3000/api/jobs and it is working perfectly by using getstaticProps(). Now I want to get a particular post based on slug and my API call is http://localhost:3000/api/jobs/:slug and it perfectly working API call But whenever I used the code shown below for dynamic routes it shows me a server error on the frontend and unable to fetch particular post data.
[slug].js
export const getStaticPaths = async () => {
const res = await fetch(`${API}/jobs`);
const post = await res.json();
const paths = post.map(job => {
return {
params: { slug: job.slug }
}
})
return {
paths,
fallback:true
}
}
export const getStaticProps = async (ctx) => {
const slug = ctx.params.slug;
const [job, photo] = await Promise.all([
fetch(`${API}/jobs/${slug}`).then(r => r.json()),
`${API}/jobs/photo/${slug}`
]);
if (!job) {
return {
notFound:true
}
}
return {
props: {
job,
photo
},
revalidate:60
}
}
Also whenever I used another API call like http://localhost:3000/api/jobs-edit which has the same function as that of http://localhost:3000/api/jobs inside getStaticPaths() then it performs well and gives us single post data.
What can be the problem?
I have setup Next.js (11) app with working connection to the firebase version 8.7.
I got an issue on getting donwload URL for image:
If I'd create a function (example below) to fetch the uploaded image - assume it is there & I know its name and location. It will work only once (dev env)
After any route change or page refresh (not on code change assuming I do not change the route or refresh the page), the app crashes with terminal error:
ReferenceError: XMLHttpRequest is not defined
I get this error when I call both in getStaticProps or in the component itself on the client side
function example:
import firebase from "firebase/app";
import "firebase/storage";
export const getImgUrl = async () => {
const storage = firebase.storage();
const pathReference = storage.ref("user_uploads/my_image.jpg");
pathReference
.getDownloadURL()
.then((url) => {
console.log("my url", url);
})
.catch((error) => {
console.error("error", error);
});
};
I have a bypass solution:
Upgrade to the firebase sdk version 9 (modular one).
Create db & storage:
const initFirebase = () => {
const db = getFirestore(firebaseApp)
const storage = getStorage(firebaseApp)
console.log('Firebase was successfully initialized')
return [db, storage]
}
// Init firebase:
export const [db, storage] = initFirebase()
use it:
const getData = async () => {
console.log('getData runs')
try {
const url = await getDownloadURL(ref(storage, 'landing/land.jpg'))
console.log('getData url:', url)
return url
} catch (error) {
// Handle any errors
}
}
and call getData in getServerSideProps or getStaticProps in any component
As per documentation we can add appcheck as below,
exports.yourCallableFunction = functions.https.onCall((data, context) => {
// context.app will be undefined if the request doesn't include a valid
// App Check token.
if (context.app == undefined) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called from an App Check verified app.')
}
});
My question right now is how do I need to add app-check for below scenario?
exports.date = functions.https.onRequest((req, res) => {
});
In the client, get an appCheck token from Firebase. Send it in a header to your function. Get the token from the req object's headers. Verify the the token with firebase-admin. I'll include the documentation for the client below, then the gist of how I implemented it client side with Apollo-client graphql. Then I'll include the documentation for the backend, then the gist of how I implemented the backend, again with Apollo.
client (from the documentation):
const { initializeAppCheck, getToken } = require('firebase/app-check');
const appCheck = initializeAppCheck(
app,
{ provider: provider } // ReCaptchaV3Provider or CustomProvider
);
const callApiWithAppCheckExample = async () => {
let appCheckTokenResponse;
try {
appCheckTokenResponse = await getToken(appCheck, /* forceRefresh= */ false);
} catch (err) {
// Handle any errors if the token was not retrieved.
return;
}
// Include the App Check token with requests to your server.
const apiResponse = await fetch('https://yourbackend.example.com/yourApiEndpoint', {
headers: {
'X-Firebase-AppCheck': appCheckTokenResponse.token,
}
});
// Handle response from your backend.
};
client (gist from my implementation)
import { setContext } from "#apollo/client/link/context";
import { app } from '../firebase/setup';
import { initializeAppCheck, ReCaptchaV3Provider, getToken } from "firebase/app-check"
let appCheck
let appCheckTokenResponse
const getAppCheckToken = async () => {
const appCheckTokenResponsePromise = await getToken(appCheck, /* forceRefresh= */ false)
appCheckTokenResponse = appCheckTokenResponsePromise
}
const authLink = setContext(async (_, { headers }) => {
if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_ENV === 'production') {
appCheck = initializeAppCheck(app, {
provider: new ReCaptchaV3Provider('my_public_key_from_recaptcha_V3'),
isTokenAutoRefreshEnabled: true
})
await getAppCheckToken()
}
return {
headers: {
...headers,
'X-Firebase-AppCheck': appCheckTokenResponse?.token,
},
}
})
backend / server (from the documentation)
const express = require('express');
const app = express();
const firebaseAdmin = require('firebase-admin');
const firebaseApp = firebaseAdmin.initializeApp();
const appCheckVerification = async (req, res, next) => {
const appCheckToken = req.header('X-Firebase-AppCheck');
if (!appCheckToken) {
res.status(401);
return next('Unauthorized');
}
try {
const appCheckClaims = await firebaseAdmin.appCheck().verifyToken(appCheckToken);
// If verifyToken() succeeds, continue with the next middleware
// function in the stack.
return next();
} catch (err) {
res.status(401);
return next('Unauthorized');
}
}
app.get('/yourApiEndpoint', [appCheckVerification], (req, res) => {
// Handle request.
});
backend / server (gist from my implementation)
import { https } from 'firebase-functions'
import gqlServer from './graphql/server'
const functions = require('firebase-functions')
const env = process.env.ENV || functions.config().config.env
const server = gqlServer()
const api = https.onRequest((req, res) => {
server(req, res)
})
export { api }
. . .
import * as admin from 'firebase-admin';
const functions = require('firebase-functions');
const env = process.env.ENV || functions.config().config.env
admin.initializeApp()
appCheckVerification = async (req: any, res: any) => {
const appCheckToken = req.header('X-Firebase-AppCheck')
if (!appCheckToken) {
return false
}
try {
const appCheckClaims = await admin.appCheck().verifyToken(appCheckToken);
return true
} catch (error) {
console.error(error)
return false
}
}
. . .
const apolloServer = new ApolloServer({
introspection: isDevelopment,
typeDefs: schema,
resolvers,
context: async ({ req, res }) => {
if (!isDevelopment && !isTest) {
const appCheckVerification = await appCheckVerification(req, res)
if (!appCheckVerification) throw Error('Something went wrong with verification')
}
return { req, res, }
}
If you enforce app check in Cloud Functions it will only allow calls from apps that are registered in your project.
I'm not sure if that is sufficient for your use-case though, as I doubt most apps where you can provide a web hook will have implemented app attestation - which is how App Check recognizes valid requests.
You can generate an app check token in the client and verify the token in the server using firebase admin SDK. Here is the firebase documentation for the same
Firebase enable App check enforcement documentation teaches you that to validate the caller from your function you just need to check the context.app then gives you an example like this
exports.EXAMPLE = functions.https.onCall((data, context) => {});
https://firebase.google.com/docs/app-check/cloud-functions?authuser=0
But when you are deploying your function in the google cloud dashboard, you select HTTP FUNCTION -> nodejs 14 -> then you are directed to code like this
/**
* Responds to any HTTP request.
*
* #param {!express:Request} req HTTP request context.
* #param {!express:Response} res HTTP response context.
*/
exports.helloWorld = (req, res) => {
let message = req.query.message || req.body.message || 'Hello World!';
res.status(200).send(message);
};
My question when I saw this was: "How am i going to get a context if I only have request/response"
The answer is simple. YOU MUST SWITCH THE CONSTRUCTORS
You must re-write your function in a way that instead of dealing with req/res like any express function you are dealing with context/data
http functions are different of callable functions (the ones that deals with context/data)
IT IS SIMILAR BUT NOT EXACTLY EQUAL AND SOME MODIFICATIONS WILL BE NECESSARY.
mainly if your function deals with async stuff and have a delayed response you are going to need to rewrite many stuff
check this tutorial
https://firebase.google.com/docs/functions/callable
I have an graphql server connected to my Firebase RTD and deployed on heroku.
When I run my server in heroku the request to reservations resolver takes forever and eventually the Playground yells Unexpected token < in JSON at position 0
I suspect this is a timeout from Firebase, but how would I go about debugging this? (Heroku logs nothing about the error).
You can try the server for yourself: https://filex-database.herokuapp.com
The specific query that's causing me trouble is:
query {
reservations {
code
name
}
}
const db = require("../datasources/db");
const masterlist = require("../datasources/masterlist.js");
const getById = (key: string, id: string) =>
db[key].filter((item) => item.id === id)[0];
const firebaseQuery = (context: { firebaseClient }, endpoint: string) => {
const finalEndpoint =
endpoint.charAt(0) === "/" ? endpoint : "/".concat(endpoint);
const baseUrl = "/workshops";
return context.firebaseClient
.database()
.ref(`${baseUrl}${finalEndpoint}`)
.once("value")
.then((snapshot) => snapshot.val());
};
const Query = {
workshops: () => db.workshops,
workshop: (_, args) => getById("workshops", args.id),
options: () => db.options,
option: (_, args) => getById("options", args.id),
// this resolver is causing me trouble
reservations: async (_, __, context) => {
const data = await firebaseQuery(context, "/applicants");
return Object.values(data);
},
reservation: async (_, args, context) => {
const data = await firebaseQuery(context, `/applicants/${args.id}`);
return data;
},
};
module.exports = { Query };
EDIT: I made another simple server using the same technology and only delivering that resolver and it also timesout (everything works fine locally though)
http://apollo-testing-gonzo.herokuapp.com