Nodejs MQTT: unsubscribe is not a function - javascript

I have inplemented MQTT well but I am experiencing an issue with unsubscribe method. All other functions (mqttCon.publish() , mqttCon.subscribe(), mqttCon.on()...) are working well except this. I have checked the spelling and even done ctrl +click and it takes me to the library implementation meaning it is the right method and referenced well yet I keep getting the error below. How can I solve it?
This is the line: mqttCon.unsubscribe(topic)
TypeError: mqttCon.unsubscribe is not a function
at noopHandler (/home/dev/project-dir/src/mqtt/processMessage.js:5:13)
at module.exports (/home/dev/project-dir/src/mqtt/processMessage.js:10:20)
at MqttClient.client.on (/home/dev/project-dir/src/mqtt/mqttCon.js:16:13)
at MqttClient.emit (events.js:189:13)
at MqttClient._handlePublish (/home/dev/project-dir/node_modules/mqtt/lib/client.js:1271:12)
at MqttClient._handlePacket (/home/dev/project-dir/node_modules/mqtt/lib/client.js:410:12)
at work (/home/dev/project-dir/node_modules/mqtt/lib/client.js:321:12)
at Writable.writable._write (/home/dev/project-dir/node_modules/mqtt/lib/client.js:335:5)
at doWrite (/home/dev/project-dir/node_modules/mqtt/node_modules/readable-stream/lib/_stream_writable.js:428:64)
at writeOrBuffer (/home/dev/project-dir/node_modules/mqtt/node_modules/readable-stream/lib/_stream_writable.js:417:5)
NOTE: I am using ES6(Emacscript 6+) javascript and not Typescript.
Nodejs 12.18.1 and npm 6.14.6
Here is my connection code mqttCon.js:
const mqtt = require('mqtt')
const processMessage = require('./processMessage')
const logger = require('../logConf')
const options = {
host: '',
port: '',
username:'',
password: '',
protocol: ''
};
const client = mqtt.connect(options)
client.on("connect", function () {
console.log("MQTT connected with status: " + client.connected);
if (client.connected) {
client.on('message', (topic, message) => {
processMessage(topic, String(message))
})
}
})
client.on('error', error => {
console.log(error,'ERROR')
logger.errorLogger.error(error)
})
client.on('reconnect', error => {
console.log(error,'RECONNECT')
logger.errorLogger.error(error)
})
client.on('close', error => {
console.log(error,'CLOSE')
logger.errorLogger.error(error)
})
client.on('disconnect', error => {
console.log(error,'DISCONNECT')
logger.errorLogger.error(error)
})
client.on('offline', error => {
console.log(error,'OFFLINE')
logger.errorLogger.error(error)
})
module.exports = client
This is the processMessage.js :
const mqttCon = require('./mqttCon')
const logger = require('../logConf')
let noopHandler = (topic, message) => {
console.log(String(message))
mqttCon.unsubscribe(topic) //THIS IS WHERE THE ERROR IS OCCURRING *******************
}
module.exports = (topic, message) => {
switch (topic) {
case 'NOOOOOOOOOOOOOOOOOOOOOOOOP':
return noopHandler(topic, message)
case 'anotherTopic':
// return handleAnotherTopic(String(message))
return
default:
logger.errorLogger.error(new Error(`No handler for topic ${topic}`))
}
}

Your mqttCon.js file has no client.prototype.unsubscribe = function() {}, so the error is correct. You are defining client as a module, but you are really needing to call mqtt.unsubscribe() somewhere. So you need to either add an unsubscribe() function to the client constant (which really should be a var in this case), or call the mqtt.unsubscribe() function after requiring the mqtt module in your processMessage.js file....which I think goes against what you are trying to do anyway. You might want to read up a bit more about how module.exports actually works: https://www.sitepoint.com/understanding-module-exports-exports-node-js/
UPDATE:
The above influenced my thoughts and the issue was that I was importing processMessage.js which inturn imports mqttCon.js which imported it. Circular import, meaning mqttCon(mqttClient) was always not yet initialized inside processMessage.js. The solution was that I imported processMessage.js inside client.on('connect'....)... block when client is already initialized and exported well as a module like below:
client.on("connect", () => {
console.log("MQTT connected with status: " + client.connected);
if (client.connected) {
client.on('message', (topic, message) => {
require('./processMessage')(topic, String(message))
})
}
})

