I'm trying to connect to tls websocket but i get websocket.js:120 WebSocket connection to ' failed: Error in connection establishment: net::ERR_CERT_AUTHORITY_INVALID
here's the code I'm trying to use
import SocketIOClient from "socket.io-client";
import * as types from "../constants/sockets";
const URL = "SERVER";
export function missingSocketConnection() {
return {
type: types.SOCKETS_CONNECTION_MISSING,
payload: new Error("Missing connection!"),
};
}
let socket = null;
export function socketsConnect() {
return (dispatch, getState) => {
const state = getState();
const { isFetching } = state.services;
const { token } = state.auth;
if (isFetching.sockets) {
return Promise.resolve();
}
dispatch({
type: types.SOCKETS_CONNECTION_REQUEST,
});
socket = SocketIOClient(URL, {
reconnection: false,
path: "/",
transports: ["websocket"],
query: { token },
rejectUnauthorized: false,
secure: false,
upgrade: false,
});
socket.on("connect", () => {
dispatch({
type: types.SOCKETS_CONNECTION_SUCCESS,
});
});
socket.on("error", (error) => {
dispatch({
type: types.SOCKETS_CONNECTION_FAILURE,
payload: new Error(`Connection: ${error}`),
});
});
socket.on("connect_error", () => {
dispatch({
type: types.SOCKETS_CONNECTION_FAILURE,
payload: new Error("We have lost a connection :("),
});
});
return Promise.resolve();
};
}
i tried providing the cert by import crt from "path/to/crt" but didn't work either
also tried tweaking the options to secure: true
the same server allowed connection without using the certificate using C# and Websocket-Sharp
Related
Basically i wanted to integrate the mqtt in my react app. I want to connect to the mqtt when user login and disconnect when user logs out.
Firstly i created actions,reducers to dispatch the connection state and get client payload. It's connecting but the mqtt servers seems to be reconnecting in a loop with few secs. I want it to connect once and disconnect user logs out automatically. I globally initiated the mqtt client in the mqqtActions.js.
import { CONNECT_MQTT_FAILURE, CONNECT_MQTT_REQUEST, CONNECT_MQTT_SUCCESS, DISCONNECT_MQTT_FAILURE, DISCONNECT_MQTT_REQUEST, DISCONNECT_MQTT_SUCCESS } from "../constants/mqttConstants"
import mqtt from 'mqtt'
const mqttHost = "host ip address here";
const mqttPort = 8083;
const mqttUrl = `ws://${mqttHost}:${mqttPort}/mqtt`
const mqttOptions = {
keepalive: 30,
protocolId: 'MQTT',
protocolVersion: 4,
clean: true,
reconnectPeriod: 1000,
connectTimeout: 30 * 1000,
clientId: "bharath",
will: {
topic: 'WillMsg',
payload: 'Connection Closed abnormally..!',
qos: 0,
retain: false
},
rejectUnauthorized: false
};
const mqttClient = mqtt.connect(mqttUrl, mqttOptions)
export const mqttConnect = () => async (dispatch) => {
try {
dispatch({
type: CONNECT_MQTT_REQUEST
})
dispatch({
type: CONNECT_MQTT_SUCCESS,
payload: mqttClient
})
//mqttClient.connect()
localStorage.setItem('mqttClient', JSON.stringify(mqttClient))
} catch (error) {
dispatch({
type: CONNECT_MQTT_FAILURE,
payload: error.response && error.response.data.message ? error.response.data.message : error.message
})
}
}
export const mqttDisconnect = () => (dispatch) => {
try {
dispatch({ type: DISCONNECT_MQTT_REQUEST })
mqttClient.end()
localStorage.removeItem('mqttClient')
dispatch({
type: DISCONNECT_MQTT_SUCCESS,
})
} catch (error) {
dispatch({
type: DISCONNECT_MQTT_FAILURE,
payload: error.response && error.response.data.message ? error.response.data.message : error.message
})
}
}
I set up my reducer file like this:
import { CONNECT_MQTT_FAILURE, CONNECT_MQTT_REQUEST, CONNECT_MQTT_SUCCESS, DISCONNECT_MQTT_FAILURE, DISCONNECT_MQTT_REQUEST, DISCONNECT_MQTT_SUCCESS } from "../constants/mqttConstants"
export const connectMqttReducer = (state = {}, action) => {
switch (action.type) {
case CONNECT_MQTT_REQUEST:
return { status: 'connecting' }
case CONNECT_MQTT_SUCCESS:
return { status: 'connected', client: action.payload }
case CONNECT_MQTT_FAILURE:
return { status: 'connect', error: action.payload }
default:
return state
}
}
export const disconnectMqttReducer = (state = {}, action) => {
switch (action.type) {
case DISCONNECT_MQTT_REQUEST:
return { status: 'connected' }
case DISCONNECT_MQTT_SUCCESS:
return { status: 'connect' }
case DISCONNECT_MQTT_FAILURE:
return { status: 'connected', error: action.payload }
default:
return state
}
}
Doing this i'm able to connect but its timestamp when connectedAt is changing continously.And also mqtt.connect() is not function error is also showing. I commented it out. I want to connect it once and disconnect when user login and logout actions are triggered.
Please try the following:
1- Add the following to your environment file:
MQTT_HOST="host ip address here"
MQTT_PORT=8083
2- Add the following in your Constants.js file:
export const mqttUrl = `ws://${process.env.MQTT_HOST}:${process.env.MQTT_PORT}/mqtt`;
export const mqttOptions = {
keepalive: 30,
protocolId: 'MQTT',
protocolVersion: 4,
clean: true,
reconnectPeriod: 1000,
connectTimeout: 30 * 1000,
clientId: 'bharath',
will: {
topic: 'WillMsg',
payload: 'Connection Closed abnormally..!',
qos: 0,
retain: false,
},
rejectUnauthorized: false,
};
export const localStorageKeys = {
mqttClient: 'mqttClient',
};
3- Create a new file which will hold all mqtt functionality /src/clients/MqttClient.js:
import mqtt from 'mqtt';
import { localStorageKeys, mqttOptions, mqttUrl } from '#/js/constants/Helpers';
let instance = null;
class MqttClient {
constructor() {
if (!instance) {
instance = this;
}
return instance;
}
myMqtt;
connect() {
this.myMqtt = mqtt.connect(mqttUrl, mqttOptions);
return new Promise((resolve, reject) => {
this.myMqtt.on('connect', () => {
//add instance to local storage if it's not present (if you need it)
const localStorageMqtt = localStorage.getItem(localStorageKeys.mqttClient);
if (localStorageMqtt === null) {
localStorage.setItem(localStorageKeys.mqttClient, JSON.stringify(this.myMqtt));
}
resolve();
});
this.myMqtt.on('error', (error) => reject(error));
});
}
disconnect() {
return new Promise((resolve) => {
this.myMqtt.end(false, {}, () => {
this.myMqtt = null;
//if you added it to the localstorage (on connect)
localStorage.removeItem(localStorageKeys.mqttClient);
resolve();
});
});
}
subscribe(event) {
return new Promise((resolve, reject) => {
if (!this.myMqtt) return reject('No mqtt connection.');
return this.myMqtt.subscribe(event, (err) => {
// Optional callback that you can use to detect if there's an error
if (err) {
console.error(err);
return reject(err);
}
return resolve();
});
});
}
publish(event, data) {
return new Promise((resolve, reject) => {
if (!this.myMqtt) return reject('No mqtt connection.');
return this.myMqtt.publish(event, data, {}, (err) => {
// Optional callback that you can use to detect if there's an error
if (err) {
console.error(err);
return reject(err);
}
return resolve();
});
});
}
on(event, fun) {
// No promise is needed here, but we're expecting one in the middleware.
return new Promise((resolve, reject) => {
if (!this.myMqtt) return reject('No mqtt connection.');
this.myMqtt.on(event, fun);
resolve();
});
}
}
export default MqttClient;
4- Create a new mqtt middleware in your store directory /src/store/middleWares/MqttMiddleWare.js:
export const mqttMiddleWare =
(mqtt) =>
({ dispatch, getState }) =>
(next) =>
(action) => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
/*
* Mqtt middleware usage.
* promise: (mqtt) => mqtt.connect()
* type: 'mqtt' //always (mqtt)
* types: [REQUEST, SUCCESS, FAILURE]
*/
const { promise, type, types, ...rest } = action;
if (type !== 'mqtt' || !promise) {
// Move on! Not a mqtt request or a badly formed one.
return next(action);
}
const [REQUEST, SUCCESS, FAILURE] = types;
next({ ...rest, type: REQUEST });
return promise({ mqtt, dispatch, getState })
.then((result) => {
return next({ ...rest, result, type: SUCCESS });
})
.catch((error) => {
console.log(error);
return next({ ...rest, error, type: FAILURE });
});
};
5- Update your store config to accept mqtt client as an argument then pass it to the mqtt middleware as follows /src/store/configureStore.js:
const middlewares = [];
// log redux data in development mode only
if (process.env.NODE_ENV !== 'production') {
const { logger } = require('redux-logger');
middlewares.push(logger);
}
const configureStore = (mqttClient) => {
const store = createStore(
rootReducer,
/* preloadedState, */
composeWithDevTools(
applyMiddleware(thunkMiddleware, mqttMiddleWare(mqttClient), ...middlewares)
)
);
return store;
};
export default configureStore;
6- Instantiate your mqtt client in /src/index.jsx and pass it to your store:
const mqttClient = new MqttClient();
const store = configureStore(mqttClient);
7- Update your reducer as follows:
import { CONNECT_MQTT_FAILURE, CONNECT_MQTT_REQUEST, CONNECT_MQTT_SUCCESS, DISCONNECT_MQTT_FAILURE, DISCONNECT_MQTT_REQUEST, DISCONNECT_MQTT_SUCCESS } from "../constants/mqttConstants"
export const connectMqttReducer = (state = {}, action) => {
switch (action.type) {
case CONNECT_MQTT_REQUEST:
return { connectionStatus: 'connecting' }
case CONNECT_MQTT_SUCCESS:
return { connectionStatus: 'connected' }
case CONNECT_MQTT_FAILURE:
return { connectionStatus: 'connect failed', error: action.error }
default:
return state
}
}
export const disconnectMqttReducer = (state = {}, action) => {
switch (action.type) {
case DISCONNECT_MQTT_REQUEST:
return { disconnectionStatus: 'disconnecting' }
case DISCONNECT_MQTT_SUCCESS:
return { disconnectionStatus: 'disconnected' }
case DISCONNECT_MQTT_FAILURE:
return { disconnectionStatus: 'connect failed', error: action.error }
default:
return state
}
}
8- Update your actions as follows:
Connect action:
export const startMqttConnection = () => ({
type: 'mqtt',
types: [CONNECT_MQTT_REQUEST, CONNECT_MQTT_SUCCESS, CONNECT_MQTT_FAILURE],
promise: ({ mqtt }) => mqtt.connect(),
});
Disconnect action:
export const stopMqttConnection = () => ({
type: 'mqtt',
types: [DISCONNECT_MQTT_REQUEST, DISCONNECT_MQTT_SUCCESS, DISCONNECT_MQTT_FAILURE],
promise: ({ mqtt }) => mqtt.disconnect(),
});
9- dispatch the required action as follows:
useEffect(() => {
dispatch(startMqttConnection());
return () => {
if (connectionStatus === 'connected') {
dispatch(stopMqttConnection());
}
};
//eslint-disable-next-line
}, [dispatch]);
I have an app where I want to authenticate a user and show authenticated user pages which are unavailable to a non authenticated oneenter code here.
On backend I am using express-sessions and apollo-server.
Basically my idea was to set userId in the session, then check if it is empty or not and send from backend to frontend if user is authenticated or not.
My index.ts file is as following:
const { createClient } = require('redis');
let redisClient = createClient({ legacyMode: true });
async function main() {
const httpServer = http.createServer(app);
// check if userid exists
function isAuthenticated(req: any) {
const userId: string = req?.userId;
return !!userId;
}
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
await redisClient.connect();
app.use(
bodyParser.json(),
cors<cors.CorsRequest>(),
session({
store: new RedisStore({
host: host,
port: 6379,
client: redisClient,
}),
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
},
}),
expressMiddleware(server, {
context: async ({ req, res }) => {
return { req, res, isAuthenticated: isAuthenticated(req) };
},
})
);
try {
await mongoose.connect(process.env.MONGO_DB_URI || '');
console.log(`Mongoose connected on port`);
} catch (error) {
console.log(error);
}
await new Promise<void>((resolve) =>
httpServer.listen({ port: 4000 }, resolve)
);
console.log(`🚀 Server ready at http://localhost:4000/`);
}
main();
my sign-in resolver is this:
signInUser: async (_, { user }, context) => {
const { username, password } = user;
const foundUser = await UserModel.findOne({ username });
if (foundUser) {
const match = await bcrypt.compare(password, foundUser.password);
if (match) {
if (context.req.session) {
context.req.session.userId = foundUser.id; // here I am setting user id to the session
return { message: 'Signed in successfully!', status: 'success' };
}
}
}
},
and
Query: {
isAuthenticated: (_, __, {isAuthenticated}) => {
return isAuthenticated;
},
},
And on frontend I created a hook like this, to use in my components.
import { useQuery, gql } from '#apollo/client';
export const IS_AUTHENTICATED = gql`
query IsAuthenticated {
isAuthenticated
}
`;
const useAuth = () => {
const { data } = useQuery(IS_AUTHENTICATED);
return {
isAuthenticated: data && data.isAuthenticated,
};
};
export default useAuth;
The problem is that I req which I check userId does not have a userId yet in the apollo context. So it is always false. So I am not sure what approach would be better there?
I don't really want to set the cookie as httpOnly: false and manipulate it on frontend, for example.
I have implemented the socket, which was not able to established after pressing F5 button,
when users logged into the app, socket has been establishes successfully, when I hit F5, page refreshes but socketConnection is not invoked because props is undefined due to which conditional statement has been failed to execute.
After inspecting I have found that, I invoked the main socket creation function inside App.js, it takes props as as argument and one conditional statement. I have found that the props itself is undefined due to which the socket i not able t re-established.
App.js
import React, { useEffect } from 'react';
import './App.scss';
import Routes from './Routes';
import { connect } from 'react-redux';
import socketConnection from '#Hoc/SocketComponent';
const App = () => {
useEffect((props) => {
if (props?.session?.user?.extraDetails?.extensionNo) {
socketConnection(props);
}
}, []);
return (
<div>
<Routes />
</div>
);
};
const mapStateToProps = (state) => ({
session: state.session
});
export default connect(mapStateToProps)(App);
SocketComponent
export default function socketConnection(socketEvent) {
if (!window?.socket)
sessionService.loadUser().then((currentUser) => {
if (
currentUser?.extraDetails?.extensionNo &&
currentUser?.params?.AgentID
)
socket = window.socket = io(REACT_APP_SOCKET_URL, {
query: {
agentId: currentUser.params.AgentID,
extensionNo: currentUser.extraDetails.extensionNo,
},
});
socket.on("connect", (data) => {
console.info(data);
});
socket.on("EventEstablished", (data) => {
eventEstablished(data, currentUser, socketEvent);
});
socket.on("EventAgentLogout", () => {
notification.error({
message: "Softphone loggedout, please re-login",
duration: 0,
});
socketEvent.history.push("/home");
});
socket.on("EventPropertiesChanged", (data) => {
manualGeocode(data);
});
socket.on("EventAttachedDataChanged", (data) => {
if (data.data?.incidentNumber) {
store.dispatch({
type: SET_LIVE_CALL_DATA,
payLoad: { psapReferenceId: data?.data?.incidentNumber },
});
}
if (data.data?.updateLocation && data.data?.isRetransmit) {
let functionCall = "retransmit" ;
// if (data.data?.isRetransmit) {
// functionCall = "retransmit" ;
// }
getCallData( functionCall );
}
// manualGeocode(data);
});
socket.on("EventDestinationBusy", (data) => {
console.log("EventDestinationBusy", data);
});
socket.on("EventAbandoned", (data) => {
notification.error({
message: "Call abandoned",
description: "The caller abandoned the call before it was answered",
duration: 0,
});
});
socket.on("EventDNOutOfService", (data) => {
notification.error({
message: "Extension Out of Service !",
description:
"This extension is out of service and cannot make or receive calls. ",
duration: 0,
});
});
socket.on("EventAgentReady", (data) => {
console.log("EventAgentReady", data);
});
socket.on("EventAgentNotReady", (data) => {
console.log("EventAgentNotReady", data);
});
socket.on("EventReleased", (data) => {
eventReleased(data);
});
socket.on("EventPartyDeleted", (data) => {
eventPartyDeleted(data);
});
socket.on("EventInvite", (data) => {
console.log(data);
if (
!store?.getState()?.secondAgent?.secondAgent?.isSecondAgent &&
socketEvent.history.location.pathname === "/waiting"
) {
eventInvite(data, currentUser, socketEvent);
}
});
socket.on("disconnect", (data) => {
console.log(data);
});
socket.on("workflow", (data) => {
store.dispatch({ type: SET_WORKFLOW_DATA, payLoad: data });
});
socket.on("workflowUpdatedComponent", (data) => {
store.dispatch({ type: SET_WORKFLOW_OPTIONS, payLoad: data });
});
socket.on("gather-info", (data) => {
console.log(data);
if (data?.data?.extensionNo != currentUser.extraDetails.extensionNo) {
store.dispatch({
type: SET_GATHER_INFO,
payLoad: data?.data?.gatherInfo,
});
}
});
socket.on("geoCodeSession", (data) => {
console.log(data);
if (data?.data?.extensionNo != currentUser.extraDetails.extensionNo) {
if (data?.data?.updateLocation) {
store.dispatch({
type: SET_MANUAL_GEOCODE,
payLoad: { updateLocation: true },
});
}
// let timeFrame = new Date(data.data.timestamp);
// timestamp = timeFrame.toLocaleDateString() + ' ' + timeFrame.toLocaleTimeString();
store.dispatch({
type: SET_MANUAL_GEOCODE,
payLoad: {
status: true,
latitude: data?.data?.latitude,
longitude: data?.data?.longitude,
address: data?.data?.address,
country: data?.data?.country,
region: data?.data?.region,
timestamp: data?.data?.timestamp,
},
});
}
});
socket.on("EventSessionInfo", (data) => {
if (data?.data?.extensionNo !== currentUser?.extraDetails.extensionNo) {
if (data.data.sessionStatus === "Over") {
store.dispatch({
type: SET_SECOND_AGENT,
payLoad: { status: "Disconnected", isSecondAgent: false },
});
} else if (
data.data.sessionStatus === "Alive" &&
data.data.agentNameChat
) {
store.dispatch({
type: SET_SECOND_AGENT,
payLoad: {
isSecondAgent: true,
status: "Connected",
anotherAgent: data.data.messageText,
isSecondAgent: true,
},
});
} else if (
data.data.sessionStatus === "Alive" &&
!data.data.messageText.includes("Leaving ChatRoom..") &&
!data.data.messageText.includes("Join Chat Session")
) {
chatStore(data);
}
}
});
});
}
I am not able to figured it out what went wrong, however I am trying to load when props are there but not able to do the same.
PROBLEM
It seems like props are not loaded while execution of `useEffect
Solution might be require some kind of delay to useEffect, so once props properly loaded form localstorage
I am trying to create a context api for twilio call connection. I would like to take help from twilio experts if I am doing it in correct way or not. In this context, What I am trying to do is connect twilio device and if the token expires then reconnect. But this way, the outgoing call is not working yet I could see the device is ready printed on my console. When I try to call, the deviceInstance state is shown null.
This is how I am doing
import { Device, Connection } from 'twilio-client';
import twilioReducers from './twilioReducers';
interface ITwilioContext {
// device?: Device;
setDeviceOnline?: () => void;
state: InitialStateType;
dispatch: React.Dispatch<any>;
handleDeviceOutgoing: (params: OutgoingProps) => void;
}
export const TwilioContext = createContext<ITwilioContext>({});
const TwilioProvider = ({ children }: { children: React.ReactNode }) => {
const [deviceInstance, setDeviceInstance] = useState<Device | null>(null);
const [activeWorkspaceId] = useLocalStorage('activeWorkspaceId', null);
const { t } = useTranslation();
const [getVoiceToken, { data }] = useLazyQuery(VOICE_TOKEN, {
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
onError: err => console.error(err),
});
// Commented inorder to prevent reintialization
// const device = new Device();
const initialState = { direction: '', showPhoneWidget: false };
const [state, dispatch] = useReducer(twilioReducers, initialState);
/* event handlers for twilio device */
const handleDeviceReady = (device: Device) => {
console.log('Device ready');
setDeviceInstance(device);
};
const handleDeviceOffline = async (device: Device) => {
console.log('Device offline', device);
if (device.token) {
const twilioTokenDecoded = jwtDecode<JwtPayload>(device.token);
if ((twilioTokenDecoded as any).exp <= Date.now() / 1000) {
await getVoiceToken({});
console.log('twilio new token data', data);
}
}
};
const handleDeviceError = (error: Connection.Error) => {
console.log('Device error', error);
if (TWILIO_ERRORS[error.code] !== undefined) {
ToastMessage({
content: t(TWILIO_ERRORS[error.code].errorKey, TWILIO_ERRORS[error.code].message),
type: 'danger',
});
}
};
/* ----------------------------- */
/* handle incoming calls */
const handleDeviceIncoming = (connection: Connection) => {
console.log('incoming', connection);
dispatch({
type: ACTIONS.INCOMING_CALL,
data: connection,
});
connection.on(deviceEvent.CANCEL, () => {
console.log('incoming-cancel');
dispatch({
type: ACTIONS.INCOMING_CALL_CANCEL,
});
});
connection.on(deviceEvent.DISCONNECT, () => {
console.log('incoming-disconnect');
dispatch({
type: ACTIONS.INCOMING_CALL_DISCONNECT,
});
});
connection.on(deviceEvent.ACCEPT, () => {
console.log('incoming-call-accept');
dispatch({
type: ACTIONS.ANSWER_INCOMING_CALL,
});
});
connection.on(deviceEvent.REJECT, () => {
console.log('incoming-call-reject');
dispatch({
type: ACTIONS.REJECT_INCOMING_CALL,
updateConversationStatus: true,
});
});
connection.on(deviceEvent.ERROR, (err: Connection.Error) => {
console.log('Connection error occured', err);
dispatch({
type: ACTIONS.INCOMING_CALL_ERROR,
status: 'error',
});
});
};
/* ----------------------------- */
/* handle outgoing calls */
const handleDeviceOutgoing = (params: OutgoingProps) => {
if (deviceInstance) {
if (deviceInstance.isInitialized || deviceInstance.status() !== 'ready') {
ToastMessage({ content: t('error.deviceSetup', 'Device is offline.'), type: 'danger' });
return;
}
const connection = deviceInstance.connect(params); // copied from premvp
dispatch({
type: ACTIONS.OUTGOING_CALL_INITIATED,
data: connection,
status: 'connecting',
channelId: params?.channel_sid,
});
connection.on(deviceEvent.RINGING, (val: boolean) => {
if (val) {
dispatch({
type: ACTIONS.OUTGOING_CALL_RINGING,
});
}
});
connection.on(deviceEvent.CANCEL, () => {
console.log('Connection cancelled');
dispatch({
type: ACTIONS.OUTGOING_CALL_DISCONNECT,
});
});
connection.on(deviceEvent.DISCONNECT, (conn: Connection) => {
// handle user hungup
console.log('Connection disconnected', conn);
dispatch({
type: ACTIONS.OUTGOING_CALL_DISCONNECT,
});
});
connection.on(deviceEvent.ACCEPT, (conn: Connection) => {
console.log('Connected to the user', conn); // handle user answercall
dispatch({
type: ACTIONS.OUTGOING_CALL_ANSWERED,
});
});
connection.on(deviceEvent.REJECT, (conn: Connection) => {
console.log('Rejected', conn); // handle user answercall
dispatch({
type: ACTIONS.REJECT_OUTGOING_CALL,
});
});
connection.on(deviceEvent.ERROR, (err: Connection.Error) => {
console.log('Connection error occured', err);
});
} else {
console.log('No Device Instance exist');
}
};
/* ----------------------------- */
useEffect(() => {
const device = new Device();
console.log('device', device, data);
if (data?.getVoiceToken?.data?.voiceToken) {
device.setup(data?.getVoiceToken?.data?.voiceToken, deviceConfig);
device.on(deviceEvent.READY, handleDeviceReady);
device.on(deviceEvent.OFFLINE, handleDeviceOffline);
device.on(deviceEvent.ERROR, handleDeviceError);
device.on(deviceEvent.INCOMING, handleDeviceIncoming);
}
return () => {
device.destroy();
setDeviceInstance(null);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data?.getVoiceToken?.data?.voiceToken]);
useEffect(() => {
if (activeWorkspaceId !== '') {
getVoiceToken({});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeWorkspaceId]);
const value = useMemo(() => {
return {
state,
dispatch,
deviceInstance,
handleDeviceOutgoing,
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state]);
return <TwilioContext.Provider value={value}>{children}</TwilioContext.Provider>;
};
export default TwilioProvider;
Code under test
// imports
const router = express.Router()
// This is what needs to be mocked
const client = new AwesomeGraphQLClient({
endpoint: process.env.GRAPHCMS_URL || '',
fetch,
fetchOptions: {
headers: {
authorization: `Bearer ${process.env.GRAPHCMS_TOKEN}`
}
}
})
interface LoginRequest {
email: string
password: string
}
router.post(
'/login',
async (req: Request<{}, {}, LoginRequest>, res: Response) => {
try {
const JWT_SECRET = getEnvironment('JWT_SECRET')
const { email, password } = req.body
if (!email || !password) {
res.status(400).json({
message: 'auth.provide.credentials',
full: 'You should provide an email and password'
})
return
}
if (!JWT_SECRET) {
res.status(500).json({
message: 'auth.secret.not.found',
full: 'Secret not found'
})
// TODO error logging
return
}
const { appUsers } = await client.request<
GetUserByEmailResponse,
GetUserByEmailVariables
>(getUserByEmailQuery, {
email
})
if (appUsers.length === 0) {
res.status(404).json({
message: 'auth.wrong.credentials',
full: 'You provided wrong credentials'
})
return
}
const user = appUsers[0]
const result: boolean = await bcrypt.compare(password, user.password)
if (result) {
var token = jwt.sign({ id: user.id, email: user.email }, JWT_SECRET)
res.status(200).json({
token
})
return
}
res.status(200).json({
message: 'auth.wrong.credentials',
full: 'You provided wrong credentials in the end'
})
} catch (e) {
console.log('E', e)
const error: ErrorObject = handleError(e)
res.status(error.code).json(error)
}
}
)
Tests for code above
import request from 'supertest'
import app from '../../../app'
import { mocked } from 'ts-jest/utils'
import { compare } from 'bcrypt'
import { AwesomeGraphQLClient } from 'awesome-graphql-client'
const mockRequestFn = jest.fn().mockReturnValue({
appUsers: [
{
id: 'tests'
}
]
})
jest.mock('awesome-graphql-client', () => ({
AwesomeGraphQLClient: jest.fn().mockImplementation(() => ({
request: mockRequestFn
}))
}))
I am trying to mock a method on a non default exported class from Awesome GraphQL. I also want to spy on this method, so I created a separate jest.fn() with a return value. The problem is that request is not a function: TypeError: client.request is not a function.
How can I mock and spy on the method of a mocked non default exported class?
SOLUTION
Managed to find a workaround. Make the method a function that returns the called mockRequest. This way you can spy on AwesomeGraphQLClient.request with mockRequest.toHaveBeenCalledTimes(x).
let mockRequest = jest.fn().mockReturnValue({
appUsers: [
{
id: 'tests'
}
]
})
jest.mock('awesome-graphql-client', () => {
return {
AwesomeGraphQLClient: jest.fn().mockImplementation(() => {
return {
request: () => mockRequest()
}
})
}
})