MQTT.JS connect function prepends "ws://localhost" to host URL - mqtt

I'm using MQTT.JS in a React component. For some reason when I try to connect to my HiveMQ host the connect function prepends the URL with "localhost"
Does anyone have an idea why this is happening?
First I called the connect function with an option object which contained a "host" key. The result was that the connect function ignored the host key and connected to ws://localhost directly
const mqttConnect = (mqttOptions) => {
console.log(JSON.stringify(mqttOptions, null, 4))
setClient(mqtt.connect( mqttOptions ))
}
The second thing I tried was to call the function naming the host explicitly by including it as the first argument. That resulted in the connect function prepending the host URL with ws://localhost
const mqttConnect = (mqttOptions) => {
console.log(JSON.stringify(mqttOptions, null, 4))
setClient(mqtt.connect( mqttOptions.host, mqttOptions) )
}
This is what the options object looks like:
mqttOptions: {
"host": "58xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0c.s1.eu.hivemq.cloud",
"port": "8884",
"clientId": "icodefusion",
"username": "icode",
"password": "password",
"protocol": "MQTT"
}
I included a snapshot of the error
https://imagizer.imageshack.com/img923/3495/UEAqem.jpg

The protocol key should come from the output of URL.parse() so should contain wss not MQTT for a MQTT over websockets.
I believe you have miss read the doc which mentions protocolId

Based on the config you are showing, this should be the correct way to call the connect function:
const mqttConnect = ({host, clientId, username, password}) => {
setClient(mqtt.connect(`wss://${host}`, {clientId, username, password}))
}
See https://www.npmjs.com/package/mqtt#api for details.

Related

connect from web to iot core using a custom authorizer

I'm trying to use a custom authorizer to authenticate a web client.
I have succesfully created a dedicated lambda and a custom authorizer. If I launch aws iot describe-authorizer --authorizer-name <authorizer-name> I can see
{
"authorizerDescription": {
"authorizerName": "<authorizer-name>",
"authorizerArn": "...",
"authorizerFunctionArn": "...",
"tokenKeyName": "<token-key-name>",
"tokenSigningPublicKeys": {
"<public-key-name>": "-----BEGIN PUBLIC KEY-----\n<public-key-content>\n-----END PUBLIC KEY-----"
},
"status": "ACTIVE",
"creationDate": "...",
"lastModifiedDate": "...",
"signingDisabled": false,
"enableCachingForHttp": false
}
}
Moreover I can test it succesfully:
$ aws iot test-invoke-authorizer --authorizer-name '<authorizer-name>' --token '<public-key-name>' --token-signature '<private-key-content>'
{
"isAuthenticated": true,
"principalId": "...",
"policyDocuments": [ "..." ],
"refreshAfterInSeconds": 600,
"disconnectAfterInSeconds": 3600
}
$
But I cannot connect using the browser.
I'm using aws-iot-device-sdk and according the SDK documentation I should set customAuthHeaders and/or customAuthQueryString (my understanding is that the latter should be used in web environment due to a limitation of the browsers) with the headers / queryparams X-Amz-CustomAuthorizer-Name, X-Amz-CustomAuthorizer-Signature and TestAuthorizerToken but no matter what combination I set for these values the iot endpoint always close the connection (I see a 1000 / 1005 code for the closed connection)
What I've written so far is
const CUSTOM_AUTHORIZER_NAME = '<authorizer-name>';
const CUSTOM_AUTHORIZER_SIGNATURE = '<private-key-content>';
const TOKEN_KEY_NAME = 'TestAuthorizerToken';
const TEST_AUTHORIZER_TOKEN = '<public-key-name>';
function f(k: string, v?: string, p: string = '&'): string {
if (!v)
return '';
return `${p}${encodeURIComponent(k)}=${encodeURIComponent(v)}`;
}
const client = new device({
region: '...',
clientId: '...',
protocol: 'wss-custom-auth' as any,
host: '...',
debug: true,
// customAuthHeaders: {
// 'X-Amz-CustomAuthorizer-Name': CUSTOM_AUTHORIZER_NAME,
// 'X-Amz-CustomAuthorizer-Signature': CUSTOM_AUTHORIZER_SIGNATURE,
// [TOKEN_KEY_NAME]: TEST_AUTHORIZER_TOKEN
// },
customAuthQueryString: `${f('X-Amz-CustomAuthorizer-Name', CUSTOM_AUTHORIZER_NAME, '?')}${f('X-Amz-CustomAuthorizer-Signature', CUSTOM_AUTHORIZER_SIGNATURE)}${f(TOKEN_KEY_NAME, TEST_AUTHORIZER_TOKEN)}`,
} as any);
As you can see I started having also doubts about the headers names!
After running my code I see that the client tries to do a GET to the host with the querystring that I wrote.
I also see that IoT core responds with a 101 Switching Protocols, and then that my client send the CONNECT command to IoT via websocket and then another packet from my browser to the backend system.
Then the connection is closed by IoT.
Looking at cloudwatch I cannot see any interaction with the lambda, it's like the request is blocked.
my doubts are:
first of all, is it possible to connect via mqtt+wss using only a custom auth, without cognito/certificates? keep in mind that I am able to use a cognito identity pool without errors, but I need to remove it.
is it correct that I just need to set up the customAuthQueryString parameter? my understanding is that this should be used on the web.
what are the values I should set up for the various headers/queryparams? X-Amz-CustomAuthorizer-Name is self explanatory, but I'm not sure about X-Amz-CustomAuthorizer-Signature (it's correct to fill it with the content of my private key?). moreover I'm not sure about the TestAuthorizerToken. Is it the correct key to set up?
I've also tried to run the custom_authorizer_connect of the sdk v2 but it's still not working, and I run out of ideas.
turns out the problem was in the permissions set on the backend systems.

