请原谅我的新手问题,但要使该方法正确工作,我遇到了很多困难。
我很难让Next.js,Apollo和GraphQL-Yoga的订阅正常运行。我尝试在任何地方搜索答案,并且已经进行了大约2周的工作,但是没有运气。
我的代码从Wes Bos的Advanced GraphQL课程代码开始,该课程代码使用Next.js,Apollo,Prisma和GraphQL-Yoga。它还使用cookie来存储JWT令牌,我认为这可能会导致WebSocketLink身份验证出现问题,但我可能是错的。我已成功将上传内容添加到Amazon S3,但是我一直在努力使订阅正常运行。我一直在超时,与websocket断开连接,有时我在控制台中看到大量的连接/断开消息,这似乎不正确,但我不确定100%会发生什么。我希望在我的网站上具有实时聊天功能以及通知。我以为我可以一次完成所有工作,但这似乎是断断续续的。有时它可以正常工作,并且聊天/通知会立即显示,而其他时候却似乎不起作用,并且需要在网络浏览器中进行重新加载才能在下一次尝试后停止工作。
无论如何,这是我的代码:
客户pages/_app.js
:
import App, { Container } from "next/app";
import Root from "../components/Root";
import { ApolloProvider } from "@apollo/react-hooks";
import withData from "../lib/withData";
class MyApp extends App {
// gets all page properties and queries before loading to pass along
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
// this exposes the query to the user
pageProps.query = ctx.query;
return { pageProps };
}
render() {
const { Component, apollo, pageProps } = this.props;
return (
<Container>
<ApolloProvider client={apollo}>
<Root client={apollo}>
<Component client={apollo} {...pageProps} />
</Root>
</ApolloProvider>
</Container>
);
}
}
export default withData(MyApp);
客户lib/withData.js
:
import withApollo from "next-with-apollo";
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from "apollo-link-http";
import * as ws from "ws";
import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities";
import { onError } from "apollo-link-error";
import { ApolloLink, Observable, split } from "apollo-link";
import { RetryLink } from "apollo-link-retry";
import { createUploadLink } from "apollo-upload-client";
import { endpoint, wsEndpoint } from "../config";
const request = (operation, headers) => {
operation.setContext({
fetchOptions: {
credentials: "include"
},
headers
});
};
function createClient({ ctx, headers, initialState }) {
const httpLink = new HttpLink({
uri: process.env.NODE_ENV === "development" ? endpoint : endpoint
});
const wsLink = process.browser
? new WebSocketLink({
uri: process.env.NODE_ENV === "development" ? wsEndpoint : wsEndpoint,
options: {
reconnect: true
}
})
: () => {
console.log("SSR");
};
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
// if (networkError) console.log(`[Network error]: ${networkError}`);
if (networkError) console.log("[Network error]: ", networkError);
}),
new ApolloLink(
(operation, forward) =>
new Observable(observer => {
let handle;
Promise.resolve(operation)
.then(oper => request(oper, headers))
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
),
new RetryLink().split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
// httpLink,
// ERROR: httpLink breaks uploader
createUploadLink({
uri: process.env.NODE_ENV === "development" ? endpoint : endpoint
})
)
]),
cache: new InMemoryCache().restore(initialState || {})
});
}
export default withApollo(createClient);
服务器index.js
:
require("dotenv").config({ path: ".env" });
const cookie = require("cookie");
const cookieParser = require("cookie-parser");
const cors = require("cors");
const jwt = require("jsonwebtoken");
const expressip = require("express-ip");
const helmet = require("helmet");
const compression = require("compression");
const { S3 } = require("aws-sdk");
const logger = require("./utils/logger");
const createServer = require("./createServer");
const db = require("./db");
const server = createServer();
const s3client = new S3({
accessKeyId: process.env.S3_KEY,
secretAccessKey: process.env.S3_SECRET,
params: {
Bucket: process.env.S3_BUCKET
}
});
server.express.use(helmet());
server.express.use(compression());
server.express.use(cookieParser());
server.express.use(expressip().getIpInfoMiddleware);
// get the users IP information
server.express.use((req, res, next) => {
const userIp = req.ipInfo;
if (userIp) {
req.userIp = userIp;
}
next();
});
// decode the JWT so we can ge the user ID on each request
server.express.use((req, res, next) => {
const { token } = req.cookies;
if (token) {
const { userId } = jwt.verify(token, process.env.APP_SECRET);
// put the userId onto the request for future requests to access
req.userId = userId;
}
next();
});
// create a middleware that populates the user on each request
server.express.use(async (req, res, next) => {
// if they aren't logged in, skip this
if (!req.userId) return next();
const user = await db.query.user(
{ where: { id: req.userId } },
"{ id, email, emailMask, emailVerified, role, permissions, account { accountType isBanned } }"
);
req.user = user;
next();
});
const corsWhitelist = [
process.env.FRONTEND_URL,
process.env.ADMIN_URL
];
server.express.use(
"/*",
cors({
credentials: true,
origin: function(origin, callback) {
// note: if same origin makes requests with origin header, it needs to be whitelisted
if (corsWhitelist.indexOf(origin) !== -1) {
// console.log("express origin on whitelist");
// console.log("express origin", origin);
callback(null, true);
} else if (origin === undefined) {
// console.log("express origin is undefined");
callback(null, true);
} else {
// callback(null, true)
callback(new Error(`${origin} not allowed by CORS`));
}
}
})
); // allow cors
// if (process.env.NODE_ENV === 'development') server.express.use(logger);
server.start(
{
cors: {
credentials: true,
origin: function(origin, callback) {
// note: if same origin makes requests with origin header, it needs to be whitelisted
if (corsWhitelist.indexOf(origin) !== -1) {
// console.log("gql origin on whitelist");
// console.log("gql origin", origin);
callback(null, true);
} else if (origin === undefined) {
// console.log("gql origin is undefined");
callback(null, true);
} else {
// callback(null, true)
callback(new Error(`${origin} not allowed by CORS`));
}
}
},
subscriptions: {
keepAlive: true,
onConnect: async (connectionParams, webSocket) => {
console.log("Websocket CONNECTED");
const header = webSocket.upgradeReq.headers.cookie;
const { token } = cookie.parse(header);
try {
const promise = new Promise((resolve, reject) => {
const { userId } = jwt.verify(token, process.env.APP_SECRET);
resolve(userId);
});
const user = await promise;
return user;
} catch (err) {
throw new Error(err);
}
},
onDisconnect: () => {
console.log("Websocket DISCONNECTED");
}
}
},
deets => {
console.log(
`? Backend is now running on port http:/localhost:${deets.port}`
);
}
);
服务器createServer.js
:
const { GraphQLServer, PubSub } = require("graphql-yoga");
const depthLimit = require("graphql-depth-limit");
const Mutation = require("./resolvers/Mutation");
const Query = require("./resolvers/Query");
const Subscription = require("./resolvers/Subscription");
const Conversation = require("./resolvers/Conversation");
const db = require("./db");
const pubsub = new PubSub();
// Create the GraphQL Yoga Server
function createServer() {
return new GraphQLServer({
typeDefs: "src/schema.graphql",
resolvers: {
Mutation,
Query,
Subscription,
Conversation
},
resolverValidationOptions: {
requireResolversForResolveType: false
},
// https://github.com/stems/graphql-depth-limit
// https://blog.apollographql.com/securing-your-graphql-api-from-malicious-queries-16130a324a6b
validationRules: [depthLimit(10)],
uploads: {
// Limits here should be stricter than config for surrounding
// infrastructure such as Nginx so errors can be handled elegantly by
// graphql-upload:
// https://github.com/jaydenseric/graphql-upload#type-uploadoptions
maxFileSize: 10000000, // 10 MB
maxFiles: 20
},
context: (req, connection) => ({
...req,
pubsub,
db
})
});
}
module.exports = createServer;
我还不断收到WebSocket connection to 'ws://localhost:4444/' failed: WebSocket is closed before the connection is established.
错误,这可能是我的主要问题,但我不知道如何解决或清除它。
任何帮助将不胜感激,因为这非常令人沮丧!
谢谢!