React Component subscription detect GraphQL server not responding - react-apollo

I have a device that sends a heartbeat to my Apollo GraphQL server every 30 seconds. I have a React component that subscribes to hbReceived, and displays the most recent heartbeat time. This works fine.
BUT,
If my GraphQL server is down, I want to handle that error. I expect these errors to be returned in the useSubscription() hook's return value error.networkError property. Instead, I just see client.ts:545 WebSocket connection to 'ws://localhost:4000/graphql' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED in the console, and the error key remains undefined in the useSubscripiton response.
schema.graphql:
type Heartbeat {
id: ID!
heartbeatTime: DateISO8601!
deviceId: ID!
}
type Subscription {
heartbeatReceived(chargePointInstallId: ID!) : Heartbeat
hbReceived(deviceId: ID!): Heartbeat
}
I made a simple version of my app in create-react-app to illustrate this problem:
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Heartbeat from './Heartbeat';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { WebSocketLink } from 'apollo-link-ws';
const link = new WebSocketLink({
uri: 'ws://localhost:4000/graphql',
options: {
reconnect: true,
}
});
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
ReactDOM.render(
<React.StrictMode>
<ApolloProvider client = {client}>
<Heartbeat deviceId={1} />
</ApolloProvider>
</React.StrictMode>,
document.getElementById('root')
);
src/Heartbeat.js
import React from 'react';
import './App.css';
import { useSubscription } from 'react-apollo';
import gql from 'graphql-tag';
export default function Heartbeat(props) {
const { loading, data, error} = useSubscription(
gql`
subscription hbReceived($deviceId: ID!) {
hbReceived(deviceId: $deviceId) {
heartbeatTime
}
}`,
{ variables:{ deviceId: `${props.deviceId}`}}
);
let mostRecentHeartbeatTimeStr;
if (error) {
console.log('Error rerturned:');
console.log(error);
mostRecentHeartbeatTimeStr = 'See console for error';
} else if (loading) {
mostRecentHeartbeatTimeStr = 'Waiting for first heartbeat';
} else {
const mostRecentHeartbeatDate = new Date(data.heartbeatReceived.heartbeatTime);
mostRecentHeartbeatTimeStr = 'Last Heartbeat: ' + mostRecentHeartbeatDate.toLocaleString('en-AU',{})
}
return (<div className='device'>
<div className='device-heading'>
Device heartbeat:
</div>
<div className='device-row'>
{mostRecentHeartbeatTimeStr}
</div>
</div>)
}
This is what I see in the console when the graphQL server is down:
WebSocket connection to 'ws://localhost:4000/graphql' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED
How do I catch that WebSocket ERR_CONNECTION_REFUSED error and display some nice message to my user?
What I have tried
I have put a connectionCallback in the options for the new WebSocketLink constructor
parameters(url,{ options: { connectionCallback(error) => { console.log(error);} });
I have tried composing a link with an onError from import { onError } from "apollo-link-error"; in it, and put { errorPolicy: 'all' } in my useSubscription call.
The documentation also says the default behaviour is that network errors are treated like GraphQL errors.
I am stuck! Any help appreciated!

Related

GraphQL Shield Type Validation Performed After Resolver Executed, Not Before

I'm using Apollo GraphQL Server with GraphQL shield. When I implement rule on GraphQL type, the Shield authorization performed after the resolver executed. Is that how GraphQL Shield works or is it caused by misconfiguration?
Here's the server configuration
import { ApolloServer } from 'apollo-server-express';
import { ApolloServerPluginDrainHttpServer } from 'apollo-server-core';
import http from 'http';
import { applyMiddleware } from 'graphql-middleware';
import schema from './schema';
import shield from './shield';
(async () => {
const httpServer = http.createServer(app);
const server = new ApolloServer({
schema: applyMiddleware(schema, shield),
csrfPrevention: true,
cache: 'bounded',
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })]
});
await server.start();
server.applyMiddleware({ app });
await new Promise<void>((resolve) =>
httpServer.listen({ port: 80 }, resolve)
);
console.log(`Server ready at http://localhost:80${server.graphqlPath}`);
})(schema);
Thankyou

Can't get Firebase emulators to work with AngularFire 7

