diff --git a/src/components/SubscriptionsList.vue b/src/components/SubscriptionsList.vue index 0fcd8cb5d9..1848d73438 100644 --- a/src/components/SubscriptionsList.vue +++ b/src/components/SubscriptionsList.vue @@ -203,6 +203,7 @@ import time from '@/utils/time' import { getSubscriptionId } from '@/utils/idGenerator' import getContextmenuPosition from '@/utils/getContextmenuPosition' import { LeftValues } from '@/utils/styles' +import getErrorReason from '@/utils/mqttErrorReason' enum SubscribeErrorReason { normal, @@ -410,6 +411,16 @@ export default class SubscriptionsList extends Vue { } } + private handleSubError(topic: string, qos: number) { + const errorReason = getErrorReason(this.record.mqttVersion, qos) + let errorReasonMsg: VueI18n.TranslateResult = '' + if (qos === 128 || qos === 135) errorReasonMsg = ', ' + this.$t('connections.qosSubFailed') + + const errorMsg: string = `Failed to subscribe to ${topic}, Error: ${errorReason} (Code: ${qos})${errorReasonMsg}` + this.$emit('onSubError', errorMsg, `Topics: ${topic}`) + this.$log.error(`Failed to subscribed to topic: ${topic}`) + } + public async subscribe( { topic, alias, qos, nl, rap, rh, subscriptionIdentifier, disabled, id }: SubscriptionModel, isAuto?: boolean, @@ -428,8 +439,9 @@ export default class SubscriptionsList extends Vue { }) } let isFinished = false + if (this.client.subscribe) { - const topicsArr = this.multiTopics ? topic.split(',') : topic + const topicsArr = this.multiTopics ? [...new Set(topic.split(','))].filter(Boolean) : topic const aliasArr = this.multiTopics ? alias?.split(',') : alias let properties: { subscriptionIdentifier: number } | undefined = undefined if (this.record.mqttVersion === '5.0' && subscriptionIdentifier) { @@ -448,21 +460,22 @@ export default class SubscriptionsList extends Vue { this.$log.error(`Error subscribing to topic: ${error}`) return false } - let errorReason = SubscribeErrorReason.normal - - if (!granted || (Array.isArray(granted) && granted.length < 1)) { + let successSubscriptions: string[] = [] + if (!granted) { this.$log.error('Error subscribing to topic: granted empty') - } else if (![0, 1, 2].includes(granted[0].qos) && topic.match(/^(\$SYS)/i)) { - errorReason = SubscribeErrorReason.qosSubSysFailed - } else if (![0, 1, 2].includes(granted[0].qos)) { - errorReason = SubscribeErrorReason.qosSubFailed + return false + } else { + granted.forEach((grant) => { + if ([0, 1, 2].includes(grant.qos)) { + successSubscriptions.push(grant.topic) + } else { + setTimeout(() => { + this.handleSubError(grant.topic, grant.qos) + }, 0) + } + }) } - - if (errorReason !== SubscribeErrorReason.normal) { - const errorReasonMsg: VueI18n.TranslateResult = this.getErrorReasonMsg(errorReason) - const errorMsg: string = `${this.$t('connections.subFailed')} ${errorReasonMsg}` - this.$log.error(`Error subscribing to topic: ${errorReasonMsg}`) - this.$emit('onSubError', errorMsg, `Topics: ${JSON.stringify(topicsArr)}`) + if (!successSubscriptions.length) { return false } if (enable) { @@ -473,10 +486,12 @@ export default class SubscriptionsList extends Vue { this.saveTopicToSubList(topic, qos, undefined, undefined, id) } else { topicsArr.forEach((topic, index) => { - this.saveTopicToSubList(topic, qos, index, aliasArr as string[], id) + if (successSubscriptions.includes(topic)) { + this.saveTopicToSubList(topic, qos, index, aliasArr as string[], id) + } }) } - this.$log.info(`Saved topic: ${topic}`) + this.$log.info(`Saved topic: ${successSubscriptions}`) } this.record.subscriptions = this.subsList if (this.record.id) { @@ -484,12 +499,12 @@ export default class SubscriptionsList extends Vue { await subscriptionService.updateSubscriptions(this.record.id, this.record.subscriptions) this.changeSubs({ id: this.connectionId, subscriptions: this.subsList }) this.showDialog = false - let subLog = `Successfully subscribed to topic: ${topic}` - this.$log.info(subLog) + successSubscriptions.length && this.$log.info(`Successfully subscribed to topic: ${successSubscriptions}`) } isFinished = true }) } + const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) // TODO: maybe we should replace mqtt.js to mqtt-async.js await new Promise(async (resolve) => { diff --git a/src/utils/mqttErrorReason.ts b/src/utils/mqttErrorReason.ts new file mode 100644 index 0000000000..c27d86deac --- /dev/null +++ b/src/utils/mqttErrorReason.ts @@ -0,0 +1,57 @@ +const MqttErrorReason: Record = { + '3.1': { + 128: 'Not authorized', + }, + '3.1.1': { + 128: 'Not authorized', + }, + '5.0': { + 4: 'Disconnect with Will Message', + 16: 'No matching subscribers', + 17: 'No subscription existed', + 24: 'Continue authentication', + 25: 'Re-authenticate', + 128: 'Unspecified error', + 129: 'Malformed Packet', + 130: 'Protocol Error', + 131: 'Implementation specific error', + 132: 'Unsupported Protocol Version', + 133: 'Client Identifier not valid', + 134: 'Bad User Name or Password', + 135: 'Not authorized', + 136: 'Server unavailable', + 137: 'Server busy', + 138: 'Banned', + 139: 'Server shutting down', + 140: 'Bad authentication method', + 141: 'Keep Alive timeout', + 142: 'Session taken over', + 143: 'Topic Filter invalid', + 144: 'Topic Name invalid', + 145: 'Packet Identifier in use', + 146: 'Packet Identifier not found', + 147: 'Receive Maximum exceeded', + 148: 'Topic Alias invalid', + 149: 'Packet too large', + 150: 'Message rate too high', + 151: 'Quota exceeded', + 152: 'Administrative action', + 153: 'Payload format invalid', + 154: 'Retain not supported', + 155: 'QoS not supported', + 156: 'Use another server', + 157: 'Server moved', + 158: 'Shared Subscriptions not supported', + 159: 'Connection rate exceeded', + 160: 'Maximum connect time', + 161: 'Subscription Identifiers not supported', + 162: 'Wildcard Subscriptions not supported', + }, +} + +const getErrorReason = (version: string, code: number) => { + const versionMap = MqttErrorReason[version] + return versionMap[code] ?? 'Unknown error' +} + +export default getErrorReason