import { CommunicationEventType , Uuid } from "../.." ; /**
 * Represents MQTT publication and subscription topics for Coaty communication
 * events (except Raw and external IoValue).
 */ export class MqttTopic { get eventType ( ) { return this . _eventType ; } get eventTypeFilter ( ) { return this . _eventTypeFilter ; } get sourceId ( ) { return this . _sourceId ; } get correlationId ( ) { return this . _correlationId ; } get version ( ) { return this . _version ; } get namespace ( ) { return this . _namespace ; } private static readonly PROTOCOL_NAME = "coaty" ; private static readonly PROTOCOL_NAME_PREFIX = MqttTopic . PROTOCOL_NAME + "/" ; private static readonly EVENT_TYPE_FILTER_SEPARATOR = ":" ; private static readonly ILLEGAL_TOPIC_CHARS_REGEX = new RegExp ( /[\u0000+#]/ ) ; private static EVENT_TYPE_TOPIC_LEVELS : { [ level ] : CommunicationEventType ; } ; private static TOPIC_LEVELS_BY_EVENT_TYPE : string [ ] ; private _version : number ; private _namespace : string ; private _eventType : CommunicationEventType ; private _eventTypeFilter : string ; private _sourceId : Uuid ; private _correlationId : Uuid ; private constructor ( ) { } param topicName returns static createByName ( topicName ) { if ( ! MqttTopic . isCoatyTopicLike ( topicName ) ) { return undefined ; } const topic = new MqttTopic ( ) ; const [ , version , namespace , event , sourceId , corrId ] = topicName . split ( "/" ) ; const v = parseInt ( version , 10 ) ; const [ eventType , eventTypeFilter ] = this . _parseEvent ( event ) ; topic . _version = v ; topic . _namespace = namespace ; topic . _eventType = eventType ; topic . _eventTypeFilter = eventTypeFilter ; topic . _sourceId = sourceId ; topic . _correlationId = corrId === "" ? undefined : corrId ; return topic ; } param version param namepace param eventType param eventTypeFilter param sourceId param correlationId static getTopicName ( version , namespace , eventType , eventTypeFilter , sourceId , correlationId ) { let eventLevel = MqttTopic . _getEventTopicLevelPrefix ( eventType ) ; if ( eventTypeFilter ) { eventLevel += MqttTopic . EVENT_TYPE_FILTER_SEPARATOR + eventTypeFilter ; } let topic = `${ MqttTopic . PROTOCOL_NAME }/${ version }/${ namespace }/${ eventLevel }/${ sourceId }` ; if ( ! MqttTopic . _isOneWayEvent ( eventType ) ) { topic += `/${ correlationId }` ; } return topic ; } param version param namepace param eventType param eventTypeFilter param correlationId static getTopicFilter ( version , namespace , eventType , eventTypeFilter , correlationId ) { let eventLevel = MqttTopic . _getEventTopicLevelPrefix ( eventType ) ; if ( eventTypeFilter ) { eventLevel += MqttTopic . EVENT_TYPE_FILTER_SEPARATOR + eventTypeFilter ; } let levels = `${ this . PROTOCOL_NAME }/${ version }/${ namespace || "+" }/${ eventLevel }/+` ; if ( ! this . _isOneWayEvent ( eventType ) ) { levels += correlationId ? `/${ correlationId }` : "/+" ; } return levels ; } param topicName returns static isCoatyTopicLike ( topicName ) { return topicName . startsWith ( this . PROTOCOL_NAME_PREFIX ) ; } param topicName param forSubscription returns static isValidTopic ( topic , forSubscription = false ) { if ( ! topic ) { return false ; } if ( topic . length * 4 > 65535 && this . _getUtf8BytesCount ( topic ) > 65535 ) { return false ; } if ( forSubscription ) { return topic . indexOf ( "\u0000" ) === - 1 ; } return ! MqttTopic . ILLEGAL_TOPIC_CHARS_REGEX . test ( topic ) ; } remarks param topic param filter returns static matches ( topic , filter ) { if ( ! topic || ! filter ) { return false ; } const SEPARATOR = "/" ; const SINGLE_WILDCARD = "+" ; const MULTI_WILDCARD = "#" ; const patternLevels = filter . split ( SEPARATOR ) ; const topicLevels = topic . split ( SEPARATOR ) ; const patternLength = patternLevels . length ; const topicLength = topicLevels . length ; const lastIndex = patternLength - 1 ; for ( let i = 0 ; i <= lastIndex ; i ++ ) { const currentPatternLevel = patternLevels [ i ] ; const currentLevel = topicLevels [ i ] ; if ( ! currentLevel && ! currentPatternLevel ) { continue ; } if ( ! currentLevel && currentPatternLevel === SINGLE_WILDCARD ) { continue ; } if ( ! currentLevel && currentPatternLevel !== MULTI_WILDCARD ) { return false ; } if ( currentPatternLevel === MULTI_WILDCARD ) { return i === lastIndex ; } if ( currentPatternLevel !== SINGLE_WILDCARD && currentPatternLevel !== currentLevel ) { return false ; } } return patternLength === topicLength ; } private static _isOneWayEvent ( eventType ) { return eventType > CommunicationEventType . OneWay && eventType < CommunicationEventType . TwoWay ; } private static _parseEvent ( event ) { const index = event . indexOf ( this . EVENT_TYPE_FILTER_SEPARATOR ) ; const type = index === - 1 ? event : event . substring ( 0 , index ) ; const filter = index === - 1 ? undefined : event . substring ( index + 1 ) ; return [ this . _getEventType ( type ) , filter ] ; } private static _getUtf8BytesCount ( str ) { let count = 0 ; const strLen = str . length ; for ( let i = 0 ; i < strLen ; i ++ ) { const code = str . charCodeAt ( i ) ; if ( code <= 0x7f ) { count ++ ; } else if ( code <= 0x7ff ) { count += 2 ; } else if ( code >= 0xd800 && code <= 0xdfff ) { count += 4 ; } else { count += 3 ; } } return count ; } private static _initTopicLevels ( ) { if ( this . EVENT_TYPE_TOPIC_LEVELS === undefined ) { this . EVENT_TYPE_TOPIC_LEVELS = { ADV : CommunicationEventType . Advertise , DAD : CommunicationEventType . Deadvertise , CHN : CommunicationEventType . Channel , ASC : CommunicationEventType . Associate , IOV : CommunicationEventType . IoValue , DSC : CommunicationEventType . Discover , RSV : CommunicationEventType . Resolve , QRY : CommunicationEventType . Query , RTV : CommunicationEventType . Retrieve , UPD : CommunicationEventType . Update , CPL : CommunicationEventType . Complete , CLL : CommunicationEventType . Call , RTN : CommunicationEventType . Return , } ; } if ( this . TOPIC_LEVELS_BY_EVENT_TYPE === undefined ) { this . TOPIC_LEVELS_BY_EVENT_TYPE = [ ] ; Object . keys ( this . EVENT_TYPE_TOPIC_LEVELS ) . findIndex ( key => { const eventType = this . EVENT_TYPE_TOPIC_LEVELS [ key ] ; this . TOPIC_LEVELS_BY_EVENT_TYPE [ eventType ] = key ; } ) ; } } private static _getEventType ( topicLevel ) { this . _initTopicLevels ( ) ; return this . EVENT_TYPE_TOPIC_LEVELS [ topicLevel ] ; } private static _getEventTopicLevelPrefix ( eventType ) { this . _initTopicLevels ( ) ; return this . TOPIC_LEVELS_BY_EVENT_TYPE [ eventType ] ; } }