How to forward Server sent events in NextJS api - reactjs

I have a Next.js api like this (/api/events/index.js):
import EventSource from 'eventsource';
import { getAccessToken, withApiAuthRequired } from '#auth0/nextjs-auth0';
export default withApiAuthRequired(async function handler(req, res) {
if (req.method === 'GET') {
const { accessToken } = await getAccessToken(req, res);
res.writeHead(200, {
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
'Content-Type': 'text/event-stream',
});
const eventSource = new EventSource(
`http://localhost:8000/v1/event-stream`,
{ headers: { Authorization: `Bearer ${accessToken}` } },
);
eventSource.onmessage = (e) => {
res.write(`data: ${e.data}\n\n`);
// res.end();
};
}
});
Essentially, I'm trying to make a request on a backend API SSE endpoint that requires an authorization bearer token (preferably, the access token should be available only on Next.js API side). After an event is received, it should be written in response to of a new event stream.
In React client code, I simply subscribe to these SSE's like this:
const IndexPage = () => {
useEffect(() => {
const eventSource = new EventSource(`http://localhost:3000/api/events`);
eventSource.onmessage = (e) => {
console.log(e);
};
return () => {
eventSource.close();
};
}, []);
return <div></div>;
}
Where's the problem? Nothing is received on the client side.
When I try to call res.end() on the API side, an event is received on the client side, but the event stream keeps reconnecting... like, every second. Basically, the connection is not kept alive. How can I forward the result of my SSE?

Related

NextJS API Endpoint Error - 'API resolved without sending a response for /api/foo, this may result in stalled requests.'

