WebSocket connection to 'ws://localhost:4444/subscriptions' failed: WebSocket is closed before the connection is established - apollo

OS: Windows 10 Pro
express: 4.17.1
apollo-server-express: 2.9.13
apollo-client: 2.6.4
apollo-link-context: 1.0.18
apollo-link-http: 1.5.15
apollo-link-ws: 1.0.18
So, I'm in the process of migrating from graphql-yoga to apollo-server 2 and am experiencing ws connection issues (See image). What am I overlooking?
My code is as follows:
withData.js
const endpoint = `http://localhost:4444/graphql`;
const endpointWS = `ws://localhost:4444/subscriptions`;
const httpLink = createHttpLink({
uri: process.env.NODE_ENV === 'development' ? endpoint : prodEndpoint,
credentials: 'include',
});
const wsLink = process.browser ? new WebSocketLink({
uri: process.env.NODE_ENV === 'development' ? endpointWS : prodEndpointWS,
options: {
reconnect: true,
timeout: 3000,
}
}) : null;
const authLink = setContext(() => {
return {
headers: {
...headers,
}
}
});
const link = process.browser ? split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
) : httpLink;
index.js
const PORT = '4444';
const path2 = '/graphql';
const createServer = require('./createServer');
const server = createServer();
const app = express();
app.use(cookieParser());
server.applyMiddleware({
app,
path: path2,
cors: {
credentials: true,
origin: process.env.FRONTEND_URL,
},
});
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.listen(PORT, err => {
if (err) throw err
console.log(`πŸš€ Server ready at http://localhost:${PORT}${server.graphqlPath}`)
console.log(`πŸš€ Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`)
});
createServer.js
const Mutation = require('./resolvers/Mutation');
const Query = require('./resolvers/Query');
const Subscription = require('./resolvers/Subscription');
const db = require('./db');
const typeDefsFile = importSchema(__dirname.concat('/schema.graphql'));
const typeDefs = gql(typeDefsFile);
function createServer() {
return new ApolloServer({
typeDefs,
resolvers: {
Mutation,
Query,
Subscription,
},
cors: {
credentials: true,
origin: process.env.FRONTEND_URL,
},
subscriptions: {
keepAlive: 1000,
path: '/subscriptions',
},
playground: process.env.NODE_ENV === 'production' ? false : '/',
tracing: true,
introspection: true,
context: req => ({ ...req, db }),
});
}
module.exports = createServer;
db.js
const { Prisma } = require('prisma-binding');
const db = new Prisma({
typeDefs: __dirname + "/schema_prep.graphql",
endpoint: process.env.PRISMA_ENDPOINT,
secret: process.env.PRISMA_SECRET,
debug: false,
});
module.exports = db;
Subscriptions.js
const Subscription = {
item: {
subscribe: async (parent, args, ctx, info) => {
const itemResult = await ctx.db.subscription
.item({
where: {
mutation_in: ['CREATED', 'UPDATED'],
},
},
info
);
return itemResult;
},
},
itemDeleted: {
subscribe: (parent, args, ctx, info) => {
const selectionSet = `{ previousValues { id, userIdentity } }`
return ctx.db.subscription.item(
{
where: {
mutation_in: ['DELETED'],
},
},
selectionSet,
);
},
resolve: (payload, args, context, info) => {
return payload ? payload.item.previousValues : payload
},
},
};
module.exports = Subscription;

I resolved this issue by changing the response and request attributes of the context in my query and mutation resolvers from ctx.response and ctx.request to ctx.res and ctx.req respectively.

Related

Graphql Apollo setting cookies is dont working

