Meteor + Apollo Subscription: Websocket connection closed - meteor

I'm using meteor and trying to make Apollo Subscriptions to work, but I'm getting
WebSocket connection to 'ws://127.0.0.1:3000/sockjs/401/m892wugm/websocket' failed: Connection closed before receiving a handshake response in the client.
I followed apollographql.com guide for Server Configuration and Client Configuration but I'm not quite sure how to connection the client to the server yet.
In the client, I'm using ApolloClient and ApolloLink to pass the Meteor auth to GraphQL.
Here's the code:
Client
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloLink } from 'apollo-link'
const httpLink = new createHttpLink()
const authLink = new ApolloLink((operation, forward) => {
operation.setContext(() => ({
headers: { 'meteor-login-token': Accounts._storedLoginToken() },
}))
return forward(operation)
})
export default ApolloClient = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
})
Server
createApolloServer({
schema,
tracing: true,
cacheControl: true,
})
new SubscriptionServer({
schema,
execute,
subscribe,
}, {
server: WebApp.httpServer,
path: '/subscriptions',
})
Package.json (not everything, of course)
Meteor 1.6.1.1
...
"apollo-client": "^2.2.5",
"apollo-link": "^1.2.1",
"apollo-link-context": "^1.0.7",
"apollo-link-http": "^1.5.3",
"apollo-link-ws": "^1.0.8",
"subscriptions-transport-ws": "^0.9.9",
...
I read somewhere that passing noServer: true to the SubscriptionServer() resolve the conflict. The error indeed goes away, but the subscription doesnt seem to work either.
And yes, I have followed the Meteor Integration guide from apollographql, but the info there is outdated and does not work.

Related

Oak framework Doesnt respond to request in deno deploy

I tried deploying my deno app to deno deploy but I have tried all means to work but still no response and I have no error in logs.
This my code below..
import { load } from "https://deno.land/std#0.171.0/dotenv/mod.ts";
import { Application } from "https://deno.land/x/oak#v11.1.0/mod.ts";
import { socketIo } from "../src/controllers/websocket/setup.ts";
import fileRouter from "./../src/routes/file_rt.ts";
import ordersRouter from "./../src/routes/orders_rt.ts";
import mealRouter from "./../src/routes/meal_rt.ts";
import userRouter from "./../src/routes/user_rt.ts";
load();
const app = new Application();
app.use(await rateLimit);
app.use(userRouter.routes());
app.use(ordersRouter.routes());
app.use(mealRouter.routes());
app.use(fileRouter.routes());
app.use(userRouter.allowedMethods());
app.use(ordersRouter.allowedMethods());
app.use(mealRouter.allowedMethods());
app.use(fileRouter.allowedMethods());
socketIo();
await app.listen({port:80});
I tried to test an api route using postman but the endpoint didn't log anything
I have fixed it by removing the socket IO connection I imported. [ socketIO() ]
But now the socket connection is not working.
export const socketIo = async () => {
io.on("connection", (socket) => {
console.log(`socket ${socket.id} connected`);
skt = socket;
signUSER(socket);
socket.on("disconnect", (reason) => {
console.log(`socket ${socket.id} disconnected due to ${reason}`);
});
console.log("Socket Hit 😎✨");
});
await serve(io.handler(), {
port: 3000,
});
}

NextJS Actioncable Proxy

So I'm trying to do two things at the same time and it's not going too well.
I have a NextJS app and a Rails API server this app connects to. For authentication I'm using a JWT token stored in an http-only encrypted cookie that the Rails API sets and the front end should not be touching. Naturally that creates a necessity for the frontend to send all the api requests though the NextJs server which proxies them to the real API.
To do that I have set up a next-http-proxy-middleware in my /pages/api/[...path] in the following way:
export const config = { api: { bodyParser: false, externalResolver: true } }
export default function handler(
req: NextApiRequest,
res: NextApiResponse
) {
httpProxyMiddleware(req, res, {
target: process.env.BACKEND_URL,
pathRewrite: [{ patternStr: "^/?api", replaceStr: "" }],
})
}
Which works great and life would be just great, but turns out I need to do the same thing with ActionCable subscriptions. Not to worry, found some handy tutorials, packed #rails/actioncable into my package list and off we go.
import {useCurrentUser} from "../../../data";
import {useEffect, useState} from "react";
const UserSocket = () => {
const { user } = useCurrentUser()
const [roomSocket, setRoomSocket] = useState<any>(null)
const loadConsumer = async () => {
// #ts-ignore
const { createConsumer } = await import("#rails/actioncable")
const newCable = createConsumer('/api/wsp')
console.log('Cable loaded')
setRoomSocket(newCable.subscriptions.create({
channel: 'RoomsChannel'
},{
connected: () => { console.log('Room Connected') },
received: (data: any) => { console.log(data) },
}))
return newCable
}
useEffect(() => {
if (typeof window !== 'undefined' && user?.id) {
console.log('Cable loading')
loadConsumer().then(() => {
console.log('Cable connected')
})
}
return () => { roomSocket?.disconnect() }
}, [typeof window, user?.id])
return <></>
}
export default UserSocket
Now when I go to load the page with that component, I get the log output all the way to Cable connected however I don't see the Room Connected part.
I tried looking at the requests made and for some reason I see 2 requests made to wsp. First is directed at the Rails backend (which means the proxy worked) but it lacks the Cookie headers and thus gets disconnected like this:
{
"type": "disconnect",
"reason": "unauthorized",
"reconnect": false
}
The second request is just shown as ws://localhost:5000/api/wsp (which is my NextJS dev server) with provisional headers and it just hangs up in pending. So neither actually connect properly to the websocket. But if I just replace the /api/wsp parameter with the actual hardcoded API address (ws://localhost:3000/wsp) it all works at once (that however would not work in production since those will be different domains).
Can anyone help me here? I might be missing something dead obvious but can't figure it out.