This is the code I am using
// function calling the api endpoint within a button onClick event handler
async () => {
const response = await fetch('/api/foo', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
const responseData = await response.json()
}
// /api/foo.js
import { ref, uploadString } from 'firebase/storage'
import { storage } from './firebase'
export default async function handler(req, res) {
const data = req.body
const storageRef = ref(storage, data.ID)
const uploadTask = uploadString(storageRef,
JSON.stringify(data.object)
).then(snapshot => {
res.status(200).json(
{ message: 'Saved!', severity: 'success' })
res.end()
}
)
}
When a request is sent to the above API endpoint, the console in vscode shows that a request was sent with this error: API resolved without sending a response for /api/foo, this may result in stalled requests.
What does this mean and how is it fixed?
Thanks in advance!
Edit - added async to handler function, but error still showing

Getting a POST 404 error on back-end server (MERN STACK)

I'm attempting to send a complete form to back-end server (decoupled MERN application in localhost).
The request is reaching the server, but not posting to the database.
This is seen in server console...
POST /contracts 404 1.371 ms - 19
The data to be sent in form is logging in console as an object (as intended).
This is the service function making post request from the frontend to the backend ...
const BASE_URL = `${process.env.REACT_APP_BACKEND_SERVER_URL}/contracts`
export const createContract = async (formData) => {
try {
const res = await fetch(BASE_URL, {
method: 'POST',
headers: {
'content-type': 'application/json',
'Authorization': `Bearer ${tokenService.getToken()}`
},
body: JSON.stringify(formData)
})
return await res.json()
} catch (error) {
console.log(error)
throw error
}
}
this is the backend routes... (already set up as /contracts in server.js)
import { Router } from 'express'
import * as contractsCtrl from '../controllers/contracts.js'
const router = Router()
router.post('/', contractsCtrl.create)
router.get('/all', contractsCtrl.index)
export { router }
create controller function on the backend...
function create(req, res) {
console.log(req.body)
Contract.create(req.body)
.then(contract => res.json(contract))
.catch(err => res.json(err))
}
Any help you have would be appreciated!!

Pass cookies through two Next.js projects API endpoints [duplicate]

Cookies are not sent to the server via getServerSideProps, here is the code in the front-end:
export async function getServerSideProps() {
const res = await axios.get("http://localhost:5000/api/auth", {withCredentials: true});
const data = await res.data;
return { props: { data } }
}
On the server I have a strategy that checks the access JWT token.
export class JwtStrategy extends PassportStrategy(Strategy, "jwt") {
constructor() {
super({
ignoreExpiration: false,
secretOrKey: "secret",
jwtFromRequest: ExtractJwt.fromExtractors([
(request: Request) => {
console.log(request.cookies) // [Object: null prototype] {}
let data = request.cookies['access'];
return data;
}
]),
});
}
async validate(payload: any){
return payload;
}
}
That is, when I send a request via getServerSideProps cookies do not come to the server, although if I send, for example via useEffect, then cookies come normally.
That's because the request inside getServerSideProps doesn't run in the browser - where cookies are automatically sent on every request - but actually gets executed on the server, in a Node.js environment.
This means you need to explicitly pass the cookies to the axios request to send them through.
export async function getServerSideProps({ req }) {
const res = await axios.get("http://localhost:5000/api/auth", {
withCredentials: true,
headers: {
Cookie: req.headers.cookie
}
});
const data = await res.data;
return { props: { data } }
}
The same principle applies to requests made from API routes to external APIs, cookies need to be explicitly passed as well.
export default function handler(req, res) {
const res = await axios.get("http://localhost:5000/api/auth", {
withCredentials: true,
headers: {
Cookie: req.headers.cookie
}
});
const data = await res.data;
res.status(200).json(data)
}

React - Trying to get Tweets from Twitter API - Cors and network error

I'm trying to fetch tweets from the Twitter API but I'm receiving network error and blocked by CORS error. I'm not sure what should I write differently or if I'm even setting up the header correctly to receive the response.
This is the code I have so far:
const Tweets = () => {
const [tweets, setTweets] = useState([])
useEffect(() => {
const token = process.env.REACT_APP_TWITTER_BEARER_TOKEN;
async function fetchTweets() {
const res = await axios.get('https://api.twitter.com/2/tweets/search/recent?query=%23ux', {
headers: {
'Authorization': `bearer ${token}`
}
});
if(res) {
setTweets(res.data);
}
console.log(res.data);
return res;
}
fetchTweets();
}, [])
return (
<div>
</div>
);
}
export default Tweets;

Handling firebase initialization delay and id tokens for custom apollo graphql backend

Currently, when I authenticate a user with firebase, I store their auth token in localStorage to be used later to connect to my backend like so:
const httpLink = new HttpLink({uri: 'http://localhost:9000/graphql'})
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization token to the headers
const token = localStorage.getItem(AUTH_TOKEN) || null
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
})
return forward(operation)
})
const authAfterware = onError(({networkError}) => {
if (networkError.statusCode === 401) AuthService.signout()
})
function createApolloClient() {
return new ApolloClient({
cache: new InMemoryCache(),
link: authMiddleware.concat(authAfterware).concat(httpLink)
})
}
My problem with this is that I have no way to refresh the token once it expires. So I tried to use the following to set the authorization token for apollo:
const httpLink = new HttpLink({uri: 'http://localhost:9000/graphql'})
const asyncAuthLink = setContext(
() => {
return new Promise((success, reject) => {
firebase.auth().currentUser.getToken().then(token => {
success({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
})
}).catch(error => {
reject(error)
})
})
}
)
const authAfterware = onError(({networkError}) => {
if (networkError.statusCode === 401) AuthService.signout()
})
function createApolloClient() {
return new ApolloClient({
cache: new InMemoryCache(),
link: asyncAuthLink.concat(authAfterware.concat(httpLink))
})
}
This works when the user first authenticates, but once the user refreshes the page, firebase is no longer initialized when my graphql queries are sent to my backend, so the token is not sent with it. Is there a way I can asynchronously wait for firebase.auth().currentUser so this will work? Or is there another approach I should take entirely? As far as I know (100% sure) currentUser.getIdToken only makes a network call if the current token is no longer valid. I think this is acceptable as in cases where the token is not valid, the backend can't respond anyway, so I will need to wait for a token refresh to continue.
Some other ideas I thought of:
Continue to use localStorage to store the auth token, refresh it in authAfterware if my backend sends a 401 response back and retry the request.
Set a delay on getting the auth token (not desirable)
Any other ideas?
Thanks!
I know is a bit late but I was stuck on that as well and found a way to solve it. Maybe is not the best one but at least it works.
My approach is to create a Next api endpoint to retrieve the user token using the getUserFromCookies method:
import { NextApiRequest, NextApiResponse } from "next";
import { getUserFromCookies } from "next-firebase-auth";
import initAuth from "../../utils/initAuth";
initAuth();
const handler = async (req: NextApiRequest, res: NextApiResponse<any>) => {
try {
const user = await getUserFromCookies({ req, includeToken: true });
const accessToken = await user.getIdToken();
return res.status(200).json({ success: true, accessToken });
} catch (e) {
console.log(`${e}`);
return res.status(500).json({ error: `Unexpected error. ${e}` });
}
};
export default handler;
And then call this endpoint in the apollo client config like that:
import { ApolloClient, InMemoryCache, ApolloLink, HttpLink, concat } from "#apollo/client";
import { setContext } from "#apollo/client/link/context";
import { relayStylePagination } from "#apollo/client/utilities";
const getUserToken = async () => {
const res = await fetch("http://localhost:3000/api/get-user-token");
const { accessToken } = await res.json();
return accessToken;
};
const asyncAuthLink = setContext(async (request) => {
const token = await getUserToken();
return { ...request, headers: { authorization: token ? `Bearer ${token}` : "" } };
});
const httpLink = new HttpLink({ uri: process.env.NEXT_PUBLIC_API_URL });
const client = new ApolloClient({
name: "web",
version: "1.0",
uri: process.env.NEXT_PUBLIC_API_URL,
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
users: relayStylePagination(),
},
},
},
}),
link: concat(asyncAuthLink, httpLink),
});
export default client;

Resources