I'm trying to set cookies in apollo client.
I'm setting the response cookie which works fine, also working on graphql playground with setting "request.credentials": "same-origin", or "include" and it stores cookies successfully.
My problem starts when I'm trying to store cookies from FE app (React, Apollo client). When i put the credentials: "same-origin" the app is working but the cookies are not saved. And when i use "include" the queries are cors blocked.
FRONTEND
const RouterComponent: FC = () => {
const cache = useMemo(() => {
return new InMemoryCache();
}, []);
const client = useMemo(() => {
const token = localStorage.getItem('token') || 'token';
const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql',
credentials: 'include',
headers: {
authorization: token ? `Bearer ${token}` : '',
},
});
const link = from([
httpLink,
createUploadLink({
uri: 'http://localhost:4000/graphql',
}),
]);
return new ApolloClient({
link,
cache,
typeDefs,
});
}, [cache]);
return (
<ApolloProvider client={client}>
<Switch>
<Route exact path="/" component={MainPage} />;
<Route exact path="/:category" component={Category} />;
</Switch>
</ApolloProvider>
);
};
QUERY
games: (_: any, args: any, {req, res}: any) => {
const refresh_token = sign({userId: "123"}, "secret", {expiresIn: "7d"})
const access_token = sign({userId: "123"}, "secret", {expiresIn: "15min"})
res.cookie('refresh-token', refresh_token, {expire: 60 * 60 * 24 * 7}) // 7 days
res.cookie('access-token', access_token, {expire: 60 * 15}) // 15 mins
return Game.find()
}
SERVER.TS
const { ApolloServer } = require( 'apollo-server-express');
const {typeDefs} = require( './typeDefs');
const {resolvers} = require( './resolvers');
const express = require( 'express');
const cors = require( 'cors');
const path = require( 'path');
const bodyParser = require( 'body-parser');
const mongoose = require("mongoose");
const dotenv = require('dotenv');
const MONGO_CONNECTION = ""
const app = express();
export default (async function () {
try {
await mongoose.connect(MONGO_CONNECTION, {
useNewUrlParser: true,
useUnifiedTopology: true})
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }: any) => {
return {
req,
res
};
},
});
const corsConfig = {
origin: '*',
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization'],
};
dotenv.config();
app.use(function (req: any, res: any, next: any) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.setHeader('Access-Control-Allow-Credentials', true);
next();
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use('/images', express.static(path.join(__dirname, 'images')));
const dir = path.join(process.cwd(), 'images');
app.use(express.static(dir));
app.use(express.static('public'))
app.use(express.static('files'))
server.applyMiddleware({ app, cors: corsConfig });
app.listen({ port: 4000 }, () =>
console.log(`πŸš€ Server ready at http://localhost:4000${server.graphqlPath}`)
)
} catch (err) {
console.error(err);
}
})();
thank you for any help

Google cloud speech to text long audio response

I am trying to convert audio to text, but getting result as,
LongRunningRecognizeResponse { results: [] }
Here is my function,
const audio = {
uri: `gs://${BUCKET}/${fileName}`,
};
const request = {
config: {
encoding: 'LINEAR16',
sampleRateHertz: 16000,
languageCode: 'en-US',
},
audio: audio,
};
client.longRunningRecognize(request)
.then(responses => {
const [operation, initialApiResponse] = responses;
operation.on('complete', (result, metadata, finalApiResponse) => {
console.log('complete', result) // LongRunningRecognizeResponse { results: [] }
});
operation.on('progress', (metadata, apiResponse) => {
console.log('progress', apiResponse)
});
operation.on('error', err => {
throw (err);
});
})
const transcribeLong = async (url, channels) => {
const nameExtract = url.split('/');
const fileName = nameExtract[nameExtract.length - 1];
let localfile;
try {
localfile = await download(url);
fs.writeFileSync(fileName, localfile);
await storage.bucket(BUCKET).upload(fileName);
const audio = {
uri: `gs://${BUCKET}/${fileName}`,
};
const request = {
config: {
languageCode: `en-US`,
alternativeLanguageCodes: [`es-MX`, `en-US`],
encoding: 'LINEAR16',
sampleRateHertz: 8000,
audioChannelCount: channels || 2, //changed from 2
enableSeparateRecognitionPerChannel: true,
},
audio,
};
const [operation] = await client.longRunningRecognize(request);
const [response] = await operation.promise();
return response
} catch (error) {
console.error(error.toString());
}
};
Issues was with wrong channels as argument in request.

What is the correct way to connect to a graphql-yoga server via apollo for subscriptions?

