Tracking online user with GraphQL Apollo - node.js

I need to handle events "user is now online" and "user is now offline" on GraphQL Apollo Node.js server. What's the best way to do it?
My investigation: I pretty sure that I don't need to implement any heartbeat logic, because subscriptions are working on WebSockets. But I didn't find any info in their docs how to handle WebSockets events like "connecting" and "disconnecting" from the subscription... Actually I can handle those events from the outside of actual subscription:
SubscriptionServer.create({
execute,
subscribe,
schema,
onConnect = (...args) => {
console.log('User connected')
},
onDisconnect = (...args) => {
console.log('User disconnected')
}
}, {
server: ws,
path: '/subscriptions'
})
But can't determine which user is connected via this socket.
My implementation: for now I made it work like that:
We have express middleware for all the calls, it is pushing user object from jsonwebtoken to req object. Here I can trigger "user is now online" logic.
I've created separate subscription, client subscribes on it on login and unsubscribes on logout. Since there is no unsubscribe handler, I manage to determine that filter function gets called on user disconnect without payload, so I did this approach:
userOnlineSubscription: {
subscribe: withFilter(
() => pubSub.asyncIterator('userOnlineSubscription'),
async (payload, variables) => {
if (!payload) {
// set user offline
}
return false
}
)
}
As for me, the solution above is ugly. Can someone recommend the better approach?

I used this approach
onConnect (connectionParams, webSocket) {
const userPromise = new Promise((resolve, reject) => {
if (connectionParams.jwt) {
jsonwebtoken.verify(
connectionParams.jwt,
JWT_SECRET,
(err, decoded) => {
if (err) {
reject(new Error('Invalid Token'))
}
resolve(
User.findOne({
where: { id: decoded.id }
})
)
}
)
} else {
reject(new Error('No Token'))
}
})
return userPromise.then(user => {
if (user) {
return { user: Promise.resolve(user) }
}
return Promise.reject(new Error('No User'))
})
}

Related

Updating Object State in MongoDB via MQTT & Node.js