Unable to connect to Firebase Emulator with iOS device

I am trying to connect with an iOS device to the Firebase Auth and RealTime Database Emulator.
The thing is, I can connect and use emulator through Firebase Admin using NodeJS on local machine (trough http://localhost:9000?ns=my-project).
Also I am able to connect with an iOS device to the remote Firebase server... But locally it doesn't work. It throws bunch of random errors, like this (when I try to complete registration/authentication):
Error Domain=NSURLErrorDomain Code=-1004 "Could not connect to the
server." NSLocalizedDescription=Could not connect to the server.,
NSErrorFailingURLStringKey=http://192.168.1.3:9099/www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=myKeyGoesHere
and
Optional(Error Domain=com.firebase.core Code=1 "Unable to get latest
value for query FQuerySpec (path: /news, params: { }), client offline
with no active listeners and no matching disk cache entries"
Here is firebase.json:
{
"database": {
"rules": "database.rules.json"
},
"emulators": {
"auth": {
"port": 9099
},
"database": {
"port": 9000
},
"ui": {
"enabled": true
}
}
}
I changed rules just in case:
{
"rules": {
".read": true,
".write": true
}
}
but its not that.
and here is how I try to connect to database in my iOS application(my FirebaseManager class):
init(){
Auth.auth().useEmulator(withHost:"192.168.1.3", port:9099)
}
private lazy var newsNodeRef:DatabaseReference? = {
guard let urlString = getBaseURL() else {return nil}
let node = LocalConstants.kNewsRef // this has value of 'news'
return Database.database(url: urlString).reference(withPath: node)
}()
private func getBaseURL()->String?{
let environment = Environment()
guard let connectionProtocol = environment.configuration(PlistKey.firebaseConnectionProtocol), let baseURL = environment.configuration(PlistKey.firebaseDatabaseURL) else {return nil}
let urlString = "\(connectionProtocol)://\(baseURL)"
return urlString // this produces something like 'http://192.168.1.3:9000?ns=my-project' (its fetched from Configuration Settings file based on selected environment)
}
the thing is, the exact same setup works on remote server, if I just change the environment(which automatically changes base url).
I have also allowed insecure http loads in info.plist, just to be sure if it is not that, but still doesn't work.
This is what I get in console when I run emulators:
What is the problem here?
I replied a little late 😊.
I saw the solution you found. It didn't work for me but I'm sure it has worked for a lot of people.
I found a solution too.
Actually, I couldn't see a problem for iOS 15. My problem was that it didn't work on iOS 14 and earlier.
Solution;
First, you need the MacBook's IP address.
To find the IP address;
You can access it right under System preferences -> Network -> Status.
Then we need to make some changes in the firebase.json file.
Adding “host” : “IP” for each part.
Overwrite the “host” part with the “port” part.
"emulators": {
"auth": {
"host": "192.168.1.11”,
"port": 9100
},
"functions": {
"host": "192.168.1.11”,
"port": 5002
},
"firestore": {
"host": "192.168.1.11”,
"port": 8081
},
"database": {
"host": "192.168.1.11",
"port": 9001
},
"storage": {
"host": "192.168.1.11",
"port": 9200
},
"ui": {
"enabled": true
}
Then we need to add in swift codes.
We need to write the IP address in the host part.
More precisely, we will replace the parts that say localhost with the IP address.
let settings = Firestore.firestore().settings
settings.host = "192.168.1.11:8081"
settings.isPersistenceEnabled = false
settings.isSSLEnabled = false
Firestore.firestore().settings = settings
Storage.storage().useEmulator(withHost:"192.168.1.11", port:9200)
Auth.auth().useEmulator(withHost:"192.168.1.11", port:9100)
let db = Database.database(url:"http://192.168.1.11:9001?ns=firebaseappname")
Functions.functions().useFunctionsEmulator(origin: "http://192.168.1.11:5002")
I think this solution will work in JS, android and other languages.
I would appreciate it if you tried this solution and let me know if it works.
It worked for me.
I actually solved it. The solution/problem, I don't even know how to declare it, was with Local Network Access prompt & permissions and its buggy behaviour (as well how I was trying to access my Mac by ip).
At first I didn't even see a prompt shows every time, but I guess it was related to a wrong setup of a port, host etc.
But when I correctly set local computer's ip and reverted firebase.json to it's default settings (which is what worked for me), the prompt started to jump out every time.
The thing is, prompt's behaviour seems broken, because instead of jumping before you try to access devices in a local network, it pops out after that action is made. Quite fast, but still after Auth system responded, which doesn't make sense.
Here, it can be confusing, cause error that is returned from a Firebase Auth system in the case when you didn't allow Local Network Access usage, doesn't really tell you much about real cause. See my ( original question) above to see the errors.
After that terrible flow, I allowed access trough the prompt. Once I did that, on every next 'api' call towards Emulator was successful. Worked like a charm.
The real problem here is Local Network Access prompt. Cause we don't have at all control over it, so we can't that easily trigger it, or easily get info what user have selected/chosen at the moment / or before. It's triggered by the system in certain conditions.
Luckily this is just for development :) but I hope it will be fixed/improved soon, cause it should.
I found a lot about this topic and its considered as a bug Local Network Access Prompt problems on Dev portal:
I was also faced the same problem while using the firebase auth in iOS simulator
then i change my code little bit
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
if (Platform.isAndroid) {
await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
}
runApp(const MyApp());
}
I have made a check for only android and it works for me
Inside this method you can see it only works for android.

what is MQTT url to my thingsboard instance

I have a thingsboard UI available at "thingsboard.MYDOMAIN.io:8080". I'm using NodeJS and MQTT.js. I can connect using the sample code on that page. But I don't know what URL i would use in that connect string. I've tried using that URL and putting MQTT:// on the front of it to no avail.
It's just one device coming into a thingsboard. I wouldn't think i would need to add anything other than the default thingsboard.
If someone could tell what the expect default URL would be?
The default MQTT port number is 1883 so something like
mqtt://thingsboard.mydomain.io:1883
would be a good starting point. You should even be able to leave the port number off e.g.
mqtt://thingsboard.mydomain.io
Connection code should be something like this:
var mqtt = require('mqtt');
const thingsboardHost = "127.0.0.1";
const ACCESS_TOKEN = "**********";
// Initialization of mqtt client using Thingsboard host and device access token
console.log('Connecting to: %s using access token: %s', thingsboardHost, ACCESS_TOKEN);
var client = mqtt.connect('mqtt://'+ thingsboardHost, { username: ACCESS_TOKEN });
The default MQTT port number is 1883, but you don't have to manually set that value. Take a look at the source code.
https://github.com/mqttjs/MQTT.js

Unable to connect to MQTT broker via ngx-mqtt in angular8

No matter what i do i can't connect to a mqtt broker via websocket in my angular application (trying in chrome and firefox).
For simplicity i'm using HiveMQ broker, i've published on the topic /gat/38/openReservationRequests some data
I've followed this medium article on how to connect to mqtt in angular using ngx-mqtt but for me it is not working.
In my app:
I've installed the module
npm install ngx-mqtt --save
i've added the configuration and set the module forRoot in my app.module.ts
...
export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = {
connectOnCreate: true,
hostname: 'broker.hivemq.com',
port: 8000,
path: '/gat/38/openReservationRequests',
protocol: 'ws',
};
...
imports: [
...
MqttModule.forRoot(MQTT_SERVICE_OPTIONS),
...
],
...
i'm executing this function inside the ngOnInit of app.component.ts
...
import { IMqttMessage, MqttConnectionState, MqttService } from 'ngx-mqtt';
...
constructor(private mqttService: MqttService) {
this.mqttService.state.subscribe((s: MqttConnectionState) => {
const status = s === MqttConnectionState.CONNECTED ? 'CONNECTED' : 'DISCONNECTED';
this.status.push(`Mqtt client connection status: ${status}`);
});
}
ngOnInit() {
this.subscription = this.mqttService
.observe('/gat/38/openReservationRequests')
.subscribe((message: IMqttMessage) => {
this.msg = message;
console.log('msg: ', message);
console.log('Message: ' + message.payload.toString() + 'for topic: ' + message.topic);
console.log('subscribed to topic: ' + /gat/38/openReservationRequests);
});
}
but i am always getting this error:
core.js:6014 ERROR TypeError: Cannot read property 'resubscribe' of undefined
at MqttClient.subscribe (mqtt.min.js:1)
at mqtt.service.js:211
at Observable._subscribe (using.js:8)
at Observable._trySubscribe (Observable.js:42)
at Observable.subscribe (Observable.js:28)
at FilterOperator.call (filter.js:13)
at Observable.subscribe (Observable.js:23)
at Observable.connect (ConnectableObservable.js:30)
at RefCountOperator.call (refCount.js:17)
at Observable.subscribe (Observable.js:23)
mqtt.min.js:1 WebSocket connection to 'ws://broker.hivemq.com:8000/gat/38/openReservationRequests' failed: Connection closed before receiving a handshake response
if i specify the clientId inside the MQTT_SERVICE_OPTIONS i still get the same error.
if i change the protocol to wss i get a different error:
core.js:6014 ERROR TypeError: Cannot read property 'resubscribe' of undefined
at MqttClient.subscribe (mqtt.min.js:1)
at mqtt.service.js:211
at Observable._subscribe (using.js:8)
at Observable._trySubscribe (Observable.js:42)
at Observable.subscribe (Observable.js:28)
at FilterOperator.call (filter.js:13)
at Observable.subscribe (Observable.js:23)
at Observable.connect (ConnectableObservable.js:30)
at RefCountOperator.call (refCount.js:17)
at Observable.subscribe (Observable.js:23)
mqtt.min.js:1 WebSocket connection to 'wss://broker.hivemq.com:8000/gat/38/openReservationRequests' failed: Error in connection establishment: net::ERR_CONNECTION_CLOSED
If i try to connect manually inside my app.component.ts ngOnInit before observing the topic:
this.mqttService.connect({
hostname: 'broker.hivemq.com',
port: 8000,
path: '/gat/38/openReservationRequests',
clientId: '34er23qwrfq42w3' //those are just random digits
});
i still get the error above.
For me it would be ideal to connect in some inner component (accessible after the user is authenticated) because i will have my private mqtt broker and the topic will depend on the logged user information.
I've tried any combination of protocol with/without cliendId etc but at this point i don't know what is wrong. I've already fully recompiled my app lots of times, i've tried publishing it on my test-server which has a ssl certificate but nothing changed.
Resolved thanks to #Anant Lalchandani i set the correct path.
The other problem was that '/mytopic' and 'mytopic' are indeed two different topic and i was using it wrong too.
This is my code, updated:
app.module.ts
export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = {
connectOnCreate: false,
hostname: 'broker.hivemq.com',
port: 8000,
path: '/mqtt'
};
appcomponent.ts (inside ngOnInit for now)
this.mqttService.connect({
hostname: 'broker.hivemq.com',
port: 8000,
path: '/mqtt',
clientId: '1234e3qer23rf'
});
this.mqttService.onConnect
.subscribe(
connack=> {
console.log('CONNECTED');
console.log(connack);
}
);
this.mqttService.observe('gat/38/openReservationRequests')
.subscribe((message: IMqttMessage) => {
this.msg = message;
console.log(new TextDecoder('utf-8').decode(message.payload));
});
I have checked the code snippets you shared in question.
In your app.module.ts, the path value should be '/mqtt'. You have set the topic as the value of path here. The topic can only be subscribed/published. As you are using a topic as a path value at the time of connecting to a websocket, your application will not be able to connect to websocket at the first place.
The reason why we need to use /mqtt as a path is it specifies you are sending MQTT messages over the WebSocket protocol.
The documentation of HiveMQ itself stated to use the path as '/mqtt' in its example. You can check the documentation here.

best way to tell swaggerui where the host is

When I build my swagger.json file I do not know which host to use. However I can work it out when my page that hosts swaggerui loads (in fact I might want to offer the user a choice). I hoped to see an options.host on the config for the swaggerUI object - I dont see one. Is there an existing way of doing this that I cant find or do I simply have to hack my way through the code and add this capability (pointers to the best place to do it would be welcome)
Swagger has a built-in json definition for host config, or can accept multiple inputs.
{
"swagger": "2.0",
"info": {
"title": "Why API",
"description": "Don't make that mistake again",
"version": "0.0.1"
},
"host": "127.0.0.1:3000",
"schemes": [
"https"
]
}
Or
"host": "test.mydomain.com:3000",
"schemes": [
"https"
],
Or you can have a dynamic host by defining a var and calling a hostname or machine name or other environment variables.
dynamic example
if (typeof this.host === 'undefined' || this.host === '') {
this.host = location.host;
}
if (location.port) {
this.host = this.host + ':' + location.port;
}
Here is what I do, since the loaded in document is just a JSON object:
var swaggerDoc = require('./api/swagger.json');
if (process.env.NODE_ENV === 'development') {
swaggerDoc.host="localhost:" + process.env.PORT
}
// Initialize the Swagger middleware
swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
// Other initialization
}
This way you don't pollute your API specification with development environment configuration.
In recent versions of Swagger UI it's possible to do this, for example in onComplete:
window.swaggerUi.api.setHost("your.host:4242");
If you are hosting it on same app server, just remove the host key from the json and provide relative path in key "basePath". as -
"basePath": "/rest/createcampaign".
two ways
One modify swagger.js so that it accepts host option. swagger-UI passes options to swagger-js so that works. I submitted a pull to swagger-js with this fix
Second choice is that swagger-UI accepts a 'spec' parameter. This means that the hosting page can load the swagger.json file, JSON.parse it , set 'host' in it and then pass to swaggerUi constructor. This is harder for the caller but doesn't require code changes to swagger
There are 2 ways which you can follow:
Load the index.html and replace the https://petstore.swagger.io/v2/swagger.json with the url where your swagger.json is hosting.
you can expose the local swagger.json on the same server.
When you follow this approach make sure you include static files in the end of above steps.
If you don't want to expose swagger.json as an API, copy the sawgger.json in the dist folder of swagger. The index.html and swagger.json must be in same repository for this. It is inside the index.html of dist folder of swagger-ui-dist.
const ui = SwaggerUIBundle({
spec: location.host,
url: "swagger.json",
dom_id: "#swagger-ui",
deepLinking: true,
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
layout: "StandaloneLayout"
});
// End Swagger UI call region
window.ui = ui;
};
Second way, host parameter in the swagger.yaml/swagger.json either make it empty
"host":""
or omit host parameter.
Swagger take the server's host as host where the swagger ui is hosted.
This is how I did this using the Java client:
DefaultApi api = new DefaultApi();
api.getApiClient().setBasePath("http://localhost:8080");
//call the API
if you use OpenApi 3.0
Variables can have arbitrary values, or may be restricted to an enum. In any case, a default value is required, which will be used if the client does not supply a value.
swagger doc
In the swagger-ui there will be the default value but the field is an input field so it is possible to customize it at runtime.
Swagger UI express itself is giving the following snippet it's getting the current host and publish dynamic with host
app.use('/api-docs', function(req, res, next){
swaggerDocument.host = req.get('host');
req.swaggerDoc = swaggerDocument;
next();
}, swaggerUi.serve, swaggerUi.setup());

Resources