I'm pretty new to graphql and apollo, and I'm working on a project where I need to be able to use context to get a variable for my query. The issue that I'm having is that my context.user is coming back null because my req.headers.authorization is undefined. I'm unsure as to why this is happening as in my frontend
const authLink = setContext(async (_, { headers }) => {
const token = await AsyncStorage.getItem('token');
try {
if (token !== null) {
return {
headers: {
...headers,
authorization: `Bearer ${token}` || null,
}
}
}
}
catch (error) {
throw error;
}
});
my token is not null and when I tested const auth: `Bearer ${token}` || null console.log(auth) after if (token !== null) { it came back with Bearer and my token value. Does anyone know why this is happening? I would really appreciate any help or advice. Thank you!
rest of frontend client.js
import { ApolloClient, split, createHttpLink, HttpLink, InMemoryCache } from '#apollo/client';
import { getMainDefinition } from '#apollo/client/utilities';
import { setContext } from "#apollo/client/link/context";
import AsyncStorage from '#react-native-async-storage/async-storage';
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const wslink = new GraphQLWsLink(
createClient({
url: "ws://localhost:4000/subscriptions",
/* connectionParams: {
authToken: user.authToken,
},*/
}),
);
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql',
});
const authLink = setContext(async (_, { headers }) => {
const token = await AsyncStorage.getItem('token');
try {
if (token !== null) {
return {
headers: {
...headers,
authorization: `Bearer ${token}` || null,
}
}
}
}
catch (error) {
throw error;
}
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wslink,
authLink.concat(httpLink)
);
export const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
backend index.js
import express from 'express';
import mongoose from 'mongoose';
import WebSocket from 'ws';
import { createServer } from 'http';
import { ApolloServer } from 'apollo-server-express';
import { makeExecutableSchema } from '#graphql-tools/schema';
import {
ApolloServerPluginDrainHttpServer,
ApolloServerPluginLandingPageLocalDefault,
} from "apollo-server-core";
import { useServer } from 'graphql-ws/lib/use/ws';
import constants from './config/constants.js';
import typeDefs from './graphql/schema.js';
import resolvers from './graphql/resolvers/index.js';
import { decodeToken } from './services/auth.js';
const app = express();
const httpServer = createServer(app);
const schema = makeExecutableSchema({ typeDefs, resolvers });
const server = new ApolloServer({
schema,
context: ({ req }) => ({
user: req.user
}),
csrfPrevention: true,
cache: "bounded",
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
async serverWillStart() {
return {
async drainServer() {
await serverCleanup.dispose();
},
};
},
},
ApolloServerPluginLandingPageLocalDefault({ embed: true }),
],
});
const wsServer = new WebSocket.Server({
server: httpServer,
path: '/graphql',
});
const serverCleanup = useServer({ schema }, wsServer);
async function auth(req, res, next) {
try {
const token = req.headers.authorization;
//token is undefined Why???
if (token != null) {
const user = await decodeToken(token);
req.user = user; // eslint-disable-line
}
else {
req.user = null; // eslint-disable-line
}
next();
} catch (error) {
throw error;
}
}
app.use(auth);
await server.start();
server.applyMiddleware({ app });
mongoose
.connect(process.env.MONGODB_URL || 'mongodb://localhost/AMO', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => {
console.log("MongoDB Connected");
return httpServer.listen(4000, () => {
console.log(`Server ready at http://localhost:4000/graphql`);
});
})
Related
I have tried to resolve this network errors but it has no possible and I do not know what is causing this. This is my apollo server index:
const mongoose = require('mongoose')
const {ObjectId} = require('mongodb')
const { createServer } = require('http')
const { execute, subscribe } = require('graphql')
const { SubscriptionServer } = require('subscriptions-transport-ws')
const { makeExecutableSchema } = require ('#graphql-tools/schema')
const express = require('express')
const { ApolloServer } = require('apollo-server-express')
const {resolvers,typeDefs} = require('./graphql')
const jwt = require('jsonwebtoken')
const {onConnect,onDisconnect} = require('./controllers/User')
require('dotenv').config()
const graphqlUploadExpress = require('graphql-upload/graphqlUploadExpress.js')
const { EventEmitter } = require('events')
const { PubSub } =require('graphql-subscriptions')
const { RedisPubSub } =require('graphql-redis-subscriptions')
const Redis = require('ioredis')
const
{
ApolloServerPluginDrainHttpServer,
ApolloServerPluginLandingPageGraphQLPlayground,
ApolloServerPluginLandingPageLocalDefault
} = require('apollo-server-core')
const { WebSocketServer} = require('ws')
const {useServer } = require('graphql-ws/lib/use/ws')
const path = require('path')
const bodyParser = require('body-parser')
const biggerEventEmitter = new EventEmitter();
biggerEventEmitter.setMaxListeners(0);
const options = {
host: process.env.REDIS_DOMAIN_NAME,
port: process.env.PORT_NUMBER,
password:process.env.REDIS_PASSWORD,
retryStrategy: times => {
// reconnect after
return Math.min(times * 50, 2000);
}
};
const pubsub = process.env.NODE_ENV === 'development' ? new PubSub({eventEmitter: biggerEventEmitter}) : new RedisPubSub({
publisher: new Redis(options),
subscriber: new Redis(options)
});
mongoose.connect(process.env.BBDD,
{},(err,_)=>
{
if(err)
{
console.log("Error de conexion")
}
else
{
console.log("Conexion Base de datos Correcta")
server()
}
})
async function server()
{
const app = express()
const httpServer = createServer(app)
const schema = makeExecutableSchema({ typeDefs, resolvers })
const PORT = process.env.APP_PORT
const getDynamicContext = async (ctx, msg, args) => {
if (ctx.connectionParams.authToken) {
const user = jwt.verify(ctx.connectionParams.authToken.replace("Bearer ", ""),process.env.KEY);
return { user, pubsub};
}
return { user: null };
};
const wsServer = new WebSocketServer(
{
server: httpServer,
path: '/graphql'
})
const serverCleanup = useServer({
schema,
context: (ctx, msg, args) => {
return getDynamicContext(ctx, msg, args)
},
onConnect: async (ctx) => {
console.log('onConnect');
let connectionParams = ctx.connectionParams
try
{
if (connectionParams.authToken)
{
const user = jwt.verify(connectionParams.authToken.replace("Bearer ", ""),process.env.KEY)
await onConnect(user.id,pubsub)
return { user , pubsub}
}
}
catch(error)
{
throw new Error('Missing auth token!')
}
},
async onDisconnect(context)
{
console.log('onDisconnect');
try
{
if(context.connectionParams&&context.connectionParams.authToken)
{
const user = jwt.verify(context.connectionParams.authToken.replace("Bearer ", ""),process.env.KEY)
await onDisconnect(user.id,pubsub)
}
}
catch(error)
{
/* throw new Error('Missing context!') */
}
}, }, wsServer)
const server = new ApolloServer(
{
schema,
persistedQueries:false,
context: async ({ req ,connection }) =>
{
let authorization = req.headers.authorization
try
{
if(authorization)
{
user = jwt.verify(authorization.replace("Bearer ", ""),process.env.KEY)
return{
user,
pubsub
}
}
}
catch (error)
{
throw new Error("Token invalido");
}
},
csrfPrevention: true,
cache: 'bounded',
plugins: [ApolloServerPluginDrainHttpServer({ httpServer }),
ApolloServerPluginLandingPageLocalDefault({ embed: true }),
{
async serverWillStart() {
return {
async drainServer() {
await serverCleanup.dispose();
},
};
},
}]
})
app.use(graphqlUploadExpress())
await server.start()
server.applyMiddleware({ app })
httpServer.listen(process.env.PORT||4000, () => {
console.log(
`Server is now running on http://localhost:${process.env.PORT||4000}${server.graphqlPath}`,
);
});
}
And this is my apollo client config:
import {ApolloClient,createHttpLink,defaultDataIdFromObject,InMemoryCache,split} from '#apollo/client'
import {WebSocketLink} from '#apollo/client/link/ws'
import { getMainDefinition } from '#apollo/client/utilities'
import { setContext } from "apollo-link-context"
import {createUploadLink} from 'apollo-upload-client'
import {getToken, getUpdatedTokenApi} from '../utils/auth'
import { GraphQLWsLink } from '#apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
import { from, HttpLink } from '#apollo/client';
import { RetryLink } from '#apollo/client/link/retry';
import { disableFragmentWarnings } from 'graphql-tag';
disableFragmentWarnings()
const type = () =>
{
if(window.location.pathname.startsWith(`/${process.env.ADMIN_DEV}`))
{
return 'admin'
}
else
{
return null
}
}
const link = (e)=>
{
switch (e) {
case 1:
return 'https://unglo.herokuapp.com/graphql'
case 2:
return 'http://localhost:4000/graphql'
default:
break;
}
}
const ws_Link = (e)=>
{
switch (e) {
case 1:
return 'wss://unglo.herokuapp.com/graphql'
case 2:
return 'ws://localhost:4000/graphql'
default:
break;
}
}
import { onError } from "#apollo/client/link/error";
// Log any GraphQL errors or network error that occurred
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError) console.log(`[Network error]: ${networkError}`);
});
const token = getToken(type())
const updatedToken = getUpdatedTokenApi()
const httpLink = new createUploadLink({
uri: link(2),
headers: {
'Apollo-Require-Preflight': 'true',
},
});
const wsLink = new GraphQLWsLink(createClient(
{
url: ws_Link(2),
connectionParams:
{
authToken: token ? `Bearer ${token}` : updatedToken ? `Bearer ${updatedToken}` : "",
},
lazy:true,
}
))
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
errorLink
);
const authMiddleware = setContext((_, { headers }) =>
{
return {
headers:
{
...headers,
Authorization: token ? `Bearer ${token}` :updatedToken ? `Bearer ${updatedToken}` : "",
},
}
})
const client = new ApolloClient
({
ssrMode: true,
connectToDevTools:true,
cache: new InMemoryCache({
}),
link:authMiddleware.concat(splitLink),
})
export default client
And these are errors:
I have tried removing all apollo cliente queries and errors persists and google console dont show error details
Please if any body know about this kind of errors it shoud be helpfull
I've been working on a way to set up authentication and authorization for my NextJS app, so far it was pretty easy but I've hit a wall.
I have a value that lives and is watched on a context, and I have a HOC that I need for my NextJS app to be able to use hooks with GraphQl, the issues is that I don't think I can call the context and use the value from a HOC, since it is simply not allowed.
Is there a way I can dynamically change the value on the HOC so that when the user logs in, I can then update the HOC to have the proper access token?
Some context: the user is first anonymous, whenever he/she logs in, I get an auth state change from Firebase from which I can extract the access token and add it to any future requests. But the point of the hoc is to provide next with full Graphql capabilities, the thing is that I need that hoc go listen for changes on a context state.
This is the Connection Builder:
import {
ApolloClient,
InMemoryCache,
HttpLink,
NormalizedCacheObject,
} from "#apollo/client";
import { WebSocketLink } from "#apollo/client/link/ws";
import { SubscriptionClient } from "subscriptions-transport-ws";
const connectionString = process.env.HASURA_GRAPHQL_API_URL || "";
const createHttpLink = (authState: string, authToken: string) => {
const isIn = authState === "in";
const httpLink = new HttpLink({
uri: `https${connectionString}`,
headers: {
// "X-hasura-admin-secret": `https${connectionString}`,
lang: "en",
"content-type": "application/json",
Authorization: isIn && `Bearer ${authToken}`,
},
});
return httpLink;
};
const createWSLink = (authState: string, authToken: string) => {
const isIn = authState === "in";
return new WebSocketLink(
new SubscriptionClient(`wss${connectionString}`, {
lazy: true,
reconnect: true,
connectionParams: async () => {
return {
headers: {
// "X-hasura-admin-secret": process.env.HASURA_GRAPHQL_ADMIN_SECRET,
lang: "en",
"content-type": "application/json",
Authorization: isIn && `Bearer ${authToken}`,
},
};
},
})
);
};
export default function createApolloClient(
initialState: NormalizedCacheObject,
authState: string,
authToken: string
) {
const ssrMode = typeof window === "undefined";
let link;
if (ssrMode) {
link = createHttpLink(authState, authToken);
} else {
link = createWSLink(authState, authToken);
}
return new ApolloClient({
ssrMode,
link,
cache: new InMemoryCache().restore(initialState),
});
}
This is the context:
import { useState, useEffect, createContext, useContext } from "react";
import { getDatabase, ref, set, onValue } from "firebase/database";
import { useFirebase } from "./use-firebase";
import { useGetUser } from "../hooks/use-get-user";
import { getUser_Users_by_pk } from "../types/generated/getUser";
import { getApp } from "firebase/app";
const FirebaseAuthContext = createContext<FirebaseAuthContextProps>({
authUser: null,
authState: "",
authToken: null,
currentUser: undefined,
loading: true,
login: () => Promise.resolve(undefined),
registerUser: () => Promise.resolve(undefined),
loginWithGoogle: () => Promise.resolve(undefined),
loginWithMicrosoft: () => Promise.resolve(undefined),
});
export const FirebaseAuthContextProvider: React.FC = ({ children }) => {
const [loading, setLoading] = useState<boolean>(true);
const [authUser, setAuthUser] = useState<User | null>(null);
const { data } = useGetUser(authUser?.uid || "");
const [authState, setAuthState] = useState("loading");
const [authToken, setAuthToken] = useState<string | null>(null);
const currentUser = data?.Users_by_pk;
// ...
const authStateChanged = async (user: User | null) => {
if (!user) {
setAuthUser(null);
setLoading(false);
setAuthState("out");
return;
}
const token = await user.getIdToken();
const idTokenResult = await user.getIdTokenResult();
const hasuraClaim = idTokenResult.claims["https://hasura.io/jwt/claims"];
if (hasuraClaim) {
setAuthState("in");
setAuthToken(token);
setAuthUser(user);
} else {
// Check if refresh is required.
const metadataRef = ref(
getDatabase(getApp()),
"metadata/" + user.uid + "/refreshTime"
);
onValue(metadataRef, async (data) => {
if (!data.exists) return;
const token = await user.getIdToken(true);
setAuthState("in");
setAuthUser(user);
setAuthToken(token);
});
}
};
useEffect(() => {
const unsubscribe = getAuth().onAuthStateChanged(authStateChanged);
return () => unsubscribe();
}, []);
const contextValue: FirebaseAuthContextProps = {
authUser,
authState,
authToken,
currentUser,
loading,
login,
registerUser,
loginWithGoogle,
loginWithMicrosoft,
};
return (
<FirebaseAuthContext.Provider value={contextValue}>
{children}
</FirebaseAuthContext.Provider>
);
};
export const useFirebaseAuth = () =>
useContext<FirebaseAuthContextProps>(FirebaseAuthContext);
This is the HOC:
export const withApollo =
({ ssr = true } = {}) =>
(PageComponent: NextComponentType<NextPageContext, any, {}>) => {
const WithApollo = ({
apolloClient,
apolloState,
...pageProps
}: {
apolloClient: ApolloClient<NormalizedCacheObject>;
apolloState: NormalizedCacheObject;
}) => {
let client;
if (apolloClient) {
// Happens on: getDataFromTree & next.js ssr
client = apolloClient;
} else {
// Happens on: next.js csr
// client = initApolloClient(apolloState, undefined);
client = initApolloClient(apolloState);
}
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
const initApolloClient = (initialState: NormalizedCacheObject) => {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === "undefined") {
return createApolloClient(initialState, "", "");
}
// Reuse client on the client-side
if (!globalApolloClient) {
globalApolloClient = createApolloClient(initialState, "", "");
}
return globalApolloClient;
};
I fixed it by using this whenever I have an update on the token:
import { setContext } from "#apollo/client/link/context";
const authStateChanged = async (user: User | null) => {
if (!user) {
setAuthUser(null);
setLoading(false);
setAuthState("out");
return;
}
setAuthUser(user);
const token = await user.getIdToken();
const idTokenResult = await user.getIdTokenResult();
const hasuraClaim = idTokenResult.claims["hasura"];
if (hasuraClaim) {
setAuthState("in");
setAuthToken(token);
// THIS IS THE FIX
setContext(() => ({
headers: { Authorization: `Bearer ${token}` },
}));
} else {
// Check if refresh is required.
const metadataRef = ref(
getDatabase(getApp()),
"metadata/" + user.uid + "/refreshTime"
);
onValue(metadataRef, async (data) => {
if (!data.exists) return;
const token = await user.getIdToken(true);
setAuthState("in");
setAuthToken(token);
// THIS IS THE FIX
setContext(() => ({
headers: { Authorization: `Bearer ${token}` },
}));
});
}
};
I have code:
import { ApolloClient, ApolloLink, InMemoryCache, from, HttpLink } from '#apollo/client';
import { WebSocketLink } from '#apollo/client/link/ws';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import { HOST, SSL_ENABLED } from './config';
const uri = (protocol: string) => `${protocol}://${HOST}/graphql`;
export default function apolloClient(token?: string | null): any {
const link = from([
new ApolloLink((operation, forward) => {
operation.setContext({
headers: {
...token && { Authorization: `Bearer ${token}` } || {},
},
});
return forward(operation);
}),
new WebSocketLink(new SubscriptionClient(uri(`ws${SSL_ENABLED ? 's' : ''}`), {
reconnect: true,
})),
new HttpLink({ uri: uri(`http${SSL_ENABLED ? 's' : ''}`) }),
]);
return new ApolloClient({
link,
cache: new InMemoryCache(),
});
}
And I am getting an warning You are calling concat on a terminating link, which will have no effect. Also any request is not send from my application to my api.
When I removed
new WebSocketLink(new SubscriptionClient(uri(`ws${SSL_ENABLED ? 's' : ''}`), {
reconnect: true,
})),
Everything is fine and app is working (sending request) without any warning.
What is wrong?
I am trying to configure subscriptions with Apollo 2 and NEXT.js. I can get the client to connect to the server and they are working in the GraphQL playground, so the bad configuration must be in the withData file, or the component that handles the subscription.
When inspecting the socket connection on the network panel in chrome, the subscription payload does not get added as a frame, like it does in the GraphQL playground.
withData:
import { ApolloLink, Observable } from 'apollo-link';
import { GRAPHQL_ENDPOINT, WS_PATH } from '../config/env';
import { ApolloClient } from 'apollo-client';
import { BatchHttpLink } from 'apollo-link-batch-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import { WebSocketLink } from 'apollo-link-ws';
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';
import { onError } from 'apollo-link-error';
import withApollo from 'next-with-apollo';
import { withClientState } from 'apollo-link-state';
function createClient({ headers }) {
const cache = new InMemoryCache();
const request = async (operation) => {
operation.setContext({
http: {
includeExtensions: true,
includeQuery: false
},
headers
});
};
const requestLink = new ApolloLink(
(operation, forward) => new Observable((observer) => {
let handle;
Promise.resolve(operation)
.then(oper => request(oper))
.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();
};
})
);
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
console.log({ graphQLErrors });
}
if (networkError) {
console.log('Logout user');
}
}),
requestLink,
// link,
withClientState({
defaults: {
isConnected: true
},
resolvers: {
Mutation: {
updateNetworkStatus: (_, { isConnected }, { cache }) => {
cache.writeData({ data: { isConnected } });
return null;
}
}
},
cache
}),
createPersistedQueryLink().concat(
new BatchHttpLink({
uri: GRAPHQL_ENDPOINT,
credentials: 'include'
}),
process.browser
? new WebSocketLink({
uri: WS_PATH,
options: {
reconnect: true
}
})
: null
)
]),
cache
});
}
export default withApollo(createClient);
Subscription component:
import { CONVERSATION_QUERY } from '../../constants/queries';
import { CONVERSATION_SUBSCRIPTION } from '../../constants/subscriptions';
import PropTypes from 'prop-types';
import { Query } from 'react-apollo';
const Conversation = props => (
<Query
{...props}
query={CONVERSATION_QUERY}
variables={{ input: { _id: props._id } }}
>
{(payload) => {
const more = () => payload.subscribeToMore({
document: CONVERSATION_SUBSCRIPTION,
variables: { input: { conversation: props._id } },
updateQuery: (prev, { subscriptionData }) => {
console.log({ subscriptionData });
if (!subscriptionData.data.messageSent) return prev;
const data = subscriptionData;
console.log({ data });
return Object.assign({}, prev, {});
},
onError(error) {
console.log(error);
},
onSubscriptionData: (data) => {
console.log('onSubscriptionData ', data);
}
});
return props.children({ ...payload, more });
}}
</Query>
);
Conversation.propTypes = {
children: PropTypes.func.isRequired
};
export default Conversation;
The subscription that has been tested in the GraphQL playground:
import gql from 'graphql-tag';
export const CONVERSATION_SUBSCRIPTION = gql`
subscription messageSent($input: messageSentInput) {
messageSent(input: $input) {
_id
users {
_id
profile {
firstName
lastName
jobTitle
company
picture
}
}
messages {
_id
body
createdAt
read
sender {
_id
profile {
firstName
lastName
jobTitle
company
picture
}
}
}
}
}
`;
The more function is then executed in componentDidMount:
componentDidMount() {
this.props.subscribeToMore();
}
The result in the console from the log in updateQuery is:
{"data":{"messageSent":null}}
I hadn't configured my withData file properly. You need to use split from the apollo-link package to let Apollo determine if the request should be handled with http or ws. Here is my working configuration file.
import { ApolloLink, Observable } from 'apollo-link';
import { ApolloClient } from 'apollo-client';
import { BatchHttpLink } from 'apollo-link-batch-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { WebSocketLink } from 'apollo-link-ws';
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';
import { getMainDefinition } from 'apollo-utilities';
import { onError } from 'apollo-link-error';
import { split } from 'apollo-link';
import withApollo from 'next-with-apollo';
import { withClientState } from 'apollo-link-state';
import { GRAPHQL_ENDPOINT, WS_PATH } from '../config/env';
function createClient({ headers }) {
const cache = new InMemoryCache();
const request = async (operation) => {
operation.setContext({
http: {
includeExtensions: true,
includeQuery: false
},
headers
});
};
const requestLink = new ApolloLink(
(operation, forward) => new Observable((observer) => {
let handle;
Promise.resolve(operation)
.then(oper => request(oper))
.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();
};
})
);
const httpLink = new BatchHttpLink({
uri: GRAPHQL_ENDPOINT
});
// Make sure the wsLink is only created on the browser. The server doesn't have a native implemention for websockets
const wsLink = process.browser
? new WebSocketLink({
uri: WS_PATH,
options: {
reconnect: true
}
})
: () => {
console.log('SSR');
};
// Let Apollo figure out if the request is over ws or http
const terminatingLink = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return (
kind === 'OperationDefinition'
&& operation === 'subscription'
&& process.browser
);
},
wsLink,
httpLink
);
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
console.error({ graphQLErrors });
}
if (networkError) {
console.error({ networkError});
}
}),
requestLink,
// link,
withClientState({
defaults: {
isConnected: true
},
resolvers: {
Mutation: {
updateNetworkStatus: (_, { isConnected }, { cache }) => {
cache.writeData({ data: { isConnected } });
return null;
}
}
},
cache
}),
// Push the links into the Apollo client
createPersistedQueryLink().concat(
// New config
terminatingLink
// Old config
// new BatchHttpLink({
// uri: GRAPHQL_ENDPOINT,
// credentials: 'include'
// })
)
]),
cache
});
}
export default withApollo(createClient);
Im using Apollo Client, Graphcool and React. I have a working login form but I need the UI to update when the user is logged in, and I need this to happen in different components.
It seems apollo-link-state is the solution for this. My code below seems to work but Im getting this error:
Missing field CurrentUserIsLoggedIn in {} in writeToStore.js
My Apollo Client setup:
import React from 'react';
import ReactDOM from 'react-dom';
// Apollo
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, split } from 'apollo-link';
import { withClientState } from 'apollo-link-state';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
// Components
import LoginTest from './components/App/LoginTest';
const wsLink = new WebSocketLink({
uri: `wss://subscriptions.graph.cool/v1/XXX`,
options: {
reconnect: true,
},
});
// __SIMPLE_API_ENDPOINT__ looks like: 'https://api.graph.cool/simple/v1/__SERVICE_ID__'
const httpLink = new HttpLink({
uri: 'https://api.graph.cool/simple/v1/XXX',
});
// auth
const middlewareAuthLink = new ApolloLink((operation, forward) => {
const token = localStorage.getItem('auth-token');
const authorizationHeader = token ? `Bearer ${token}` : null;
operation.setContext({
headers: {
authorization: authorizationHeader,
},
});
return forward(operation);
});
const cache = new InMemoryCache();
const defaultState = {
CurrentUserIsLoggedIn: {
__typename: 'CurrentUserIsLoggedIn',
value: false,
},
};
const stateLink = withClientState({
cache,
defaults: defaultState,
resolvers: {
Mutation: {
CurrentUserIsLoggedIn: (_, args) => {
const data = {
CurrentUserIsLoggedIn: {
__typename: 'CurrentUserIsLoggedIn',
value: args.value,
},
};
cache.writeData({ data });
},
},
},
});
const client = new ApolloClient({
cache,
link: ApolloLink.from([
stateLink,
middlewareAuthLink,
split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
),
]),
});
ReactDOM.render(
<ApolloProvider client={client}>
<LoginTest />
</ApolloProvider>,
document.getElementById('root'),
);
LoginTest.js:
import React from 'react';
import { graphql, compose } from 'react-apollo';
import gql from 'graphql-tag';
import App from './App';
const LoginTest = props => {
if (props.LoginServerQuery.loading) return <p>Loading...</p>;
// If the server tells us the user is logged in
if (props.LoginServerQuery.loggedInUser) {
// Then set the local logged in state to true
props.CurrentUserIsLoggedInMutation({
variables: {
value: true,
},
});
}
return <App />;
};
const CurrentUserIsLoggedInMutation = gql`
mutation CurrentUserIsLoggedInMutation($value: Boolean) {
CurrentUserIsLoggedIn(value: $value) #client {
value
}
}
`;
const LoginServerQuery = gql`
query LoginServerQuery {
loggedInUser {
id
}
}
`;
const LoginTestQuery = compose(
graphql(LoginServerQuery, { name: 'LoginServerQuery' }),
graphql(CurrentUserIsLoggedInMutation, {
name: 'CurrentUserIsLoggedInMutation',
}),
)(LoginTest);
export default LoginTestQuery;
At the moment, apollo-link-state requires you to return any result in your resolver function. It can be null too. This might be changed in the future.
const stateLink = withClientState({
cache,
defaults: defaultState,
resolvers: {
Mutation: {
CurrentUserIsLoggedIn: (_, args) => {
const data = {
CurrentUserIsLoggedIn: {
__typename: 'CurrentUserIsLoggedIn',
value: args.value,
},
};
cache.writeData({ data });
return data;
},
},
},
try adding a return statement in your mutation. Similar problem occured here with different function: apollo-link-state cache.writedata results in Missing field warning