I am building a node.js API to manage IOT devices. The IOT device have a state which can be true or false, depending on user input. The server subscribes the user to the device topic, once the user adds the device to their account. I am able to toggle the state easily, with the following code :
exports.toggleState = async (req, res, next) => {
let deviceID = req.params.id
let userId = req.userId
try {
let userdata = await User.findById(userId)
let deviceList = userdata.deviceList
deviceList = deviceList.map(el => {
return el.toString()
})
if (deviceList.indexOf(deviceID) !== -1) {
Device.findById(mongoose.Types.ObjectId(deviceID)).then(result => {
result.state = !result.state
return result.save()
})
.then(result => {
req.mqttClient.publish(`${result.topic}`, `${result.state}`, { qos: 0, retain: true }, (error) => {
if (error) {
throw error
}
})
res.status(201).json({ message: 'Device State Changed', data: result })
})
.catch(err => {
console.log(err)
throw err
})
}
else {
res.status(500).json({ message: 'Device not linked to your account!' })
}
} catch (err) {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
}
};
I have added mqtt to make it easier for device side operation (Arduino based).
The challenge I am facing is that if the state changes from the device side (via hardware input), how do I keep the device state updated on the database through the Mqtt server? Some thing like :
Device.findById(deviceID).then(result => {
req.mqttClient.on('message', (`${result.topic}`, payload) => {
console.log('Received Message:', `${result.topic}` , payload.toString())
})
result.state = payload.toString()
return result.save()
}.then(()=>{
console.log('State successfully updated')
}
Problem is I don't know where do I plug this code so that it runs continuously, monitoring incoming messages and updating device state. Would greatly appreciate any help in this.

How i can return the token after the user register complete?

I'm making a api to register users and i like to return in the json response the user and the jwt token.
Actually this is my function:
initializeCreate( {request} ){
const data = request.only(["username", "password", "permission", "status"])
return new Promise(function(resolve, reject) {
user.create(data, function(err, resp, body) {
if (err) {
reject(err);
} else {
resolve(JSON.parse(body))
}
})
})
}
createUser({ auth }){
var initializePromise = initializeCreate();
initializePromise.then(function(result) {
const token = await auth.attempt(result.username, result.password)
return token
}, function(err) {
console.log(err);
})}
I suppose that i have to wait the event User.create() finish to make the auth.attempt, so i create this promise, but this is the better way to do this? There's a way that i make this in only 1 function?
Actually i'm receiving this error:
Unexpected token const token = await auth.attempt(result.username,
result.password)
You can use .generate(user) - documentation
const user = await User.find(1)
const token = await auth.generate(user)
or .attempt(uid, password) - documentation
const token = await auth.attempt(uid, password)
I suppose that i have to wait the event User.create() finish to make
the auth.attempt, so i create this promise, but this is the better way
to do this?
Yes. You need to use await. like :
await user.create(...)
(And now you can put everything in a function)
You can use try/catch :
async login({ auth, response, request }) {
const data = request.only(['email', 'password'])
try {
await auth.check()
return response.status(200).send({ message: 'You are already logged!' })
} catch (error) {
try {
return await auth.attempt(data.email, data.password)
} catch (error) {
return response.status(401).send({ message: error.message })
}
}
}
Sample code for a personal project

What is the proper way to execute axios / firebase promises in a specific order in a firebase function?

What is the best way to chain axios / firebase promises that must be linked in a specific order and use the returns of previous promises?
I am writing a firebase function that allows me to update a user via a third-party JWT API. So I have to fulfill several promises (I use axios for that) to build the final query with a uid, a token and a refresh token.
These requests must be executed in the right order, each promise waiting for the result of the previous one to be able to execute.
recover the firebase client token to identify the user
search in a collection for the tokens (access & refresh) that were previously stored and associated with the user's uid.
Execute the "me" request on the third-party API to retrieve the user's information and update the user.
My question: What is the most correct way to chase these axios promises?
For the moment, I have managed to achieve this result, by interlocking the calls successively to properly manage the "catch" and by moving in separate functions the calls to make a little more digest the reading of the code.
/* index.js */
const userModule = require('./user');
exports.me = functions.https.onRequest( (request, response) => {
cors(request, response, () => {
let idToken = request.body.data.token;
userModule
.get(idToken)
.then((uid) => {
console.log('User found : ' + uid);
return userModule
.retrieve(uid)
.then((userTokens) => {
console.log('User tokens found : ' + userTokens.token);
return userModule
.me(userTokens.token, uid)
.then((me) => {
return me;
}).catch((error) => {
return response.status(404).json({
data : {
error : 404,
message : 'NO_USER_ON_API'
}
});
})
}).catch((error) => {
console.log(error);
return response.status(404).json({
data : {
error : 404,
message : 'NO_TOKEN_USER_FOUND'
}
});
})
})
.catch((error) => {
console.log(error);
return response.status(500).json({
data : {
error : 500,
message : 'USER_TOKEN_NO_MATCH'
}
});
})
.then((user) => {
if(user.data !== undefined)
{
return response.status(200).json({
data : {
user : user.data
}
});
}
else
{
return response.status(204).json({
data : {
user : null
}
});
}
})
});
});
/* user.js */
exports.get = (firebaseToken) {
return admin.auth().verifyIdToken(firebaseToken)
.then(function(decodedToken) {
return decodedToken.uid;
})
.catch(function(error) {
throw {
code: 500,
body: "INTERNAL_ERROR"
};
});
};
exports.retrieve = (uid) {
return admin.firestore().collection("AccessTokenCollection").doc(uid).get()
.then(function(docRef) {
return docRef.data();
})
.catch(function(error) {
throw {
code: 404,
body: "NO_USER_FOUND"
};
});
};
exports.me = (UserToken, uid) {
let params = {
params: {
},
headers: {
'Authorization': 'Bearer ' + UserToken
}
};
return axiosInstance.instance.get(url + '/users/me', params)
.then(userMe => {
return userMe;
})
.catch(errMe => {
console.log(errMe.response.status);
throw {
code: 401,
body: "EXPIRING_TOKEN"
};
});
};
Etc...
The code works as it is more a theoretical question or optimization!
const userModule = require('./user');
exports.me = functions.https.onRequest((request, response) => {
cors(request, response, async () => {
let idToken = request.body.data.token;
try {
let uid = await userModule.get(idToken);
console.log('User found : ' + uid);
let userTokens = await userModule.retrieve(uid);
console.log('User tokens found : ' + userTokens.token);
let meObj = await userModule.me(userTokens.token, uid);
} catch (error) {
console.log('error', error);
}
});
});
So, here using async-await i have removed then-catch block. await keyword will work as then and will only move forward to second call after first call has been completed. And i have made a common catch block for error handling which you can modified according to your needs
you can use promise.all and async-await instead of then and catch

Mock internal dependency

I have a request that has an internal dependency to a Facebook graph objects that performs another request against the FB graph API.
I'm wondering if it is possible to use sinon to mock the graph object so that it wouldn't actually perform a request in a test but would execute the callback function with a value that I provide in the test instead.
server.post("/facebookLogin", function(req, res) {
graph.setAccessToken(req.body.fbtoken);
graph.get("me?fields=email", function(err, obj) {
if (!err) {
var email = obj.email;
checkUserAlreadyRegistered(email, function(user) {
if (user) {
return res.send(200, {user:user, token: decorateToken(user.id)});
} else {
return res.send(404);
}
});
} else {
return res.send(500);
}
});
});
I had the exact same issue, and digging into the fbgraph source code I found out that even though it's using "graphql", internally is a network request with request so you can easily intercept it with nock:
// https://github.com/criso/fbgraph/blob/master/lib/graph.js#L34 <-- fb graph url
const fbMock = nock('https://graph.facebook.com/v4.0/')
.get('/me')
.query(true)
.reply(200, {
id: '123123',
name: 'fb username',
email: 'user#fb.com'
})
it('should not call fb"', (done) => {
chai.request(server)
.post('/facebookLogin')
.send({ fbtoken: 'token_fb' })
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(200)
expect(fbMock).to.have.been.requested
done()
})
}
note: the /v4.0/ part could be different depending on your configuration but the default value is 2.9 so be sure to use the same one you set with the setVersion method

NodeJS EventEmitter test with Mocha

I have a problem testing ldapjs client search operation. It returns an EventEmitter that you have to make listen for some specific event. I wrapped this operations to promisify it and to define my logic and I would like to unit-test it.
findUser(username) {
return new Promise((resolve, reject) => {
logger.debug('Searching user: ', username);
this.ldapClient.bind(user.name, .user.password, err => {
if (err) return reject(err);
else
this.ldapClient.search(root, {filter: `(cn=${username})`}, (errSearch, resSearch) => {
if (errSearch) return reject(errSearch);
const entries = [];
resSearch.on('searchEntry', entry => entries.push(entry.object));
resSearch.on('searchReference', referral => reject(new Error(`Received search referall: ${referral}`)));
resSearch.on('error', err => reject((err));
resSearch.on('end', result => {
if (result.status === 0 && entries.length === 1) {
return resolve({
cn: entries[0].cn,
objectclass: entries[0].objectclass,
password: entries[0].password
});
} else {
return reject(new Error(`Wrong search result: ${result}`));
}
});
});
});
});
}
I am using mockery and Sinon to replace ldapjs dependency inside my module:
beforeEach(function () {
searchEM = new EventEmitter();
sandbox = sinon.createSandbox();
ldapClientStub = Stubs.getLdapClientStub(sandbox);
ldapClientStub.bind.yields(null);
ldapClientStub.search.withArgs('o=ldap', {filter: `(cn=${findParam})`}).yields(null, searchEM);
mockery.registerMock('ldapjs', Stubs.getLdapStub(ldapClientStub));
mockery.registerAllowable('../src/client');
UserClientCls = require('../src/client').default;
userClient = new UserClientCls(config.get());
});
it('should return user with given username', function (done) {
setTimeout(() => {
searchEM.emit('searchEntry', users[1]);
searchEM.emit('end', {status: 0});
console.log('emitted');
}, 500);
searchEM.on('end', res => console.log(res));
userClient.findUser(findParam)
.then(user => {
user.cn.should.equal(users[1].attributes.cn);
user.objectclass.should.equal(users[1].attributes.objectclass);
user.password.should.equal(users[1].attributes.password);
return done();
})
.catch(err => done(err));
});
The problem is that listeners defined inside findUser are never called (but the function itself is called). The listener I defined in the test (just to debug the behaviour) is correctly called.
I do not understand if I miss something about how EventEmitters works or if I am doing the test in a wrong way. Or maybe I wrote a bad piece of code that cannot be tested.
I found a solution to my problem. I extended the base EventEmitter: I added the logic to store which event I want to emit and overrode its on method with a logic to emit my fake event.
class TestEventEmitter extends EventEmitter {
constructor() {
super();
}
setFakeEmit(fakeEmit) {
this.fakeEmit = fakeEmit;
}
on(eventName, cb) {
super.on(eventName, cb);
if (super.eventNames().length === 4)
this.fakeEmit.forEach(f => this.emit(f.name, f.obj));
}
}
So, in beforeEach I can stub ldapClientStub.search to make it return my TestEventEmitter:
beforeEach(function() {
searchEM = new TestEventEmitter();
searchEM.setFakeEmit([{
name: 'searchEntry',
obj: { object: users[1].attributes }
}, {
name: 'end',
obj: { status: 0 }
}]);
...
ldapClientStub.search.withArgs('o=ldap', { filter: `(&(cn=${findParam})(objectclass=astsUser))` }).yields(null, searchEM);
})
This solution may be not very elegant, but it works. If someone can post a better solution I'll be glad to have a look.

Resources