Skip to content
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7146c33
udpate metrics for mqtt3
xiazhvera Jan 6, 2026
60ae90e
update aws-c-mqtt
xiazhvera Jan 6, 2026
9ca5d2d
set metrics for mqtt5 client
xiazhvera Jan 7, 2026
daeaf35
fix libray import
xiazhvera Jan 7, 2026
c5dcafb
update test as we are removing metrics from username
xiazhvera Jan 7, 2026
2ecc0b4
fix metrics implementation
xiazhvera Jan 7, 2026
ff625a4
fix dependency path
xiazhvera Jan 7, 2026
34f5f77
fix mqtt5 metrics. The mqtt5 custom auth should fail with this commit
xiazhvera Jan 7, 2026
6bf3e55
update custom auth test
xiazhvera Jan 7, 2026
33676ad
update browser metrics
xiazhvera Jan 7, 2026
c48c9cb
clean up
xiazhvera Jan 7, 2026
c839553
add enable_metrics option for mqtt3 browser
xiazhvera Jan 8, 2026
9f4d041
revert mqtt3 metrics changes
xiazhvera Jan 8, 2026
e7eee32
remove enable metrics from browser
xiazhvera Jan 8, 2026
d2d68b0
remove enablemetrics from browser
xiazhvera Jan 8, 2026
402af38
clean up
xiazhvera Jan 8, 2026
5744019
fix basic auth test
xiazhvera Jan 8, 2026
503b0be
Merge branch 'main' into iot_metrics
xiazhvera Jan 14, 2026
ddde402
update to append metrics
xiazhvera Jan 15, 2026
34952e5
fix test SDK naming
xiazhvera Jan 15, 2026
c9cc6b9
Merge branch 'main' into iot_metrics
xiazhvera Jan 15, 2026
0a38686
kick ci
xiazhvera Jan 15, 2026
969d97e
Merge branch 'iot_metrics' of https://github.com/awslabs/aws-crt-node…
xiazhvera Jan 15, 2026
956dbbe
kick ci
xiazhvera Jan 15, 2026
6727fd3
and retry
xiazhvera Jan 16, 2026
93fcba5
Merge branch 'main' into iot_metrics
xiazhvera Jan 30, 2026
892c18f
set metrics should not fail on client
xiazhvera Feb 12, 2026
ec06c63
do not fail mqtt5 client with invalid metrics
xiazhvera Feb 12, 2026
1823b66
update metrics structure
xiazhvera Feb 13, 2026
16fd073
update aws-c-mqtt
xiazhvera Feb 17, 2026
970dfd1
wip mqtt3 basic auth failure
xiazhvera Mar 2, 2026
f06e2b9
add mqtt5 custom auth with metrics
xiazhvera Mar 2, 2026
efadb0c
fix custom auth username/pw
xiazhvera Mar 2, 2026
196522d
clean up mqtt3 metrics enabled test
xiazhvera Mar 2, 2026
21c9c5d
Merge branch 'main' into iot_metrics
xiazhvera Mar 9, 2026
6c8b2dc
update comments for enable metrics
xiazhvera Mar 18, 2026
7693055
update cr comments
xiazhvera Mar 27, 2026
c831255
Merge branch 'main' into iot_metrics
xiazhvera Apr 3, 2026
d20b464
create MqttConnectionConfigBase to share
xiazhvera Apr 20, 2026
7a8dc39
export MqttConnectionConfigBase
xiazhvera Apr 20, 2026
31c2f13
Merge branch 'main' of https://github.com/awslabs/aws-crt-nodejs into…
xiazhvera Apr 20, 2026
b1cb699
update mqtt5 client options
xiazhvera Apr 20, 2026
51fa175
clean up
xiazhvera Apr 20, 2026
49e421a
lint
xiazhvera Apr 20, 2026
22c7c27
fix test
xiazhvera Apr 20, 2026
0486d91
fix test
xiazhvera Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/browser/aws_iot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,12 +328,12 @@ export class AwsIotMqttConnectionConfigBuilder {

// Add the metrics string
if (this.params.username == undefined || this.params.username == null || this.params.username == "") {
this.params.username = "?SDK=NodeJSv2&Version="
this.params.username = "?SDK=IoTDeviceSDK/JS&Version="
} else {
if (this.params.username.indexOf("?") != -1) {
this.params.username += "&SDK=NodeJSv2&Version="
this.params.username += "&SDK=IoTDeviceSDK/JS&Version="
} else {
this.params.username += "?SDK=NodeJSv2&Version="
this.params.username += "?SDK=IoTDeviceSDK/JS&Version="
}
}
this.params.username += platform.crt_version()
Expand Down
13 changes: 6 additions & 7 deletions lib/common/aws_iot_shared.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,29 @@ jest.setTimeout(10000);
test('Aws IoT Mqtt5 Username Construction - No Custom Auth', async () => {
let finalUsername : string = iot_shared.buildMqtt5FinalUsername(undefined);

expect(finalUsername).toEqual(expect.stringContaining("?SDK=NodeJSv2&Version="));
expect(finalUsername).toEqual(expect.stringContaining("?SDK=IoTDeviceSDK/JS&Version="));
});

test('Aws IoT Mqtt5 Username Construction - Empty custom auth', async () => {
let finalUsername : string = iot_shared.buildMqtt5FinalUsername({});

expect(finalUsername).toEqual(expect.stringContaining("?SDK=NodeJSv2&Version="));
expect(finalUsername).toEqual(expect.stringContaining("?SDK=IoTDeviceSDK/JS&Version="));
});


test('Aws IoT Mqtt5 Username Construction - Simple username', async () => {
let finalUsername : string = iot_shared.buildMqtt5FinalUsername({
username: "Derp"
});

expect(finalUsername).toEqual(expect.stringContaining("Derp?SDK=NodeJSv2&Version="));
expect(finalUsername).toEqual(expect.stringContaining("Derp?SDK=IoTDeviceSDK/JS&Version="));
});

test('Aws IoT Mqtt5 Username Construction - Query param username', async () => {
let finalUsername : string = iot_shared.buildMqtt5FinalUsername({
username: "Derp?Param1=Value1"
});

expect(finalUsername).toEqual(expect.stringContaining("Derp?Param1=Value1&SDK=NodeJSv2&Version="));
expect(finalUsername).toEqual(expect.stringContaining("Derp?Param1=Value1&SDK=IoTDeviceSDK/JS&Version="));
});

test('Aws IoT Mqtt5 Username Construction - Authorizer Name', async () => {
Expand All @@ -42,7 +41,7 @@ test('Aws IoT Mqtt5 Username Construction - Authorizer Name', async () => {
authorizerName: "MyAuthorizer"
});

expect(finalUsername).toEqual(expect.stringContaining("Hello?x-amz-customauthorizer-name=MyAuthorizer&SDK=NodeJSv2&Version="));
expect(finalUsername).toEqual(expect.stringContaining("Hello?x-amz-customauthorizer-name=MyAuthorizer&SDK=IoTDeviceSDK/JS&Version="));
});

test('Aws IoT Mqtt5 Username Construction - Token Signing', async () => {
Expand All @@ -54,7 +53,7 @@ test('Aws IoT Mqtt5 Username Construction - Token Signing', async () => {
tokenSignature: "SignedToken"
});

expect(finalUsername).toEqual(expect.stringContaining("Hello?x-amz-customauthorizer-name=MyAuthorizer&MyToken=TheToken&x-amz-customauthorizer-signature=SignedToken&SDK=NodeJSv2&Version="));
expect(finalUsername).toEqual(expect.stringContaining("Hello?x-amz-customauthorizer-name=MyAuthorizer&MyToken=TheToken&x-amz-customauthorizer-signature=SignedToken&SDK=IoTDeviceSDK/JS&Version="));
});

test('Aws IoT Mqtt5 Username Construction Failure - Missing token key name', async () => {
Expand Down
5 changes: 3 additions & 2 deletions lib/common/aws_iot_shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,11 @@ function addParam(paramName: string, paramValue: string | undefined, paramSet: [
* and SDK metrics properties.
*
* @param customAuthConfig intended AWS IoT custom auth client configuration
* @param appendMetrics manually append SDK metrics, set to true while we build username for browser.
Comment thread
xiazhvera marked this conversation as resolved.
Outdated
*
* @internal
*/
export function buildMqtt5FinalUsername(customAuthConfig?: MqttConnectCustomAuthConfig) : string {
export function buildMqtt5FinalUsername(customAuthConfig?: MqttConnectCustomAuthConfig): string {

let path : string = "";
let paramList : [string, string][] = [];
Expand Down Expand Up @@ -241,7 +242,7 @@ export function buildMqtt5FinalUsername(customAuthConfig?: MqttConnectCustomAuth
}
}

paramList.push(["SDK", "NodeJSv2"]);
paramList.push(["SDK", "IoTDeviceSDK/JS"]);
paramList.push(["Version", platform.crt_version()]);

return (path ?? "") + "?" + paramList.map((value : [string, string]) => `${value[0]}=${value[1]}`).join("&");
Expand Down
11 changes: 11 additions & 0 deletions lib/common/mqtt_shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ export function normalize_payload_to_buffer(payload: any): Buffer {
export const DEFAULT_KEEP_ALIVE : number = 1200;


/**
* IoT Device SDK Metrics Structure
* @internal
*/
export class AwsIoTDeviceSDKMetrics {
/**
* Name of the library
*/
libraryName: string = "IoTDeviceSDK/JS";
Comment thread
xiazhvera marked this conversation as resolved.
Outdated
}

function isValidTopicInternal(topic: string, isFilter: boolean) : boolean {
if (topic.length === 0 || topic.length > 65535) {
return false;
Expand Down
13 changes: 0 additions & 13 deletions lib/native/aws_iot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { MqttClientConnection, MqttConnectionConfig, MqttWill} from "./mqtt";
import { DEFAULT_RECONNECT_MIN_SEC, DEFAULT_RECONNECT_MAX_SEC} from "../common/mqtt"
import * as io from "./io";
import { TlsContextOptions } from "./io";
import * as platform from '../common/platform';
import { HttpProxyOptions } from "./http";
import { WebsocketOptionsBase } from "../common/auth";
import { CrtError } from "./error";
Expand Down Expand Up @@ -504,18 +503,6 @@ export class AwsIotMqttConnectionConfigBuilder {
this.params.tls_ctx = new io.ClientTlsContext(this.tls_ctx_options);
}

// Add the metrics string
if (iot_shared.is_string_and_not_empty(this.params.username) == false) {
this.params.username = "?SDK=NodeJSv2&Version="
} else {
if (this.params.username?.indexOf("?") != -1) {
this.params.username += "&SDK=NodeJSv2&Version="
} else {
this.params.username += "?SDK=NodeJSv2&Version="
}
}
this.params.username += platform.crt_version()

return this.params;
}
}
3 changes: 3 additions & 0 deletions lib/native/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { PublishCompletionResult } from "../common/mqtt5";
import * as eventstream from "./eventstream";
import { ConnectionStatistics } from "./mqtt";
import * as mqtt_request_response from "../native/mqtt_request_response";
import { AwsIoTDeviceSDKMetrics } from "../common/mqtt_shared";


/**
Expand Down Expand Up @@ -204,6 +205,7 @@ export function mqtt5_client_new(
socket_options?: NativeHandle,
tls_ctx?: NativeHandle,
proxy_options?: NativeHandle,
metrics? : AwsIoTDeviceSDKMetrics
): NativeHandle;

/** @internal */
Expand Down Expand Up @@ -251,6 +253,7 @@ export function mqtt_client_connection_new(
websocket_handshake_transform?: (request: HttpRequest, done: (error_code?: number) => void) => void,
reconnect_min_sec?: number,
reconnect_max_sec?: number,
metrics? : AwsIoTDeviceSDKMetrics
): NativeHandle;

/** @internal */
Expand Down
33 changes: 31 additions & 2 deletions lib/native/mqtt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,40 @@ test_env.conditional_test(test_env.AWS_IOT_ENV.mqtt311_is_valid_direct_auth_mqtt
clean_session: true,
username: test_env.AWS_IOT_ENV.MQTT311_BASIC_AUTH_USERNAME,
password: test_env.AWS_IOT_ENV.MQTT311_BASIC_AUTH_PASSWORD,
socket_options: new SocketOptions()
socket_options: new SocketOptions(),
enable_metrics: false
}
await test_connection(config, new MqttClient(new ClientBootstrap()));
})
});

test_env.conditional_test(test_env.AWS_IOT_ENV.mqtt311_is_valid_direct_auth_mqtt())('MQTT311 Connection - basic auth with metrics', async () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this against IoT Core; as it stands this will only get run in codebuild

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test cannot run against IoT Core, as IoT Core will rule out the metrics string. The test actually uses the connection failure to verify that the metrics are appended.

await retry.networkTimeoutRetryWrapper( async () => {
const config: MqttConnectionConfig = {
client_id: `node-mqtt-unit-test-${uuid()}`,
host_name: test_env.AWS_IOT_ENV.MQTT311_DIRECT_AUTH_MQTT_HOST,
port: parseInt(test_env.AWS_IOT_ENV.MQTT311_DIRECT_AUTH_MQTT_PORT),
clean_session: true,
username: test_env.AWS_IOT_ENV.MQTT311_BASIC_AUTH_USERNAME,
password: test_env.AWS_IOT_ENV.MQTT311_BASIC_AUTH_PASSWORD,
socket_options: new SocketOptions(),
enable_metrics: true
}

const client = new MqttClient(new ClientBootstrap())
const connection = client.new_connection(config);

let onConnectionFailureCalled = false;
connection.on('connection_failure', async (callback_data) => {
onConnectionFailureCalled = true;
})

const connected = connection.connect();
await expect(connected).rejects.toBeDefined();
await expect(onConnectionFailureCalled).toBeTruthy();
})
});

test_env.conditional_test(test_env.AWS_IOT_ENV.mqtt311_is_valid_direct_tls_mqtt())('MQTT311 Connection - TLS', async () => {
await retry.networkTimeoutRetryWrapper( async () => {
const tls_ctx_options = new TlsContextOptions();
Expand Down Expand Up @@ -194,7 +222,8 @@ test_env.conditional_test(test_env.AWS_IOT_ENV.mqtt311_is_valid_ws_auth_mqtt())(
use_websocket: true,
username: test_env.AWS_IOT_ENV.MQTT311_BASIC_AUTH_USERNAME,
password: test_env.AWS_IOT_ENV.MQTT311_BASIC_AUTH_PASSWORD,
socket_options: new SocketOptions()
socket_options: new SocketOptions(),
enable_metrics: false
}
config.websocket_handshake_transform = async (request, done) => {
done();
Expand Down
7 changes: 7 additions & 0 deletions lib/native/mqtt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ export interface MqttConnectionConfig {
/** Optional proxy options */
proxy_options?: HttpProxyOptions;

/** Optional enable Aws IoT SDK Metrics. The metrics includes SDK name, version, and platform.
*
* @group Node-only
Comment thread
xiazhvera marked this conversation as resolved.
Outdated
*/
enable_metrics?: boolean;

/**
* Optional function to transform websocket handshake request.
* If provided, function is called each time a websocket connection is attempted.
Expand Down Expand Up @@ -321,6 +327,7 @@ export class MqttClientConnection extends NativeResourceMixin(BufferedEventEmitt
config.websocket_handshake_transform,
min_sec,
max_sec,
config.enable_metrics == false ? undefined : new crt.AwsIoTDeviceSDKMetrics()
));
this.tls_ctx = config.tls_ctx;
crt_native.mqtt_client_connection_on_message(this.native_handle(), this._on_any_publish.bind(this));
Expand Down
19 changes: 18 additions & 1 deletion lib/native/mqtt5.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,15 @@ function createNodeSpecificTestConfig (testType: test_utils.SuccessfulConnection
HttpProxyConnectionType.Tunneling);
}

let enableMetrics = test_utils.ClientEnvironmentalConfig.isTestBasicAuth(testType) == false
Comment thread
xiazhvera marked this conversation as resolved.
Outdated

return {
hostName: "unknown",
port: 0,
tlsCtx: tlsCtx,
httpProxyOptions: proxyOptions,
websocketHandshakeTransform: wsTransform
websocketHandshakeTransform: wsTransform,
enableMetrics: enableMetrics
};
}

Expand Down Expand Up @@ -306,6 +309,20 @@ test_utils.conditional_test(test_utils.ClientEnvironmentalConfig.hasValidSuccess
}));
});

test_utils.conditional_test(test_utils.ClientEnvironmentalConfig.hasValidSuccessfulConnectionTestConfig(test_utils.SuccessfulConnectionTestType.WS_MQTT_WITH_BASIC_AUTH))('Connection Failure - Websocket Mqtt connection with basic authentication with metrics', async () => {
await test_utils.testFailedConnection(new mqtt5.Mqtt5Client({
hostName: test_utils.ClientEnvironmentalConfig.WS_MQTT_BASIC_AUTH_HOST,
port: test_utils.ClientEnvironmentalConfig.WS_MQTT_BASIC_AUTH_PORT,
websocketHandshakeTransform: (request: HttpRequest, done: (error_code?: number) => void) => { done(0); },
connectProperties : {
keepAliveIntervalSeconds: 1200,
username: test_utils.ClientEnvironmentalConfig.BASIC_AUTH_USERNAME,
password: test_utils.ClientEnvironmentalConfig.BASIC_AUTH_PASSWORD,
},
enableMetrics: true,
}));
});

test_utils.conditional_test(test_utils.ClientEnvironmentalConfig.hasValidSuccessfulConnectionTestConfig(test_utils.SuccessfulConnectionTestType.WS_MQTT))('Connection Failure - Websocket MQTT Bad Handshake', async () => {
let tls_ctx_opt : TlsContextOptions = new TlsContextOptions();
tls_ctx_opt.verify_peer = false;
Expand Down
11 changes: 10 additions & 1 deletion lib/native/mqtt5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,14 @@ export interface Mqtt5ClientConfig {
* @group Node-only
*/
extendedValidationAndFlowControlOptions? : ClientExtendedValidationAndFlowControl;


/**
* Options for enable/disable Aws IoT Metrics. The metrics includes SDK name, version, and platform.
*
* @group Node-only
Comment thread
xiazhvera marked this conversation as resolved.
*/
enableMetrics? : boolean
}

/**
Expand Down Expand Up @@ -295,7 +303,8 @@ export class Mqtt5Client extends NativeResourceMixin(BufferedEventEmitter) imple
config.clientBootstrap ? config.clientBootstrap.native_handle() : null,
config.socketOptions ? config.socketOptions.native_handle() : null,
config.tlsCtx ? config.tlsCtx.native_handle() : null,
config.httpProxyOptions ? config.httpProxyOptions.create_native_handle() : null
config.httpProxyOptions ? config.httpProxyOptions.create_native_handle() : null,
config.enableMetrics == false ? undefined : new mqtt_shared.AwsIoTDeviceSDKMetrics()
));
}

Expand Down
25 changes: 23 additions & 2 deletions source/mqtt5_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <aws/http/proxy.h>
#include <aws/io/socket.h>
#include <aws/io/tls_channel_handler.h>
#include <aws/mqtt/mqtt.h>
#include <aws/mqtt/v5/mqtt5_client.h>
#include <aws/mqtt/v5/mqtt5_packet_storage.h>
#include <aws/mqtt/v5/mqtt5_types.h>
Expand Down Expand Up @@ -2103,7 +2104,7 @@ static void s_init_default_mqtt5_client_options(

napi_value aws_napi_mqtt5_client_new(napi_env env, napi_callback_info info) {

napi_value node_args[12];
napi_value node_args[13];
size_t num_args = AWS_ARRAY_SIZE(node_args);
napi_value *arg = &node_args[0];
AWS_NAPI_CALL(env, napi_get_cb_info(env, info, &num_args, node_args, NULL, NULL), {
Expand All @@ -2112,7 +2113,7 @@ napi_value aws_napi_mqtt5_client_new(napi_env env, napi_callback_info info) {
});

if (num_args != AWS_ARRAY_SIZE(node_args)) {
napi_throw_error(env, NULL, "mqtt5_client_new - needs exactly 12 arguments");
napi_throw_error(env, NULL, "mqtt5_client_new - needs exactly 13 arguments");
return NULL;
}

Expand Down Expand Up @@ -2145,6 +2146,12 @@ napi_value aws_napi_mqtt5_client_new(napi_env env, napi_callback_info info) {
struct aws_napi_mqtt5_client_creation_storage options_storage;
AWS_ZERO_STRUCT(options_storage);

struct aws_mqtt_iot_metrics metrics;
AWS_ZERO_STRUCT(metrics);

struct aws_byte_buf libraryName;
AWS_ZERO_STRUCT(libraryName);

s_init_default_mqtt5_client_options(&client_options, &connect_options);

/* Arg #1: the mqtt5 client */
Expand Down Expand Up @@ -2328,6 +2335,19 @@ napi_value aws_napi_mqtt5_client_new(napi_env env, napi_callback_info info) {
client_options.http_proxy_options = aws_napi_get_http_proxy_options(proxy_binding);
}

// Set metrics
napi_value node_metrics = *arg++;
if (!aws_napi_is_null_or_undefined(env, node_metrics)) {
napi_value node_libraryName = NULL;
if (napi_get_named_property(env, node_metrics, "libraryName", &node_libraryName) == napi_ok &&
Comment thread
xiazhvera marked this conversation as resolved.
Outdated
aws_byte_buf_init_from_napi(&libraryName, env, node_libraryName) == AWS_OP_SUCCESS) {
metrics.library_name = aws_byte_cursor_from_buf(&libraryName);
client_options.metrics = &metrics;
} else {
AWS_LOGF_DEBUG(AWS_LS_NODEJS_CRT_GENERAL, "Failed to set metrics, continuing without metrics");
}
}

client_options.publish_received_handler = s_on_publish_received;
client_options.publish_received_handler_user_data = binding;

Expand Down Expand Up @@ -2362,6 +2382,7 @@ napi_value aws_napi_mqtt5_client_new(napi_env env, napi_callback_info info) {
cleanup:

s_aws_napi_mqtt5_client_creation_storage_clean_up(&options_storage);
aws_byte_buf_clean_up(&libraryName);

return napi_client_wrapper;
}
Expand Down
Loading
Loading