Good talk yesterday at the Firebase Summit about emulators! I was able to get the Functions emulator to work with AngularFire 6. I can't get the Firestore emulator or the Functions emulator to work with AngularFire 7. Here's my app.module.ts:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { AppComponent } from './app.component';
import { initializeApp,provideFirebaseApp } from '#angular/fire/app';
import { environment } from '../environments/environment';
import { provideFirestore,getFirestore } from '#angular/fire/firestore';
import { USE_EMULATOR as USE_FIRESTORE_EMULATOR } from '#angular/fire/compat/functions';
import { USE_EMULATOR as USE_FUNCTIONS_EMULATOR } from '#angular/fire/compat/functions';
import { FormsModule } from '#angular/forms';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideFirestore(() => getFirestore()),
],
providers: [
{ provide: USE_FIRESTORE_EMULATOR, useValue: environment.useEmulators ? ['localhost', 8080] : undefined },
{ provide: USE_FUNCTIONS_EMULATOR, useValue: environment.useEmulators ? ['localhost', 5001] : undefined }
],
bootstrap: [AppComponent]
})
export class AppModule { }
There's a smell here. I'm initializing Firebase using AngularFire 7 but I'm importing the emulator from AngularFire 6.1.0. Firebase can be initialized with AngularFire 6 or AngularFire 7 but not both, i.e., you can't mix AngularFire 6 and 7.
How do I import the emulators without using AngularFire 6?
In environments.ts I made a property useEmulators:
export const environment = {
firebase: {
projectId: 'my-awesome-project',
appId: '1:234567890:web',
storageBucket: 'my-awesome-project.appspot.com',
apiKey: 'ABCdef',
authDomain: 'my-awesome-project.firebaseapp.com',
messagingSenderId: '0987654321',
},
production: false,
useEmulators: true
};
My Cloud Function runs great in the cloud but doesn't run in the emulators.
Each time I make a change in a Cloud Function, deploy the update to the cloud, wait a minute for the deploy to propagate, test my function, and wait for the logs to show up in the Firebase Console is ten minutes. I'm looking forward to using the emulators to speed up this development cycle.
Here's the rest of my code. I doubt there's anything wrong with these files.
The Cloud Function triggers from writing a message to Firestore, changes the message to uppercase, and writes the uppercase message to a new field in the document.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.uppercaseMe = functions.firestore.document('Triggers/{docId}').onCreate((snap, context) => {
var original = snap.data().message;
functions.logger.log('Uppercasing', context.params.docId, original);
var uppercase = original.toUpperCase();
return snap.ref.set({ uppercase }, { merge: true });
});
The HTML view has a form for submitting a message. It displays the data that was written to Firestore and then displays the results from the Cloud Function.
<form (ngSubmit)="triggerMe()">
<input type="text" [(ngModel)]="message" name="message" placeholder="Message" required>
<button type="submit" value="Submit">Submit</button>
</form>
<div>{{ data$ }}</div>
<div>{{ upperca$e }}</div>
The app.component.ts controller writes the message to Firestore, reads back the message from Firestore, then sets up a document listener to wait for the cloud function to write a new field to the document.
import { Component } from '#angular/core';
import { Firestore, doc, getDoc, collection, addDoc, onSnapshot } from '#angular/fire/firestore';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
data$: any;
docSnap: any;
message: string | null = null;
upperca$e: string | null = null;
unsubMessage$: any;
constructor(public firestore: Firestore) {}
async triggerMe() {
try {
// write to Firestore
const docRef = await addDoc(collection(this.firestore, 'Triggers'), {
message: this.message,
});
this.message = null; // clear form fields
// read from Firestore
this.docSnap = await getDoc(doc(this.firestore, 'Triggers', docRef.id));
this.data$ = this.docSnap.data().message;
// document listener
this.unsubMessage$ = onSnapshot(doc(this.firestore, 'Triggers', docRef.id), (snapshot: any) => {
this.upperca$e = snapshot.data().uppercase;
});
} catch (error) {
console.error(error);
}
}
}
Firebase emulators work independently of Angular or other apps! I reread the documentation and learned that you just spin up the emulators,
firebase emulators:start
open your browser to http://localhost:4000, and you can write data in Firestore and then see the results of your function appear in Firestore. You can also read the logs. This only works with triggerable functions, not with callable functions.
Amazing what you can learn by reading the documentation. :-)

Vue3 composition API useMutation appolo response

I am building a Vue3 app with the composition API. Installed it correctly. In my template is a button (just for testing purpose):
<button #click="getToken"> get token </button>
When I click this button I can see in the chrome developer tools (network tab) that this Graphql mutation is executed and is respponding correctly.
<script setup>
import { useQuery, useMutation } from "#vue/apollo-composable";
import gql from "graphql-tag";
const { mutate: getToken} = useMutation(gql`
mutation login {
login(
authProfileUuid: "1234567"
username: "email#company.com"
password: "secret"
) {
jwtToken
}
}
`,)
</script>
My problem is that i just can't figure out how to get access to the JSON data in the response. Its my first time using apollo and can't find it in the docs. Can anyone help me ?
Martijn D.
Hey you should change your code to this:
<script setup>
import { useQuery, useMutation } from "#vue/apollo-composable";
import gql from "graphql-tag";
const { mutate: getToken, onDone } = useMutation(gql`
mutation login {
login(
authProfileUuid: "1234567"
username: "email#company.com"
password: "secret"
) {
jwtToken
}
}
`);
onDone((res) => {
console.log(res);
});
</script>

WebSockets on specific route in Nest.js