apollo-client: 2.6.3
apollo-link-http: 1.5.15
apollo-link-ws: 1.0.18
subscriptions-transport-ws: 0.9.16
So I have a front-end (node) server which connects to a back-end (graphql-yoga) server as follows:
const httpLink = createHttpLink({
uri: 'https://localhost:4444',
credentials: 'include',
});
const wsLink = process.browser ? new WebSocketLink({
uri: 'wss://eu1.prisma.sh/MyID/MyProject/dev',
options: {
reconnect: true,
timeout: 3000,
}
}) : null;
const authLink = setContext(() => {
return {
headers: {
...headers,
}
}
});
const link = process.browser ? split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
) : httpLink;
const client = new ApolloClient({
link: authLink.concat(link),
...
});
Query and mutation resolvers are being triggered correctly, but the subscription resolver is not being triggered. I'm assuming that's because I'm not connecting directly to graphql-yoga.
But if I change the WebSocketLink uri to ws://localhost:4000 this causes an:
failed: Error during WebSocket handshake: Unexpected response code: 200
error message to be issued.
The graphql-yoga server itself makes a subscription connection to prisma as follows:
const options = {
subscriptions: 'https://eu1.prisma.sh/MyID/MyProject/dev',
};
// start it!!
server.start(options, ({ port }) =>
console.log(`Server is now running on port http://localhost:${port}`),
);
And my subscription resolver is as follows:
const Subscription = {
order: {
async subscribe(parent, args, ctx, info) {
return ctx.db.subscription.order(
{
where: {
mutation_in: ['CREATED', 'UPDATED'],
},
},
info
).catch(handleSubmitError);
},
}
};
If I run the following subscription in playground:
subscription {
order {
mutation
node {
id
total
createdAt
items {
id
title
price
description
mainDescription
quantity
image
}
}
}
}
and trigger an order mutation via my site, the correct
data resultset is shown:
But the subscription resolver is not being actioned from the subscription call:
const USER_ORDERS_SUBSCRIPTION = gql`
subscription {
order {
mutation
node {
id
total
createdAt
items {
id
title
price
description
mainDescription
quantity
image
}
}
}
}
`;
<Query query={USER_ORDERS_QUERY}>
{({ subscribeToMore, data: { orders }, loading, error }) => {
if (loading) return <p>loading...</p>;
if (error) return <Error erorr={error} />;
return (
<div>
<h2>You have {orders.length === 0 ? 'no' : orders.length} order{orders.length > 1 || orders.length === 0 ? 's' : ''}.</h2>
<OrderListItem
orders={orders}
urlReferer={urlReferer}
subscribeToNewOrders={() =>
subscribeToMore({
document: USER_ORDERS_SUBSCRIPTION,
variables: {},
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
const newOrdertem = subscriptionData.data.order.node;
return Object.assign({}, prev, {
orders: [newOrdertem, ...prev.orders]
});
}
})
}
/>
</div>
);
}}
</Query>
How do I resolve this?
So it turns out the solution was to:
const options = {
subscriptions: '/subscriptions',
};
and then call the server as:
const wsLink = process.browser ? new WebSocketLink({
uri: 'ws://localhost:4444/subscriptions',
})

Apollo link state only works when redundant query is defined?

I have Apollo link state working:
import React from 'react';
import ReactDOM from 'react-dom';
import { HttpLink, InMemoryCache, ApolloClient } from 'apollo-client-preset';
import { WebSocketLink } from 'apollo-link-ws';
import { ApolloLink, split } from 'apollo-link';
import { getMainDefinition } from 'apollo-utilities';
import { AUTH_TOKEN } from './constant';
import RootContainer from './components/RootContainer';
import { ApolloProvider } from 'react-apollo';
import { withClientState } from 'apollo-link-state';
import { gql } from 'apollo-boost';
const httpLink = new HttpLink({ uri: 'http://localhost:4000' });
const middlewareLink = new ApolloLink((operation, forward) => {
const tokenValue = localStorage.getItem(AUTH_TOKEN);
operation.setContext({
headers: {
Authorization: tokenValue ? `Bearer ${tokenValue}` : '',
},
});
return forward(operation);
});
const httpLinkAuth = middlewareLink.concat(httpLink);
const wsLink = new WebSocketLink({
uri: `ws://localhost:4000`,
options: {
reconnect: true,
connectionParams: {
Authorization: `Bearer ${localStorage.getItem(AUTH_TOKEN)}`,
},
},
});
const link = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLinkAuth,
);
const cache = new InMemoryCache();
const stateLink = withClientState({
cache,
defaults: {
groupMenuStatus: {
__typename: 'GroupMenuStatus',
isOpen: false,
},
},
resolvers: {
Mutation: {
updateGroupMenuStatus: (_, { isOpen }, { cache }) => {
const data = {
groupMenuStatus: {
__typename: 'GroupMenuStatus',
isOpen,
},
};
cache.writeData({ data });
return null;
},
},
Query: {
groupMenuStatus: async (_, args, { cache }) => {
const query = gql`
query groupMenuStatus {
groupMenuStatus #client {
isOpen
}
}
`;
const res = cache.readQuery({ query });
return res.groupMenuStatus;
},
},
},
});
const client = new ApolloClient({
link: ApolloLink.from([stateLink, link]),
cache,
connectToDevTools: true,
});
const token = localStorage.getItem(AUTH_TOKEN);
ReactDOM.render(
<ApolloProvider client={client}>
<RootContainer token={token} />
</ApolloProvider>,
document.getElementById('root'),
);
However is most of the examples online they havn't needed to define a query resolver. If I remove the code below then the query from the front-end will always return the default state, the mutation seems to have no effect:
Query: {
groupMenuStatus: async (_, args, { cache }) => {
const query = gql`
query groupMenuStatus {
groupMenuStatus #client {
isOpen
}
}
`;
const res = cache.readQuery({ query });
return res.groupMenuStatus;
},
},
According to the official docs on https://www.apollographql.com/docs/link/links/state.html
Query resolvers are only called on a cache miss. Since the first time you call the query will be a cache miss, you should return any default state from your resolver function.
So, if you define a default, your query resolver will never be called. (You got the definition right, it is called Query indeed)
If you do not declare a default, you might use the query resolver to write something on cache (and then the query resolver will not be called anymore), or you can just return some value, and the resolver will be called every time.
I use it, for example to get user geolocation on the first call, that's my default value now, and the resolver is never called again.
Check my use case:
Query: {
async smePosition(_: any, {}: any, { cache }: IContext): Bluebird<any> {
return new Bluebird((resolve: any, reject: any): void => {
window.navigator.geolocation.getCurrentPosition(
({coords: {latitude: lat, longitude: lng}}) => {
const data = {
smePosition: {
__typename: 'SMe',
position: {lat, lng , __typename: 'IPosition'},
},
}
cache.writeData({ data })
resolve()
},
)
})
},
},
In this case, I don't define a defaults value for 'smePosition'