Next js - Next Auth - Keep having error=OAuthCreateAccount (google provider)

I have set up next-auth with the GoogleProvider.
Everything works fine locally, however in production, I am having aOAuthCreateAccount error: api/auth/signin?error=OAuthCreateAccount
stating "Try signing in with a different account."
I have provided the ID & Secret of the Provider, I have dropped my DB, tried to log with multiples accounts... I do not understand. Is there something that my production environment is not accessing?
Here's my nextauth.js:
`
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import CredentialsProvider from "next-auth/providers/credentials";
import { MongoDBAdapter } from "#next-auth/mongodb-adapter";
import clientPromise from "../../../lib/mongodb";
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
// ...add more providers here
],
secret: process.env.NEXTAUTH_SECRET,
// Can custom page & path
pages: {
signOut: "/auth/signout",
error: "/auth/error", // Error code passed in query string as ?error=
verifyRequest: "/auth/verify-request", // (used for check email message)
// newUser: "/auth/new-user", // New users will be directed here on first sign in (leave the property out if not of interest)
newUser: "/recruiter/2", // New users will be directed here on first sign in (leave the property out if not of interest)
},
adapter: MongoDBAdapter(clientPromise),
});
`
And my mongodb.js:
`
import { MongoClient } from "mongodb";
const uri = process.env.MONGODB_URI;
const options = {
useUnifiedTopology: true,
useNewUrlParser: true,
};
let client;
let clientPromise;
if (!process.env.MONGODB_URI) {
throw new Error("Please add your Mongo URI to .env.local");
}
if (process.env.NODE_ENV === "development") {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options);
global._mongoClientPromise = client.connect();
}
clientPromise = global._mongoClientPromise;
} else {
// In production mode, it's best to not use a global variable.
client = new MongoClient(uri, options);
clientPromise = client.connect();
}
// Export a module-scoped MongoClient promise. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise;
`
Thank you!
Read the documentations.
Look on Stackoverflow and github thread, tried all the offered solutions, in vain.
I have managed to fix it reading this thorough article: https://medium.com/geekculture/why-and-how-to-get-started-with-next-auth-61740558b45b
I was missing the database variable in my deployment system (vercel) :)

NextJs - Apollo - First render is not SSR

I am using NextJs(9.3.0) for SSR and graphQL (Apollo).
My app is working well, but when I check what google is seeing, the data from Apollo are not available
If I am doing a curl https://myWebsite.com randomly, I have sometime the content (like google) without the data from Apollo, and sometimes with the data from Apollo.
For SEO purpose, I need to always have the first render (after a refresh) with the data given buy my Backend (Apollo)
Here is my file: apolloClient.tsx
import { ApolloClient } from "apollo-client";
import { AUTH_TOKEN } from "./config";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from "apollo-link-http";
import Cookies from "js-cookie";
import fetch from "isomorphic-unfetch";
import nextCookies from "next-cookies";
import { uriBackend } from "./config";
let token = null;
export default function createApolloClient(initialState, ctx) {
// The `ctx` (NextPageContext) will only be present on the server.
// use it to extract auth headers (ctx.req) or similar.
// on server
if (ctx && ctx.req && ctx.req.headers["cookie"]) {
token = nextCookies(ctx)[AUTH_TOKEN];
// on client
} else {
// console.log("with data get client cookie");
token = Cookies.get(AUTH_TOKEN);
}
const headers = token ? { Authorization: `Bearer ${token}` } : {};
return new ApolloClient({
ssrMode: Boolean(ctx),
link: new HttpLink({
uri: uriBackend, // Server URL (must be absolute)
fetch,
headers
}),
cache: new InMemoryCache().restore(initialState)
});
}
Looks to me like your SSR doesn't wait for the data fetching.
One solution for you could be, if your data changes rarely, to staticelly generate you pages with data:
https://nextjs.org/docs/basic-features/pages#scenario-1-your-page-content-depends-on-external-data
If you use SSR, make sure you use an async getServerSideProps that awaits your data requests:
https://nextjs.org/docs/basic-features/pages#server-side-rendering

Meteor/React/ApolloServer/BodyParser - Payload too large

I am trying to save quite a big object thanks to a Mutation object in my meteor/react app but I am getting a Payload too large error in the console :
PayloadTooLargeError: request entity too large
I know my object is more than the 100kb which is the default limit for bodyparser but I can not managed to have it changed.
I have tried the following while initiating the Apollo Server:
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
return ({
user: await getUser(req.headers.authorization)
})
}
})
server.applyMiddleware({
app: WebApp.connectHandlers,
path: '/graphql'
})
WebApp.connectHandlers.use(bodyParser.json({limit: '100mb', extended: true}));
WebApp.connectHandlers.use('/graphql', (req, res) => {
if (req.method === 'GET') {
res.end()
}
})
But I am still getting the same error. I think my object is around 400kb. I am hoping one of you could help me. Thanks in advance.
apollo-server-express already includes body-parser so you shouldn't add it again as middleware. Instead, you can pass body-parser options to Apollo when calling applyMiddlware:
server.applyMiddleware({
app: WebApp.connectHandlers,
path: '/graphql',
bodyParserConfig: {
limit: '100mb',
},
})
See the docs for a full list of available options.

Resources