I'd like to create specific API route which will be used only WebSocket (/api/events) but in all examples of implementing WebSockets on Nest.js I stumbled upon module is imported in AppModule and client is emitting events toward the root URL, which I can't do because I have this middleware;
frontend.middleware.ts
import { Request, Response } from 'express';
import { AppModule } from '../../app.module';
export function FrontendMiddleware(
req: Request,
res: Response,
next: Function,
) {
const { baseUrl } = req;
if (baseUrl.indexOf('/api') === 0) {
next();
} else {
res.sendFile('index.html', { root: AppModule.getStaticAssetsRootPath() });
}
}
Here is the EventGateway and EventModule:
event.gateway.ts
import {
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
WsResponse,
} from '#nestjs/websockets';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Client, Server } from 'socket.io';
#WebSocketGateway({ namespace: 'events' })
export class EventGateway {
#WebSocketServer()
server: Server;
#SubscribeMessage('events')
findAll(client: Client, data: any): Observable<WsResponse<number>> {
return from([1, 2, 3]).pipe(map(item => ({ event: 'events', data: item })));
}
#SubscribeMessage('identity')
async identity(client: Client, data: number): Promise<number> {
return data;
}
}
event.module.ts
import { Module } from '#nestjs/common';
import { EventGateway } from './event.gateway';
#Module({
components: [EventGateway],
})
export class EventModule {}
Is there a way to create controller which will allow server-client communication via /api/events?
Yes, it is possible to create the WebsocketGateway on another path. You can just use the options of the WebsocketGateway to configure the underlying IO-Connection:
E.g:
import {
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
WsResponse,
} from '#nestjs/websockets';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Client, Server } from 'socket.io';
#WebSocketGateway({ path: '/api/events', namespace: 'events' })
export class EventGateway {
#WebSocketServer()
server: Server;
#SubscribeMessage('events')
findAll(client: Client, data: any): Observable<WsResponse<number>> {
return from([1, 2, 3]).pipe(map(item => ({ event: 'events', data: item })));
}
#SubscribeMessage('identity')
async identity(client: Client, data: number): Promise<number> {
return data;
}
}
This will start the IO-Connection on http://localhost/api/events
Remember to change the connection-path also in your client. It won't be the default /socket.io path anymore, it will be /api/events in your sample.
Websocket is running on the server, not an endpoint. Therefore you cannot have it listen to requests under a specific route, rather just a port, which for Nest's default configuration happens to be the same as the HTTP one.
You could use a reverse proxy like Nginx to redirect the requests towards /api/events facing the Websocket server and also handle the redirection to index.html without changing even the Websocket server's port. Then you would not need the FrontendMiddleware class at all. It is also better since the application does not take the burden of managing request redirections.

Apollo with Graphene- Graphql mutations 400 error (not Query, not Mutation django-graphql-jwt, Relay works)

I am using Apollo v. 2.1, Graphene-django v.2.
Short question: Some mutations (not all) don't work in Apollo, but they do in Relay. Apolloerror?
Apollo Mutations throw error 400. Network Error: Response not successful. POST - Bad Request. Queries and Django-graphene-JWT Mutations (tokenAuth, etc) returns correct result.
import gql from "graphql-tag";
import { ApolloProvider } from 'react-apollo'
import { ApolloClient } from 'apollo-boost'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloLink } from 'apollo-client-preset'
import { onError } from "apollo-link-error"
const token = cookies.get('AUTH_TOKEN')
const authorizationHeader = token ? `JWT ${token}` : null
const httpLink = new HttpLink({
onError: (e) => { console.log(e.graphQLErrors) },
credentials: 'same-origin',
headers: {
'X-CSRFToken': window.csrf, //CSRF-token from Django-template
Authorization: authorizationHeader, // auth-token from cookie
}
})
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
connectToDevTools: true,
})
const COUNTRY_MUTATION = gql`
mutation ($country: String) {
createCountry(
country: "Liberland",
){
country {
name
}
}
}
`
client.mutate({
mutation: COUNTRY_MUTATION, variables: {country:"Liberland"}}).then((resp) => {
console.log(resp)
}).catch((error) => {
console.log(error)
});
Django returns no error. Apollo (console) returns error. It is possible to make mutations inside GraphIQL/Insomnia without errors.
I have tried turning on/off Django-url csrf_excempt without change. If csrf_exempt off and I add no X-CSRFToken to index.js I get a 403 error. GraphIql is turned off. Developer window of browser shows return of JSON.
If I do schema.execute in django mutation work normally.
Main Schema:
import graphene
import graphql_jwt
import about.schema
import shop.schema
class Query(shop.schema.Query, graphene.ObjectType):
pass
class Mutation(shop.schema.Mutation, graphene.ObjectType):
token_auth = graphql_jwt.ObtainJSONWebToken.Field()
verify_token = graphql_jwt.Verify.Field()
refresh_token = graphql_jwt.Refresh.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
Shop Schema:
class CountryType(DjangoObjectType):
class Meta:
model = Country
class CountryInput(graphene.InputObjectType):
name = graphene.String()
class CreateCountry(graphene.Mutation):
country = graphene.Field(CountryType)
class Arguments:
country = graphene.String()
def mutate(self, info, country="LIBERLAND"):
country = Country(
name=country,
)
country.save()
return CreateCountry(
country=country
)
class Query(graphene.ObjectType):
country = graphene.List(CountryType)
def resolve_country(self, info):
return Country.objects.all()
class Mutation(graphene.ObjectType):
create_country = CreateCountry.Field()
I guess the problem is with APOLLO. Same mutations are possible with RELAY Is my apollo-setup wrong?

Resources