Apollo: Update React Props on Subscription Update?

Looking at the Apollo docs example code for subscriptions, I am not yet seeing how to update the React props with the subscription results.
From http://dev.apollodata.com/react/subscriptions.html:
Here is a regular query:
import { CommentsPage } from './comments-page.js';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
const COMMENT_QUERY = gql`
query Comment($repoName: String!) {
entry(repoFullName: $repoName) {
comments {
id
content
}
}
}
`;
const withData = graphql(COMMENT_QUERY, {
name: 'comments',
options: ({ params }) => ({
variables: {
repoName: `${params.org}/${params.repoName}`
},
})
});
export const CommentsPageWithData = withData(CommentsPage);
Now, let’s add the subscription.
Note that this sample code appears to leave out this part of the props code for usual queries - from http://dev.apollodata.com/react/queries.html:
props: ({ ownProps, data: { loading, currentUser, refetch } }) => ({
userLoading: loading,
user: currentUser,
refetchUser: refetch,
}),
...which AFAIK is the correct way to update the data props on my React component and trigger a page refresh.
Here is the complete subscription code sample from http://dev.apollodata.com/react/subscriptions.html:
const withData = graphql(COMMENT_QUERY, {
name: 'comments',
options: ({ params }) => ({
variables: {
repoName: `${params.org}/${params.repoName}`
},
}),
props: props => {
return {
subscribeToNewComments: params => {
return props.comments.subscribeToMore({
document: COMMENTS_SUBSCRIPTION,
variables: {
repoName: params.repoFullName,
},
updateQuery: (prev, {subscriptionData}) => {
if (!subscriptionData.data) {
return prev;
}
const newFeedItem = subscriptionData.data.commentAdded;
return Object.assign({}, prev, {
entry: {
comments: [newFeedItem, ...prev.entry.comments]
}
});
}
});
}
};
},
});
How do I get the code shown here, to update the data props on my React component and trigger a page refresh, when the results come in from the non-subscription query COMMENT_QUERY?
Thanks to #neophi on the Apollo Slack for this answer!
const withDataAndSubscription = graphql(GETIMS_QUERY, {
options({toID}) {
console.log(GETIMS_QUERY);
const fromID = Meteor.userId();
return {
fetchPolicy: 'cache-and-network',
variables: {fromID: `${fromID}`, toID: `${toID}`}
};
}
,
props: props => {
return {
loading: props.data.loading,
instant_message: props.data.instant_message,
subscribeToMore: props.data.subscribeToMore,
subscribeToNewIMs: params => {
const fromID = Meteor.userId();
const toID = params.toID;
return props.data.subscribeToMore({
document: IM_SUBSCRIPTION_QUERY,
variables: {fromID: `${fromID}`, toID: `${toID}`},
updateQuery: (previousResult, {subscriptionData}) => {
if (!subscriptionData.data) {
return previousResult;
}
const newMsg = subscriptionData.data.createIM;
return update(previousResult, {
instant_message: {
$push: [newMsg],
},
});
}
});
}
};
},
})
;