Netlify Apollo NextJS SSR Client network socket disconnected before secure TLS connection was established - ssl

I have an application I've built using NextJS that is hosted on Netlify. The API is hosted on Heroku (it's a NestJS project running GraphQL)
In local development mode, I have no problem with any of my SSR pages. However, in production, I continually get 500 errors that produce the following logs in the Netlify functions panel:
ERROR ApolloError: request to https://api.paladindeck.com/graphql failed, reason: Client network socket disconnected before secure TLS connection was established
at new ApolloError (/var/task/node_modules/#apollo/client/errors/errors.cjs:34:28)
at /var/task/node_modules/#apollo/client/core/core.cjs:1598:19
at both (/var/task/node_modules/#apollo/client/utilities/utilities.cjs:986:53)
at /var/task/node_modules/#apollo/client/utilities/utilities.cjs:979:72
at new Promise (<anonymous>)
at Object.then (/var/task/node_modules/#apollo/client/utilities/utilities.cjs:979:24)
at Object.error (/var/task/node_modules/#apollo/client/utilities/utilities.cjs:987:49)
at notifySubscription (/var/task/node_modules/zen-observable/lib/Observable.js:140:18)
at onNotify (/var/task/node_modules/zen-observable/lib/Observable.js:179:3)
at SubscriptionObserver.error (/var/task/node_modules/zen-observable/lib/Observable.js:240:7) {
graphQLErrors: [],
clientErrors: [],
networkError: FetchError: request to https://api.paladindeck.com/graphql failed, reason: Client network socket disconnected before secure TLS connection was established
at ClientRequest.<anonymous> (/var/task/node_modules/next/dist/compiled/node-fetch/index.js:1:64142)
at ClientRequest.emit (events.js:412:35)
at ClientRequest.emit (domain.js:475:12)
at TLSSocket.socketErrorListener (_http_client.js:475:9)
at TLSSocket.emit (events.js:400:28)
at TLSSocket.emit (domain.js:475:12)
at emitErrorNT (internal/streams/destroy.js:106:8)
at emitErrorCloseNT (internal/streams/destroy.js:74:3)
at processTicksAndRejections (internal/process/task_queues.js:82:21) {
type: 'system',
errno: 'ECONNRESET',
code: 'ECONNRESET'
},
extraInfo: undefined
}
I have attached Sentry to the application and it's capturing some similar information:
http
POST https://api.paladindeck.com/graphql [[undefined]]
Info
09:15:05
console
ApolloError: request to https://api.paladindeck.com/graphql failed, reason: Client network socket disconnected before secure TLS connection was established
at new ApolloError (/var/task/node_modules/#apollo/client/errors/errors.cjs:34:28)
at /var/task/node_modules/#apollo/client/core/core.cjs:1598:19
at both (/var/task/node_modules/#apollo/client/utilities/utilities.cjs:986:53)
at /var/task/node_modules/#apollo/client/utilities/utilities.cjs:979:72
at new Promise (<anonymous>)
at Object.then (/var/task/node_modules/#apollo/client/utilities/utilities.cjs:979:24)
at Object.error (/var/task/node_modules/#apollo/client/utilities/utilities.cjs:987:49)
at notifySubscription (/var/task/node_modules/zen-observable/lib/Observable.js:140:18)
at onNotify (/var/task/node_modules/zen-observable/lib/Observable.js:179:3)
at SubscriptionObserver.error (/var/task/node_modules/zen-observable/lib/Observable.js:240:7) {
graphQLErrors: [],
clientErrors: [],
networkError: FetchError: request to https://api.paladindeck.com/graphql failed, reason: Client network socket disconnected before secure TLS connection was established
at ClientRequest.<anonymous> (/var/task/node_modules/next/dist/compiled/node-fetch/index.js:1:64142)
at ClientRequest.emit (events.js:412:35)
at ClientRequest.emit (domain.js:475:12)
at TLSSocket.socketErrorListener (_http_client.js:475:9)
at TLSSocket.emit (events.js:400:28)
at TLSSocket.emit (domain.js:475:12)
at emitErrorNT (internal/streams/destroy.js:106:8)
at emitErrorCloseNT (internal/streams/destroy.js:74:3)
at processTicksAndRejections (internal/process/task_queues.js:82:21) {
type: 'system',
errno: 'ECONNRESET',
code: 'ECONNRESET'
},
extraInfo: undefined
}
Error
09:15:06
console
[GET] /_next/data/hHiW6IT3wpykwmCV9Cdhe/collections/d45ebedf-d7f1-4208-bfbf-e7aa1af43bd3/e54b8945-6ed0-4094-8c54-fbd42e755e97.json?cardInCollectionId=e54b8945-6ed0-4094-8c54-fbd42e755e97&collectionId=d45ebedf-d7f1-4208-bfbf-e7aa1af43bd3 (SSR)
Info
09:15:06
All of the other pages (which do not use SSR, but query the API) work as expected.
I have looked at other similar issues but none of the solutions have helped thus far.
When I'm unable to find a solution to an issue like this I tend to think I'm doing something very stupid and not realizing it. So, it's entirely possible I'm just missing something so basic I'm not even thinking about it.

Whew... that took me a few days.
So, it turns out this wasn't a simple thing to diagnose (at least, not for me).
The short answer to my problem was: Don't pass the context headers from getServerSideProps to the Apollo client. For some reason, those headers, even with the authorization header being appended, were causing something to break.
Here's what I'm doing now:
// graphql-client.ts
export class GraphQLClient {
private readonly logger = new Logger(GraphQLClient.name);
get value(): ApolloClient<NormalizedCacheObject> {
if (!this._client) {
this._client = this.createClient();
}
if (this._client === undefined)
throw new Error(`Error when creating graphql client`);
return this._client;
}
constructor(
private readonly user?: User | null,
private _client?: ApolloClient<NormalizedCacheObject>,
) {}
private createClient(): ApolloClient<NormalizedCacheObject> {
const isSsrMode = typeof window === 'undefined';
const httpLink = createHttpLink({ uri: apolloConfig.uri });
const authLink = setContext(async (_, context) => {
let token: string | undefined;
if (context?.headers?.cookie) {
try {
token = getCookie(TOKEN_COOKIE_NAME, context.headers.cookie);
} catch (err) {
this.logger.error(err);
token = await this.user?.getIdToken();
}
} else {
token = await this.user?.getIdToken();
}
const headers = {
// HERE IS HOW I FIXED THINGS
// If this is SSR, DO NOT PASS THE REQUEST HEADERS.
// Just send along the authorization headers.
// The **correct** headers will be supplied by the `getServerSideProps` invocation of the query.
...(!isSsrMode ? context.headers : []),
authorization: token ? `Bearer ${token}` : ``,
};
return { headers };
});
return new ApolloClient({
link: authLink.concat(httpLink),
credentials: 'include',
cache: new InMemoryCache({
possibleTypes: generatedIntrospection.possibleTypes,
}),
ssrMode: isSsrMode,
});
}
}
// mypage.tsx
...
...
...
export const getServerSideProps: GetServerSideProps = async (context) => {
if (!isCardDetailsPageQueryType(context.query))
return {
props: {},
};
const logger = new Logger(
`${CardDetailsPage.name}_${getServerSideProps.name}`,
);
const client = new GraphQLClient();
const GET_CARD_DETAILS_QUERY = gql`
// query
`;
const results = await client.value.query({
query: GET_CARD_DETAILS_QUERY,
variables: { id: context.query.cardInCollectionId },
context: {
headers: {
...context.req.headers, // <-- just pass the context headers, the client will automatically append the authorization header
},
},
});
const GET_OTHER_PRINTINGS_BY_NAME_QUERY = gql`
// query
`;
const otherPrintingResults = await client.value.query({
query: GET_OTHER_PRINTINGS_BY_NAME_QUERY,
variables: {
name: results.data.cardsInCollection.card.name,
collectionId: context.query.collectionId,
},
context: {
headers: {
...context.req.headers, // <-- same as above
},
},
});
return {
props: {
cardsInCollection: results.data.cardsInCollection,
otherPrintings: otherPrintingResults.data.otherPrintings,
allCardsInCollection: otherPrintingResults.data.allCardsInCollection,
},
};
};
This might be a very specific issue for my specific use case, but I do hope someone, someday, finds this helpful.

Related

Reverse proxy : http-proxy-middleware

I am creating a reverse proxy server in NestJs and I encountered an error while proxying a request.
'Error: write EPROTO 8005BB0501000000:error:0A000152:SSL routines:final_renegotiate:unsafe legacy renegotiation disabled:../deps/openssl/openssl/ssl/statem/extensions.c:908:\n\n at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:94:16)\n at WriteWrap.callbackTrampoline (node:internal/async_hooks:130:17)'
Did not quite understood this but seems an error with ssl files. Please help.
Thanks.
const options = {
target: `${protocol}://${host}`,
pathRewrite: {
'^/proxy/':''
},
changeOrigin: true,
onProxyReq: function (proxyReq,req,res) {
// add any headers if required.
},
onError(err, req, res) {
},
proxyTimeout: 60 * 1000,
onProxyRes(proxyRes, req, res){
}
}
createProxyMiddleware{options}

Not being able to read statusCode when sending a request

I am developing an api with node.js but i am having trouble with one of my routers while sending a request in postman.
The following is my router:
//#route GET api/profile/github/:username
//#desc Get user repos from github
//#access public
router.get('/github/:username', (req,res)=>{
try {
const options = {
uri: `https://api/github.com/users/${req.params.username}/repos?per_page=5&sort=created:asc&client_id=${config.get('githubClientId')}&client_secret=${config.get('githubSecret')}`,
method:'GET',
headers:{'user_agent': 'node.js'}
};
request(options, (error, response, body) => {
if(error) console.error(error);
if(response.statusCode !== 200){
res.status(404).json('No Github profile found');
}
res.json(JSON.parse(body));
});
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
})
So in this route i am trying to find a github username that is being passed via the uri.
This is the request i am sending:
http://localhost:5000/api/profile/github/<GITHUB_USERNAME>
but when i send my request i get the following error in my VSC console.
Cannot read properties of undefined (reading 'statusCode')
if(response.statusCode !==200){
There are a couple issues here:
First, https://api/github.com/users/xxx should be https://api.github.com/users/xxx. You put a / where there should have been a . .
And, in fact, your very code should be showing the error:
Error: getaddrinfo ENOTFOUND api
at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:71:26) {
errno: -3008,
code: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'api'
}
which is telling you that the domain api was not found. It's looking for that domain because of the error in your URL.
Second, this error is complicated in your code because if you get an error returned from the request() library, then the callback arguments response and body are invalid, but you try to use them. The physical request did not succeed so there is no response or body.
You can amend your code like this:
router.get('/github/:username', (req, res) => {
try {
const options = {
uri: `https://api/github.com/users/${req.params.username}/repos?per_page=5&sort=created:asc&client_id=${config.get('githubClientId')}&client_secret=${config.get('githubSecret')}`,
method: 'GET',
headers: { 'user_agent': 'node.js' }
};
request(options, (error, response, body) => {
if (error) {
console.error(error);
res.status(500).json({code: error.code, message: "Network error communicating with github"});
return;
}
if (response.statusCode !== 200) {
res.status(404).json('No Github profile found');
return;
}
res.json(JSON.parse(body));
});
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
});
This handles the error by returning a 500 status and creating an error object to send back to the client. It also adds return statements after sending a response so the following code doesn't execute and try to send other responses.
NOTE: The request() library has been deprecated since early 2020 and will no longer be developed with new features. It is NOT recommended for new code. You can use newer, promise-based libraries such as node-fetch, got or any of the others listed here. In the most recent versions of nodejs, you can use the built-in version of fetch.

Socket.io server TransportError

I'm facing to an issue with a Socket.io server running throw an Express.js server with Next.js.
The server send error without any client connected. But clients can connect from browser without any issue...
Here is the error on console where the server is running :
Socket error TransportError: xhr poll error
at Polling.onError (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/engine.io-client/build/cjs/transport.js:46:37)
at Request.<anonymous> (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/engine.io-client/build/cjs/transports/polling.js:255:18)
at Request.Emitter.emit (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/#socket.io/component-emitter/index.js:143:20)
at Request.onError (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/engine.io-client/build/cjs/transports/polling.js:356:14)
at Timeout._onTimeout (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/engine.io-client/build/cjs/transports/polling.js:329:30)
at listOnTimeout (node:internal/timers:559:17)
at processTimers (node:internal/timers:502:7) {
description: 0,
context: XMLHttpRequest {
UNSENT: 0,
OPENED: 1,
HEADERS_RECEIVED: 2,
LOADING: 3,
DONE: 4,
readyState: 4,
onreadystatechange: [Function (anonymous)],
responseText: 'Error: getaddrinfo ENOTFOUND undefined\n' +
' at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:71:26)',
responseXML: '',
status: 0,
statusText: Error: getaddrinfo ENOTFOUND undefined
at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:71:26) {
errno: -3008,
code: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'undefined'
},
open: [Function (anonymous)],
setDisableHeaderCheck: [Function (anonymous)],
setRequestHeader: [Function (anonymous)],
getResponseHeader: [Function (anonymous)],
getAllResponseHeaders: [Function (anonymous)],
getRequestHeader: [Function (anonymous)],
send: [Function (anonymous)],
handleError: [Function (anonymous)],
abort: [Function (anonymous)],
addEventListener: [Function (anonymous)],
removeEventListener: [Function (anonymous)],
dispatchEvent: [Function (anonymous)]
},
type: 'TransportError'
}
socket.io-client:manager reconnect attempt error +1ms
socket.io-client:manager will wait 5000ms before reconnect attempt +0ms
Just to be clear, this error is not on client side but on the server, clients from browser have a working connection even with this error from the server. The client does not get any error and works fine.
Here is my express.js server code :
import express, {Express} from 'express';
import * as http from 'http';
import next, {NextApiHandler} from 'next';
import passport from 'passport';
import session, {SessionOptions} from 'express-session';
import {Options} from "session-file-store";
const FileStore = require('session-file-store')(session);
import uid from 'uid-safe';
import bodyParser from 'body-parser';
import routes from './routes';
import SocketServer from "../socketio/SocketServer";
import socketMiddleware from "../socketio/SocketMiddleware";
const port: number = parseInt(process.env.PORT || '3000', 10);
const dev: boolean = process.env.NODE_ENV !== 'production';
const nextApp = next({ dev });
const nextHandler: NextApiHandler = nextApp.getRequestHandler();
nextApp.prepare().then(() => {
const app: Express = express();
const server: http.Server = http.createServer(app);
// Socket io server
const socketServer = new SocketServer(server);
// Session
const fileStoreOptions: Options = {
path: './.next/session' // register session folder in .next folder
};
const sessionConfig: SessionOptions = {
//secret: uid.sync(18),
genid: (req) => {
return 'app_' + uid.sync(18) // use UUIDs for session IDs
},
secret: 'SECRET',
cookie: {
maxAge: 43200 * 1000 // 12h
},
resave: false,
saveUninitialized: true,
store: new FileStore(fileStoreOptions)
};
app.use(bodyParser.json());
app.use(session(sessionConfig));
passport.serializeUser((user: any, done: (a: any, b: string) => void) => done(null, user));
passport.deserializeUser((user: string, done: (a: any, b: any) => void) => done(null, user));
app.use(passport.initialize());
app.use(passport.session());
// Add socketio to request to have available in express routes
app.use(socketMiddleware(socketServer));
app.use('/api/auth', routes.security); // anonymous
app.use('/api/users', routes.users); // authenticated
app.use('/api/patients', routes.patients); // authenticated
app.use('/api/appointments', routes.appointments); // authenticated
app.use('/api/consultations', routes.consultations); // authenticated
app.use('/api/tasks', routes.tasks); // authenticated
app.use('/api/documents', routes.documents); // authenticated
app.use('/', routes.nextjs); // anonymous and authenticated
// Handle all nextjs route
app.all('*', (req: any, res: any) => nextHandler(req, res));
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
And then my SocketServer.ts
import {Server, Socket} from "socket.io";
import * as http from 'http';
import {ServerEvents, ClientEvents} from "./SocketEvents";
class SocketServer {
private io: Server;
public constructor(server: http.Server) {
this.io = new Server(server);
this.initServer();
}
private initServer = (): void => {
this.io.on(ServerEvents.CONNECTION, (socket: Socket) => {
this.initClient(socket);
});
}
private initClient = (socket: Socket): void => {
console.log('New client connected with id', socket.id);
// Emit status message for new connected client
socket.emit(ServerEvents.STATUS, 'Connected on Gynemanager SocketIO server');
socket.on(ClientEvents.USER_CONNECTED, async (socketId, username) => {
const sockets = await this.io.fetchSockets(); // Get all connected clients
// Add username to socket data
sockets.forEach(socket => {
if (socket.id === socketId) socket.data.username = username; // add username to new connected client
})
})
socket.on(ServerEvents.DISCONNECT, (reason: string) => {
console.log('Client disconnected', reason);
})
}
}
export default SocketServer
And then the SocketMiddleware.ts
import {Response, NextFunction} from 'express';
import SocketServer from "./SocketServer";
import {CustomRequest} from "../types/express";
const socketMiddleware = (socketServer: SocketServer) => {
return (req: CustomRequest, res: Response, next: NextFunction) => {
req.socketServer = socketServer;
next();
}
}
export default socketMiddleware;
Any help will be appreciate.
Finally I figure out what was my problem.
I initialised the socket.io connection from _app.tsx for clients because this is the place where I use React context provider to bind my SocketClient.ts to the whole application.
To fix the issue I have to move the connection initialisation from _app.tsx to another page (for my case from the page redirected from login page), and check if connection is already init on the other page where socket is also needed to avoid multi connection and then everything works as expected.

getaddrinfo ENOTFOUND error Shopify getSubscriptionUrl Shopify Admin API GraphQL

I'm attempting to setup a Shopify recurring subscription app written in React/Node, but I get a FetchError when I call the following code from my server.js
The Error:
FetchError: request to https://[ACCESS_TOKEN]/admin/api/2020-10/graphql.json failed, reason: getaddrinfo ENOTFOUND [ACCESS_TOKEN]
at ClientRequest.<anonymous> (/Users/cormachayden/Desktop/apps/easy-tok/node_modules/node-fetch/lib/index.js:1461:11)
at ClientRequest.emit (events.js:315:20)
at TLSSocket.socketErrorListener (_http_client.js:469:9)
at TLSSocket.emit (events.js:315:20)
at emitErrorNT (internal/streams/destroy.js:106:8)
at emitErrorCloseNT (internal/streams/destroy.js:74:3)
at processTicksAndRejections (internal/process/task_queues.js:80:21)
My Code:
const getSubscriptionUrl = async (accessToken, shop, returnUrl = process.env.HOST) => {
const subscirptionQuery = JSON.stringify({
query: `mutation {
appSubscriptionCreate(
name: "Easy Tok Premium"
lineItems: {
plan: {
appRecurringPricingDetails: {
price: { amount: 4.0, currencyCode: USD}
}
}
}
test: true
returnUrl: "https://${shop}.myshopify.com/admin/apps/easy-tok"
) {
confirmationUrl
}
}`
});
const response = await fetch(`https://${shop}/admin/api/2020-10/graphql.json`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"X-Shopify-Access-Token": accessToken,
},
body: subscirptionQuery
})
const responseJson = await response.json();
return responseJson.data.appSubscriptionCreate.confirmationUrl;
};
module.exports = getSubscriptionUrl;
The same error occurs both locally and when I deploy to Heroku
Your shop variable is returning your AccessToken and not shop_name.myshopify.com. So your request is becoming https://ACESS_TOKEN/admin/api/2020-10/graphql.json which is invalid.
Please double check your shop variable and where are you setting this and please generate a new access token immediately since you shouldn't provide your AccessToken publicly at any cost.
PS: I edited your question to remove the token for security reasons! (have in mind that the history will still keep it, so please change it)

React Native [Network error]: TypeError: Network request failed using Apollo Client

I just want to get connected on my Apollo Server on localhost using React Native with Expo.
If I try to use the following api link https://q815p14lp.lp.gql.zone/graphql its working fine. But if I try to use http://127.0.0.1:8080/graphql or http://localhost:8080/graphql I get the following error: [Network error]: TypeError: Network request failed
This is my code where I try to get connected.
const httpLink = new HttpLink({ uri: 'http://127.0.0.1:8080/graphql' });
const authLink = setContext(async (_, { headers }) => {
const token = await getToken();
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
}
};
});
const errorLink = onError(({ networkError, graphQLErrors }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError) console.log(`[Network error]: ${networkError}`);
})
const client = new ApolloClient({
link: ApolloLink.from([errorLink, authLink, httpLink]),
cache: new InMemoryCache()
});
"apollo-boost": "^0.1.18",
"apollo-link-context": "^1.0.9",
"apollo-link-error": "^1.1.1",
"expo": "^30.0.1",
"react-apollo": "^2.2.4",
Any idea what am I doing wrong?
The host *.lp.gql.zone is a Launchpad graphql server. So you have your server running on Launchpad, not on your local machine.
Also connecting to your local machine using Expo does not make a lot of sense. The "localhost" in this case is the phone device that runs the app.
I guess what you really want is for the Expo app on the device to connect to your development graphql server on your mac/pc? In this case you should use the IP address you see in the debug view:
Your connect url in this case becomes: http://10.10.9.70:8080/graphql