UNPKG

aws-crt

Version:

NodeJS/browser bindings to the aws-c-* libraries

855 lines (732 loc) 32.7 kB
/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ /** * Browser specific MQTT5 client implementation * * DEVELOPER PREVIEW DISCLAIMER * * MQTT5 support is currently in **developer preview**. We encourage feedback at all times, but feedback during the * preview window is especially valuable in shaping the final product. During the preview period we may make * backwards-incompatible changes to the public API, but in general, this is something we will try our best to avoid. * * [MQTT5 Client User Guide](https://www.github.com/awslabs/aws-crt-nodejs/blob/main/MQTT5-UserGuide.md) * * @packageDocumentation * @module mqtt5 * @mergeTarget * */ import { BufferedEventEmitter } from "../common/event"; import * as mqtt from "mqtt"; /* The mqtt-js external dependency */ import * as mqtt5 from "../common/mqtt5"; import * as mqtt5_packet from "../common/mqtt5_packet" import { CrtError } from "./error"; import * as WebsocketUtils from "./ws"; import * as mqtt_utils from "./mqtt5_utils"; import * as mqtt_shared from "../common/mqtt_shared"; import * as auth from "./auth"; export * from "../common/mqtt5"; export * from '../common/mqtt5_packet'; /** * Factory function that allows the user to completely control the url used to form the websocket handshake * request. */ export type Mqtt5WebsocketUrlFactory = () => string; /** * Type of url to construct when establishing an MQTT5 connection over websockets */ export enum Mqtt5WebsocketUrlFactoryType { /** * Websocket connection over plain-text with no additional handshake transformation */ Ws = 1, /** * Websocket connection over TLS with no additional handshake transformation */ Wss = 2, /** * Websocket connection over TLS with a handshake signed by the Aws Sigv4 signing process */ Sigv4 = 3, /** * Websocket connection whose url is formed by a user-supplied callback function */ Custom = 4 } /** * Websocket factory options discriminated union variant for untransformed connections over plain-text */ export interface Mqtt5WebsocketUrlFactoryWsOptions { urlFactory: Mqtt5WebsocketUrlFactoryType.Ws; }; /** * Websocket factory options discriminated union variant for untransformed connections over TLS */ export interface Mqtt5WebsocketUrlFactoryWssOptions { urlFactory: Mqtt5WebsocketUrlFactoryType.Wss; }; /** * Websocket factory options discriminated union variant for untransformed connections over TLS signed by * the AWS Sigv4 signing process. */ export interface Mqtt5WebsocketUrlFactorySigv4Options { urlFactory : Mqtt5WebsocketUrlFactoryType.Sigv4; /** * AWS Region to sign against. */ region?: string; /** * Provider to source AWS credentials from */ credentialsProvider: auth.CredentialsProvider; } /** * Websocket factory options discriminated union variant for arbitrarily transformed handshake urls. */ export interface Mqtt5WebsocketUrlFactoryCustomOptions { urlFactory: Mqtt5WebsocketUrlFactoryType.Custom; customUrlFactory: Mqtt5WebsocketUrlFactory; }; /** * Union of all websocket factory option possibilities. */ export type Mqtt5WebsocketUrlFactoryOptions = Mqtt5WebsocketUrlFactoryWsOptions | Mqtt5WebsocketUrlFactoryWssOptions | Mqtt5WebsocketUrlFactorySigv4Options | Mqtt5WebsocketUrlFactoryCustomOptions; /** * Browser-specific websocket configuration options for connection establishment */ export interface Mqtt5WebsocketConfig { /** * Options determining how the websocket url is created. */ urlFactoryOptions : Mqtt5WebsocketUrlFactoryOptions; /** * Opaque options set passed through to the underlying websocket implementation regardless of url factory. * Use this to control proxy settings amongst other things. */ wsOptions?: any; } /** * Configuration options for mqtt5 client creation. */ export interface Mqtt5ClientConfig { /** * Host name of the MQTT server to connect to. */ hostName: string; /** * Network port of the MQTT server to connect to. */ port: number; /** * Controls how the MQTT5 client should behave with respect to MQTT sessions. */ sessionBehavior? : mqtt5.ClientSessionBehavior; /** * Controls how the reconnect delay is modified in order to smooth out the distribution of reconnection attempt * timepoints for a large set of reconnecting clients. */ retryJitterMode? : mqtt5.RetryJitterType; /** * Minimum amount of time to wait to reconnect after a disconnect. Exponential backoff is performed with jitter * after each connection failure. */ minReconnectDelayMs? : number; /** * Maximum amount of time to wait to reconnect after a disconnect. Exponential backoff is performed with jitter * after each connection failure. */ maxReconnectDelayMs? : number; /** * Amount of time that must elapse with an established connection before the reconnect delay is reset to the minimum. * This helps alleviate bandwidth-waste in fast reconnect cycles due to permission failures on operations. */ minConnectedTimeToResetReconnectDelayMs? : number; /** * All configurable options with respect to the CONNECT packet sent by the client, including the will. These * connect properties will be used for every connection attempt made by the client. */ connectProperties?: mqtt5_packet.ConnectPacket; /** * Overall time interval to wait to establish an MQTT connection. If a complete MQTT connection (from socket * establishment all the way up to CONNACK receipt) has not been established before this timeout expires, * the connection attempt will be considered a failure. */ connectTimeoutMs? : number; /** * Options for the underlying websocket connection * * @group Browser-only */ websocketOptions?: Mqtt5WebsocketConfig; } /** * @internal * * Mqtt-js only supports reconnect on a fixed delay. * * This helper class allows for variable time-delay rescheduling of reconnect attempts by implementing the * reconnect delay options supported by the native client. Variable-delay reconnect actually happens by configuring * the mqtt-js client to have a much longer reconnect delay than our configured maximum and then letting this class * "interrupt" that long reconnect delay with the real, shorter wait-then-connect each time. */ class ReconnectionScheduler { private connectionFailureCount: number; private lastReconnectDelay: number | undefined; private resetConnectionFailureCountTask : ReturnType<typeof setTimeout> | undefined; private reconnectionTask : ReturnType<typeof setTimeout> | undefined; constructor(private browserClient: mqtt.MqttClient, private clientConfig: Mqtt5ClientConfig) { this.connectionFailureCount = 0; this.lastReconnectDelay = 0; this.resetConnectionFailureCountTask = undefined; this.reconnectionTask = undefined; this.lastReconnectDelay = undefined; } /** * Invoked by the client when a successful connection is established. Schedules the task that will reset the * delay if a configurable amount of time elapses with a good connection. */ onSuccessfulConnection() : void { this.clearTasks(); this.resetConnectionFailureCountTask = setTimeout(() => { this.connectionFailureCount = 0; this.lastReconnectDelay = undefined; }, this.clientConfig.minConnectedTimeToResetReconnectDelayMs ?? mqtt_utils.DEFAULT_MIN_CONNECTED_TIME_TO_RESET_RECONNECT_DELAY_MS); } /** * Invoked by the client after a disconnection or connection failure occurs. Schedules the next reconnect * task. */ onConnectionFailureOrDisconnection() : void { this.clearTasks(); let nextDelay : number = this.calculateNextReconnectDelay(); this.lastReconnectDelay = nextDelay; this.connectionFailureCount += 1; this.reconnectionTask = setTimeout(async () => { let wsOptions = this.clientConfig.websocketOptions; if (wsOptions && wsOptions.urlFactoryOptions.urlFactory == Mqtt5WebsocketUrlFactoryType.Sigv4) { let sigv4Options = wsOptions.urlFactoryOptions as Mqtt5WebsocketUrlFactorySigv4Options; if (sigv4Options.credentialsProvider) { await sigv4Options.credentialsProvider.refreshCredentials(); } } this.browserClient.reconnect(); }, nextDelay); } /** * Resets any reconnect/clear-delay tasks. */ clearTasks() : void { if (this.reconnectionTask) { clearTimeout(this.reconnectionTask); } if (this.resetConnectionFailureCountTask) { clearTimeout(this.resetConnectionFailureCountTask); } } private randomInRange(min: number, max: number) : number { return min + (max - min) * Math.random(); } /** * Computes the next reconnect delay based on the Jitter/Retry configuration. * Implements jitter calculations in https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ * @private */ private calculateNextReconnectDelay() : number { const jitterType : mqtt5.RetryJitterType = this.clientConfig.retryJitterMode ?? mqtt5.RetryJitterType.Default; const [minDelay, maxDelay] : [number, number] = mqtt_utils.getOrderedReconnectDelayBounds(this.clientConfig.minReconnectDelayMs, this.clientConfig.maxReconnectDelayMs); const clampedFailureCount : number = Math.min(52, this.connectionFailureCount); let delay : number = 0; if (jitterType == mqtt5.RetryJitterType.None) { delay = minDelay * Math.pow(2, clampedFailureCount); } else if (jitterType == mqtt5.RetryJitterType.Decorrelated && this.lastReconnectDelay) { delay = this.randomInRange(minDelay, 3 * this.lastReconnectDelay); } else { delay = this.randomInRange(minDelay, Math.min(maxDelay, minDelay * Math.pow(2, clampedFailureCount))); } delay = Math.min(maxDelay, delay); this.lastReconnectDelay = delay; return delay; } } /** * Elements of a simple state machine that allows us to adapt the mqtt-js control model to our mqtt5 client * control model (start/stop). * * @internal */ enum Mqtt5ClientState { Stopped = 0, Running = 1, Stopping = 2, Restarting = 3, } /** * Elements of a simple state machine that allows us to adapt the mqtt-js event set to our mqtt5 client's * lifecycle event set. * * @internal */ enum Mqtt5ClientLifecycleEventState { None = 0, Connecting = 1, Connected = 2, Disconnected = 3, } /** * Browser specific MQTT5 client implementation * * DEVELOPER PREVIEW DISCLAIMER * * MQTT5 support is currently in **developer preview**. We encourage feedback at all times, but feedback during the * preview window is especially valuable in shaping the final product. During the preview period we may make * backwards-incompatible changes to the public API, but in general, this is something we will try our best to avoid. * * [MQTT5 Client User Guide](https://www.github.com/awslabs/aws-crt-nodejs/blob/main/MQTT5-UserGuide.md) */ export class Mqtt5Client extends BufferedEventEmitter implements mqtt5.IMqtt5Client { private browserClient?: mqtt.MqttClient; private state : Mqtt5ClientState; private lifecycleEventState : Mqtt5ClientLifecycleEventState; private lastDisconnect? : mqtt5_packet.DisconnectPacket; private lastError? : Error; private reconnectionScheduler? : ReconnectionScheduler; private mqttJsConfig : mqtt.IClientOptions; /** * Client constructor * * @param config The configuration for this client */ constructor(private config: Mqtt5ClientConfig) { super(); this.mqttJsConfig = mqtt_utils.create_mqtt_js_client_config_from_crt_client_config(this.config); this.state = Mqtt5ClientState.Stopped; this.lifecycleEventState = Mqtt5ClientLifecycleEventState.None; } /** * Triggers cleanup of native resources associated with the MQTT5 client. On the browser, the implementation is * an empty function. */ close() {} /** * Notifies the MQTT5 client that you want it to maintain connectivity to the configured endpoint. * The client will attempt to stay connected using the properties of the reconnect-related parameters * in the mqtt5 client configuration. * * This is an asynchronous operation. */ start() { if (this.state == Mqtt5ClientState.Stopped) { this.lifecycleEventState = Mqtt5ClientLifecycleEventState.Connecting; this.lastDisconnect = undefined; /* pause event emission until everything is fully-initialized */ this.cork(); this.emit('attemptingConnect'); const create_websocket_stream = (client: mqtt.MqttClient) => WebsocketUtils.create_mqtt5_websocket_stream(this.config); this.browserClient = new mqtt.MqttClient(create_websocket_stream, this.mqttJsConfig); // hook up events this.browserClient.on('end', () => {this._on_stopped_internal();}); this.browserClient.on('reconnect', () => {this.on_attempting_connect();}); this.browserClient.on('connect', (connack: mqtt.IConnackPacket) => {this.on_connection_success(connack);}); this.browserClient.on('message', (topic: string, payload: Buffer, packet: mqtt.IPublishPacket) => { this.on_message(topic, payload, packet);}); this.browserClient.on('error', (error: Error) => { this.on_browser_client_error(error); }); this.browserClient.on('close', () => { this.on_browser_close(); }); this.browserClient.on('disconnect', (packet: mqtt.IDisconnectPacket) => { this.on_browser_disconnect_packet(packet); }); this.reconnectionScheduler = new ReconnectionScheduler(this.browserClient, this.config); this.state = Mqtt5ClientState.Running; /* unpause event emission */ this.uncork(); } else if (this.state == Mqtt5ClientState.Stopping) { this.state = Mqtt5ClientState.Restarting; } } /** * Notifies the MQTT5 client that you want it to end connectivity to the configured endpoint, disconnecting any * existing connection and halting reconnection attempts. * * This is an asynchronous operation. Once the process completes, no further events will be emitted until the client * has {@link start} invoked. Invoking {@link start start()} after a {@link stop stop()} will always result in * a new MQTT session. * * @param disconnectPacket (optional) properties of a DISCONNECT packet to send as part of the shutdown process */ stop(disconnectPacket?: mqtt5_packet.DisconnectPacket) { if (this.state == Mqtt5ClientState.Running) { if (disconnectPacket) { this.browserClient?.end(true, mqtt_utils.transform_crt_disconnect_to_mqtt_js_disconnect(disconnectPacket)); } else { this.browserClient?.end(true); } this.state = Mqtt5ClientState.Stopping; } else if (this.state == Mqtt5ClientState.Restarting) { this.state = Mqtt5ClientState.Stopping; } } /** * Subscribe to one or more topic filters by queuing a SUBSCRIBE packet to be sent to the server. * * @param packet SUBSCRIBE packet to send to the server * @returns a promise that will be rejected with an error or resolved with the SUBACK response */ async subscribe(packet: mqtt5_packet.SubscribePacket) : Promise<mqtt5_packet.SubackPacket> { return new Promise<mqtt5_packet.SubackPacket>((resolve, reject) => { try { if (!this.browserClient) { reject(new Error("Client is stopped and cannot subscribe")); return; } if (!packet) { reject(new Error("Invalid subscribe packet")); return; } let subMap: mqtt.ISubscriptionMap = mqtt_utils.transform_crt_subscribe_to_mqtt_js_subscription_map(packet); let subOptions: mqtt.IClientSubscribeOptions = mqtt_utils.transform_crt_subscribe_to_mqtt_js_subscribe_options(packet); // @ts-ignore this.browserClient.subscribe(subMap, subOptions, (error, grants) => { if (error) { reject(error); return; } const suback: mqtt5_packet.SubackPacket = mqtt_utils.transform_mqtt_js_subscription_grants_to_crt_suback(grants); resolve(suback); }); } catch (err) { reject(err); } }); } /** * Unsubscribe from one or more topic filters by queuing an UNSUBSCRIBE packet to be sent to the server. * * @param packet UNSUBSCRIBE packet to send to the server * @returns a promise that will be rejected with an error or resolved with the UNSUBACK response */ async unsubscribe(packet: mqtt5_packet.UnsubscribePacket) : Promise<mqtt5_packet.UnsubackPacket> { return new Promise<mqtt5_packet.UnsubackPacket>((resolve, reject) => { try { if (!this.browserClient) { reject(new Error("Client is stopped and cannot unsubscribe")); return; } if (!packet) { reject(new Error("Invalid unsubscribe packet")); return; } let topicFilters: string[] = packet.topicFilters; let unsubOptions: Object = mqtt_utils.transform_crt_unsubscribe_to_mqtt_js_unsubscribe_options(packet); this.browserClient.unsubscribe(topicFilters, unsubOptions, (error, packet) => { if (error) { reject(error); return; } /* * sigh, mqtt-js doesn't emit the unsuback packet, we have to make something up that won't reflect * reality. */ if (!packet || packet.cmd !== 'unsuback') { /* this is a complete lie */ let unsuback: mqtt5_packet.UnsubackPacket = { type: mqtt5_packet.PacketType.Unsuback, reasonCodes: topicFilters.map((filter: string, index: number, array: string[]): mqtt5_packet.UnsubackReasonCode => { return mqtt5_packet.UnsubackReasonCode.Success; }) }; resolve(unsuback); } else { const unsuback: mqtt5_packet.UnsubackPacket = mqtt_utils.transform_mqtt_js_unsuback_to_crt_unsuback(packet as mqtt.IUnsubackPacket); resolve(unsuback); } }); } catch (err) { reject(err); } }); } /** * Send a message to subscribing clients by queuing a PUBLISH packet to be sent to the server. * * @param packet PUBLISH packet to send to the server * @returns a promise that will be rejected with an error or resolved with the PUBACK response (QoS 1), or * undefined (QoS 0) */ async publish(packet: mqtt5_packet.PublishPacket) : Promise<mqtt5.PublishCompletionResult> { return new Promise<mqtt5.PublishCompletionResult>((resolve, reject) => { try { if (!this.browserClient) { reject(new Error("Client is stopped and cannot publish")); return; } if (!packet) { reject(new Error("Invalid publish packet")); return; } let publishOptions : mqtt.IClientPublishOptions = mqtt_utils.transform_crt_publish_to_mqtt_js_publish_options(packet); let qos : mqtt5_packet.QoS = packet.qos; let payload = mqtt_shared.normalize_payload(packet.payload); this.browserClient.publish(packet.topicName, payload, publishOptions, (error, completionPacket) => { if (error) { reject(error); return; } switch (qos) { case mqtt5_packet.QoS.AtMostOnce: resolve(undefined); break; case mqtt5_packet.QoS.AtLeastOnce: if (!completionPacket) { reject(new Error("Invalid puback packet from mqtt-js")); return; } /* * sadly, mqtt-js returns the original publish packet when the puback is a success, so we have * to create a fake puback instead. This means we won't reflect any reason string or * user properties that might have been present in the real puback. */ if (completionPacket.cmd !== "puback") { resolve({ type: mqtt5_packet.PacketType.Puback, reasonCode: mqtt5_packet.PubackReasonCode.Success }) } const puback: mqtt5_packet.PubackPacket = mqtt_utils.transform_mqtt_js_puback_to_crt_puback(completionPacket as mqtt.IPubackPacket); resolve(puback); break; default: /* Technically, mqtt-js supports QoS 2 but we don't yet model it in the CRT types */ reject(new Error("Unsupported QoS value")); break; } }); } catch (err) { reject(err); } }); } /** * Event emitted when the client encounters a disruptive error condition. Not currently used. * * Listener type: {@link ErrorEventListener} * * @event */ static ERROR : string = 'error'; /** * Event emitted when the client encounters a transient error event that will not disrupt promises based on * lifecycle events. Currently, mqtt-js client error events are relayed to this event. * * Listener type: {@link ErrorEventListener} * * @event * @group Browser-only */ static INFO : string = 'info'; /** * Event emitted when an MQTT PUBLISH packet is received by the client. * * Listener type: {@link MessageReceivedEventListener} * * @event */ static MESSAGE_RECEIVED : string = 'messageReceived'; /** * Event emitted when the client begins a connection attempt. * * Listener type: {@link AttemptingConnectEventListener} * * @event */ static ATTEMPTING_CONNECT : string = 'attemptingConnect'; /** * Event emitted when the client successfully establishes an MQTT connection. Only emitted after * an {@link ATTEMPTING_CONNECT attemptingConnect} event. * * Listener type: {@link ConnectionSuccessEventListener} * * @event */ static CONNECTION_SUCCESS : string = 'connectionSuccess'; /** * Event emitted when the client fails to establish an MQTT connection. Only emitted after * an {@link ATTEMPTING_CONNECT attemptingConnect} event. * * Listener type: {@link ConnectionFailureEventListener} * * @event */ static CONNECTION_FAILURE : string = 'connectionFailure'; /** * Event emitted when the client's current connection is closed for any reason. Only emitted after * a {@link CONNECTION_SUCCESS connectionSuccess} event. * * Listener type: {@link DisconnectionEventListener} * * @event */ static DISCONNECTION : string = 'disconnection'; /** * Event emitted when the client finishes shutdown as a result of the user invoking {@link stop}. * * Listener type: {@link StoppedEventListener} * * @event */ static STOPPED : string = 'stopped'; /** * Registers a listener for the client's {@link ERROR error} event. An {@link ERROR error} event is emitted when * the client encounters a disruptive error condition. * * @param event the type of event to listen to * @param listener the event listener to add */ on(event: 'error', listener: mqtt5.ErrorEventListener): this; /** * Registers a listener for the client's {@link INFO info} event. An {@link INFO info} event is emitted when * the client encounters a transient error event that will not disrupt promises based on lifecycle events. * Currently, mqtt-js client error events are relayed to this event. * * @param event the type of event to listen to * @param listener the event listener to add * * @group Browser-only */ on(event: 'info', listener: mqtt5.ErrorEventListener): this; /** * Registers a listener for the client's {@link MESSAGE_RECEIVED messageReceived} event. A * {@link MESSAGE_RECEIVED messageReceived} event is emitted when an MQTT PUBLISH packet is received by the * client. * * @param event the type of event to listen to * @param listener the event listener to add */ on(event: 'messageReceived', listener: mqtt5.MessageReceivedEventListener): this; /** * Registers a listener for the client's {@link ATTEMPTING_CONNECT attemptingConnect} event. A * {@link ATTEMPTING_CONNECT attemptingConnect} event is emitted every time the client begins a connection attempt. * * @param event the type of event to listen to * @param listener the event listener to add */ on(event: 'attemptingConnect', listener: mqtt5.AttemptingConnectEventListener): this; /** * Registers a listener for the client's {@link CONNECTION_SUCCESS connectionSuccess} event. A * {@link CONNECTION_SUCCESS connectionSuccess} event is emitted every time the client successfully establishes * an MQTT connection. * * @param event the type of event to listen to * @param listener the event listener to add */ on(event: 'connectionSuccess', listener: mqtt5.ConnectionSuccessEventListener): this; /** * Registers a listener for the client's {@link CONNECTION_FAILURE connectionFailure} event. A * {@link CONNECTION_FAILURE connectionFailure} event is emitted every time the client fails to establish an * MQTT connection. * * @param event the type of event to listen to * @param listener the event listener to add */ on(event: 'connectionFailure', listener: mqtt5.ConnectionFailureEventListener): this; /** * Registers a listener for the client's {@link DISCONNECTION disconnection} event. A * {@link DISCONNECTION disconnection} event is emitted when the client's current MQTT connection is closed * for any reason. * * @param event the type of event to listen to * @param listener the event listener to add */ on(event: 'disconnection', listener: mqtt5.DisconnectionEventListener): this; /** * Registers a listener for the client's {@link STOPPED stopped} event. A * {@link STOPPED stopped} event is emitted when the client finishes shutdown as a * result of the user invoking {@link stop}. * * @param event the type of event to listen to * @param listener the event listener to add */ on(event: 'stopped', listener: mqtt5.StoppedEventListener): this; on(event: string | symbol, listener: (...args: any[]) => void): this { super.on(event, listener); return this; } private on_browser_disconnect_packet(packet: mqtt.IDisconnectPacket) { this.lastDisconnect = mqtt_utils.transform_mqtt_js_disconnect_to_crt_disconnect(packet); } private on_browser_close() { let lastDisconnect : mqtt5_packet.DisconnectPacket | undefined = this.lastDisconnect; let lastError : Error | undefined = this.lastError; if (this.lifecycleEventState == Mqtt5ClientLifecycleEventState.Connected) { this.lifecycleEventState = Mqtt5ClientLifecycleEventState.Disconnected; this.reconnectionScheduler?.onConnectionFailureOrDisconnection(); let disconnectionEvent : mqtt5.DisconnectionEvent = { error: new CrtError(lastError?.toString() ?? "disconnected") } if (lastDisconnect !== undefined) { disconnectionEvent.disconnect = lastDisconnect; } setTimeout(() => { this.emit(Mqtt5Client.DISCONNECTION, disconnectionEvent); }, 0); } else if (this.lifecycleEventState == Mqtt5ClientLifecycleEventState.Connecting) { this.lifecycleEventState = Mqtt5ClientLifecycleEventState.Disconnected; this.reconnectionScheduler?.onConnectionFailureOrDisconnection(); let connectionFailureEvent: mqtt5.ConnectionFailureEvent = { error: new CrtError(lastError?.toString() ?? "connectionFailure") }; setTimeout(() => { this.emit(Mqtt5Client.CONNECTION_FAILURE, connectionFailureEvent); }, 0); } this.lastDisconnect = undefined; this.lastError = undefined; } private on_browser_client_error(error: Error) { this.lastError = error; setTimeout(() => { this.emit(Mqtt5Client.INFO, new CrtError(error)); }, 0); } private on_attempting_connect () { this.lifecycleEventState = Mqtt5ClientLifecycleEventState.Connecting; let attemptingConnectEvent: mqtt5.AttemptingConnectEvent = {}; setTimeout(() => { this.emit(Mqtt5Client.ATTEMPTING_CONNECT, attemptingConnectEvent); }, 0); } private on_connection_success (connack: mqtt.IConnackPacket) { this.lifecycleEventState = Mqtt5ClientLifecycleEventState.Connected; this.reconnectionScheduler?.onSuccessfulConnection(); let crt_connack : mqtt5_packet.ConnackPacket = mqtt_utils.transform_mqtt_js_connack_to_crt_connack(connack); let settings : mqtt5.NegotiatedSettings = mqtt_utils.create_negotiated_settings(this.config, crt_connack); let connectionSuccessEvent: mqtt5.ConnectionSuccessEvent = { connack: crt_connack, settings: settings }; setTimeout(() => { this.emit(Mqtt5Client.CONNECTION_SUCCESS, connectionSuccessEvent); }, 0); } private _on_stopped_internal() { this.reconnectionScheduler?.clearTasks(); this.reconnectionScheduler = undefined; this.browserClient = undefined; this.lifecycleEventState = Mqtt5ClientLifecycleEventState.None; this.lastDisconnect = undefined; this.lastError = undefined; if (this.state == Mqtt5ClientState.Restarting) { this.state = Mqtt5ClientState.Stopped; this.start(); } else if (this.state != Mqtt5ClientState.Stopped) { this.state = Mqtt5ClientState.Stopped; this.emit(Mqtt5Client.STOPPED); } } private on_message = (topic: string, payload: Buffer, packet: mqtt.IPublishPacket) => { let crtPublish : mqtt5_packet.PublishPacket = mqtt_utils.transform_mqtt_js_publish_to_crt_publish(packet); let messageReceivedEvent: mqtt5.MessageReceivedEvent = { message: crtPublish }; setTimeout(() => { this.emit(Mqtt5Client.MESSAGE_RECEIVED, messageReceivedEvent); }, 0); } }