Related

How to use Peer.js in Next.js with TypeScript?

Next.js runs on server side also, so Peer.js raise error when using Next.js. Here one says: https://stackoverflow.com/a/66292100/239219
this is probably because peer js is performing some side effect during import.
He propose this:
useEffect(() => {
import('peerjs').then(({ default: Peer }) => {
// Do your stuff here
});
}, [])
But I need DataConnection as using Typescript, and also asign it to a useState. would you show an example how?
This is what I put togeter, but TypesScript raise errors:
useEffect(() => {
import('peerjs').then(({ default: Peer, DataConnection }) => {
const peer = new Peer(localStorage.token)
peer.on('connection', (conn: DataConnection) => {
console.log('Connected to peer:', conn)
conn.on('data', (data) => {
console.log('Received data:', data)
})
})
return () => {
peer.destroy()
}
})
}, [])
like: 'DataConnection' refers to a value, but is being used as a type here. Did you mean 'typeof DataConnection'?
You can use a type-only import (introduced in version 3.8) at the top of the file:
import type { DataConnection } from "peerjs";
This import will be erased in the output, so rest assured that this will not import it "early".
Or if that doesn't fancy you, you can also "inline" the import:
peer.on('connection', (conn: import("peerjs").DataConnection) => {
Looks weird, but when import(...) is used as a type, it resolves to the namespace that importing the module would give you.

mongo client: how can I reuse the client in separate file?

Here is db.js file
const client = new MongoClient(DATABASE, mongodbOptions);
const connectWithMongoDb = () => {
client.connect((err) => {
if (err) {
throw err;
} else {
console.log('db connected');
}
});
};
module.exports = { client, connectWithMongoDb };
I called the connectWithMongoDb function from my server.js. db connects successfully. but the problem is I can't reuse the client. for example, I want to make a separate directory for collections. (in order to get a collection I need client object)
So, here is my collection.js file
const { client } = require('../helpers/db-helper/db');
exports.collection = client.db('organicdb').collection('products');
but the problem arises as soon as this file(collection.js) is called.
I am getting this error:
throw new MongoError('MongoClient must be connected before calling MongoClient.prototype.db'
You have to get the connection after connecting to MongoDB post that you can use it anywhere.
Read - https://mongodb.github.io/node-mongodb-native/api-generated/mongoclient.html
let client;
async function connect() {
if (!client) {
client = await MongoClient.connect(DATABASE, mongodbOptions)
.catch(err => { console.log(err); });
}
return client;
}
conet getConectedClient = () => client;
const testConnection = connect()
.then((connection) => console.log(connection)); // call the function like this
module.exports = { connect, getConectedClient };

Getting error: Test webhook error: 400 when trying to send a test event to a webhook endpoint

I am attempting to send a test webhook as instructed in this tutorial.
But when I go to do it I get the error seen in the first link, and below:
Test webhook error: 400
Here is my index.ts code & functions I have deployed to firebase functions.
import * as functions from 'firebase-functions';
​
​
// const functions = require('firebase-functions');
const stripe = require('stripe')(functions.config().keys.webhooks);
const admin = require('firebase-admin');
​
admin.initializeApp();
const endpointSecret = functions.config().keys.signing;
​
exports.events = functions.https.onRequest((request, response) => {
​
let sig = request.headers["stripe-signature"];
​
try {
let event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret); // Validate the request
return admin.database().ref('/events').push(event) // Add the event to the database
.then((snapshot: { ref: { toString: () => any; }; }) => {
// Return a successful response to acknowledge the event was processed successfully
return response.json({ received: true, ref: snapshot.ref.toString() });
})
.catch((err: any) => {
console.error(err) // Catch any errors saving to the database
return response.status(500).end();
});
}
catch (err) {
return response.status(400).end(); // Signing signature failure, return an error 400
}
});
​
exports.exampleDatabaseTrigger = functions.database.ref('/events/{eventId}').onCreate((snapshot, context) => {
return console.log({
eventId: context.params.eventId,
data: snapshot.val()
});
});
How do I fix this and successfully run the test?
My current thinking is that the problem may have something to do with:
How I wrote this line: snapshot: { ref: { toString: () => any; };
Update:
From my testing, this does not appear to be the case.
I don't believe that the 'test webhook' properly signs them; you should use Stripe CLI for this instead.

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an instance of Object

I am using the source code from a security rules tutorial to attempt to do integration testing with Jest for my Javascript async function async_create_post, used for my firebase HTTP function create_post The files involved has a directory structure of the following:
Testing file: root/tests/handlers/posts.test.js
File to be tested: root/functions/handlers/posts.js
Helper code from the tutorial: root/tests/rules/helpers.js
And here is the source code that is involved:
posts.test.js
const { setup, teardown} = require("../rules/helpers");
const {
async_get_all_undeleted_posts,
async_get_post,
async_delete_post,
async_create_post
} = require("../../functions/handlers/posts");
describe("Post Creation", () => {
afterEach(async () => {
await teardown();
});
test("should create a post", async () => {
const db = await setup();
const malloryUID = "non-existent uid";
const firstPost = {
body: "First post from Mallory",
author_id: malloryUID,
images: ["url1", "url2"]
}
const before_post_snapshot = await db.collection("posts").get();
expect(before_post_snapshot.docs.length).toBe(0);
await async_create_post(firstPost); //fails at this point, expected to create a new post, but instead threw an error
const after_post_snapshot = await db.collection("posts").get();
expect(after_post_snapshot.docs.length).toBe(1);
});
});
posts.js
const {admin, db } = require('../util/admin');
//admin.initializeApp(config); //my credentials
//const db = admin.firestore();
const { uuid } = require("uuidv4");
const {
success_response,
error_response
} = require("../util/validators");
exports.async_create_post = async (data, context) => {
try {
const images = [];
data.images.forEach((url) => {
images.push({
uid: uuid(),
url: url
});
})
const postRecord = {
body: data.body,
images: images,
last_updated: admin.firestore.FieldValue.serverTimestamp(),
like_count: 0,
comment_count: 0,
deleted: false,
author_id: data.author_id
};
const generatedToken = uuid();
await db
.collection("posts")
.doc(generatedToken)
.set(postRecord);
// return success_response();
return success_response(generatedToken);
} catch (error) {
console.log("Error in creation of post", error);
return error_response(error);
}
}
When I run the test in Webstorm IDE, with 1 terminal running Firebase emulators:start , I get the following error message.
console.log
Error in creation of post TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an instance of Object
at validateString (internal/validators.js:120:11)
at Object.basename (path.js:1156:5)
at GrpcClient.loadProto (/Users/isaac/Desktop/project/functions/node_modules/google-gax/src/grpc.ts:166:23)
at new FirestoreClient (/Users/isaac/Desktop/project/functions/node_modules/#google-cloud/firestore/build/src/v1/firestore_client.js:118:38)
at ClientPool.clientFactory (/Users/isaac/Desktop/project/functions/node_modules/#google-cloud/firestore/build/src/index.js:330:26)
at ClientPool.acquire (/Users/isaac/Desktop/project/functions/node_modules/#google-cloud/firestore/build/src/pool.js:87:35)
at ClientPool.run (/Users/isaac/Desktop/project/functions/node_modules/#google-cloud/firestore/build/src/pool.js:164:29)
at Firestore.request (/Users/isaac/Desktop/project/functions/node_modules/#google-cloud/firestore/build/src/index.js:961:33)
at WriteBatch.commit_ (/Users/isaac/Desktop/project/functions/node_modules/#google-cloud/firestore/build/src/write-batch.js:485:48)
at exports.async_create_post (/Users/isaac/Desktop/project/functions/handlers/posts.js:36:5) {
code: 'ERR_INVALID_ARG_TYPE'
}
at exports.async_create_post (/Users/isaac/Desktop/project/functions/handlers/posts.js:44:13)
Error: expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 0
<Click to see difference>
at Object.<anonymous> (/Users/isaac/Desktop/project/tests/handlers/posts.test.js:59:45)
Error in creation of post comes from the console.log("Error in creation of post", error); in posts.js, so the error is shown in the title of this post.
I want to know why calling the async_create_post from posts.test.js will cause this error and does not populate my database with an additional record as expected behaviour. Do inform me if more information is required to solve the problem.
Here are some code snippets that may give more context.
helpers.js [Copied from the repository]
const firebase = require("#firebase/testing");
const fs = require("fs");
module.exports.setup = async (auth, data) => {
const projectId = `rules-spec-${Date.now()}`;
const app = firebase.initializeTestApp({
projectId,
auth
});
const db = app.firestore();
// Apply the test rules so we can write documents
await firebase.loadFirestoreRules({
projectId,
rules: fs.readFileSync("firestore-test.rules", "utf8")
});
// write mock documents if any
if (data) {
for (const key in data) {
const ref = db.doc(key); // This means the key should point directly to a document
await ref.set(data[key]);
}
}
// Apply the actual rules for the project
await firebase.loadFirestoreRules({
projectId,
rules: fs.readFileSync("firestore.rules", "utf8")
});
return db;
// return firebase;
};
module.exports.teardown = async () => {
// Delete all apps currently running in the firebase simulated environment
Promise.all(firebase.apps().map(app => app.delete()));
};
// Add extensions onto the expect method
expect.extend({
async toAllow(testPromise) {
let pass = false;
try {
await firebase.assertSucceeds(testPromise);
pass = true;
} catch (error) {
// log error to see which rules caused the test to fail
console.log(error);
}
return {
pass,
message: () =>
"Expected Firebase operation to be allowed, but it was denied"
};
}
});
expect.extend({
async toDeny(testPromise) {
let pass = false;
try {
await firebase.assertFails(testPromise);
pass = true;
} catch (error) {
// log error to see which rules caused the test to fail
console.log(error);
}
return {
pass,
message: () =>
"Expected Firebase operation to be denied, but it was allowed"
};
}
});
index.js
const functions = require('firebase-functions');
const {
async_get_all_undeleted_posts,
async_get_post,
async_delete_post,
async_create_post
} = require('./handlers/posts');
exports.create_post = functions.https.onCall(async_create_post);
The error message means that a method of the path module (like path.join) expects one of its arguments to be a string but got something else.
I found the offending line by binary search commenting the program until the error was gone.
Maybe one of your modules uses path and you supply the wrong arguments.

Object defined outside fetch but not inside

I'm using tmi.js to get the data of a chat message on www.twitch.tv.
This is my code:
const tmi = require('tmi.js');
require('dotenv').config();
const options = {
options: {
debug: true
},
identity: {
username: process.env.OAUTH_USERNAME,
password: process.env.OAUTH_PASSWORD
},
connection: {
reconnect: true
},
channels: [`instak`]
};
const client = new tmi.client(options);
client.on(`chat`, (channel, userstate, message/*, self */) => {
console.log(userstate.username); //This logs my userstate, I can see the username which is passed on correctly.
switch (message) {
case `!kluiten`:
fetch(`http://localhost:8000/api/users/${userstate.username}`) // fetch from Express.js server
.then(response => response.json())
.then(result => {
console.log(`USERSTATE IS`, userstate);
client.action(channel, `${userstate[`display-name`]}, you've got ${result.instakluiten} instakluiten.`);
});
break;
default:
break;
}
});
// Connect the client to the server..
client.connect();
As I said in the commented part after the console.log(userstate), I get all the correct information I was expecting, so outside of the switch case and the fetch. However, when I log my userstate.username inside the fetch, userstate is undefined... I don't see why that happens, because when I did it in the front-end I had no problems... Now I'm doing it in node and it's undefined...
This looks like a common "you need to wait for your response"-problem, but I don't see a logical explanation for that being the problem because in the client.on(chat) userstate is defined... I'm troubled.

Categories

Resources