160 lines
4.5 KiB
TypeScript
160 lines
4.5 KiB
TypeScript
// Other Socket Errors: EADDRINUSE, ECONNRESET, ENOTFOUND, ETIMEDOUT.
|
|
|
|
import { PacketHandler, ErrorWithReasonCode } from '../shared'
|
|
|
|
export const ReasonCodes = {
|
|
0: '',
|
|
1: 'Unacceptable protocol version',
|
|
2: 'Identifier rejected',
|
|
3: 'Server unavailable',
|
|
4: 'Bad username or password',
|
|
5: 'Not authorized',
|
|
16: 'No matching subscribers',
|
|
17: 'No subscription existed',
|
|
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 handleAck: PacketHandler = (client, packet) => {
|
|
/* eslint no-fallthrough: "off" */
|
|
const { messageId } = packet
|
|
const type = packet.cmd
|
|
let response = null
|
|
const cb = client.outgoing[messageId] ? client.outgoing[messageId].cb : null
|
|
let err = null
|
|
|
|
// Checking `!cb` happens to work, but it's not technically "correct".
|
|
//
|
|
// Why? client code assumes client "no callback" is the same as client "we're not
|
|
// waiting for responses" (puback, pubrec, pubcomp, suback, or unsuback).
|
|
//
|
|
// It would be better to check `if (!client.outgoing[messageId])` here, but
|
|
// there's no reason to change it and risk (another) regression.
|
|
//
|
|
// The only reason client code works is becaues code in MqttClient.publish,
|
|
// MqttClinet.subscribe, and MqttClient.unsubscribe ensures client we will
|
|
// have a callback even if the user doesn't pass one in.)
|
|
if (!cb) {
|
|
client.log('_handleAck :: Server sent an ack in error. Ignoring.')
|
|
// Server sent an ack in error, ignore it.
|
|
return
|
|
}
|
|
|
|
// Process
|
|
client.log('_handleAck :: packet type', type)
|
|
switch (type) {
|
|
case 'pubcomp':
|
|
// same thing as puback for QoS 2
|
|
case 'puback': {
|
|
const pubackRC = packet.reasonCode
|
|
// Callback - we're done
|
|
if (pubackRC && pubackRC > 0 && pubackRC !== 16) {
|
|
err = new ErrorWithReasonCode(
|
|
`Publish error: ${ReasonCodes[pubackRC]}`,
|
|
pubackRC,
|
|
)
|
|
client['_removeOutgoingAndStoreMessage'](messageId, () => {
|
|
cb(err, packet)
|
|
})
|
|
} else {
|
|
client['_removeOutgoingAndStoreMessage'](messageId, cb)
|
|
}
|
|
|
|
break
|
|
}
|
|
case 'pubrec': {
|
|
response = {
|
|
cmd: 'pubrel',
|
|
qos: 2,
|
|
messageId,
|
|
}
|
|
const pubrecRC = packet.reasonCode
|
|
|
|
if (pubrecRC && pubrecRC > 0 && pubrecRC !== 16) {
|
|
err = new ErrorWithReasonCode(
|
|
`Publish error: ${ReasonCodes[pubrecRC]}`,
|
|
pubrecRC,
|
|
)
|
|
client['_removeOutgoingAndStoreMessage'](messageId, () => {
|
|
cb(err, packet)
|
|
})
|
|
} else {
|
|
client['_sendPacket'](response)
|
|
}
|
|
break
|
|
}
|
|
case 'suback': {
|
|
delete client.outgoing[messageId]
|
|
client.messageIdProvider.deallocate(messageId)
|
|
const granted = packet.granted as number[]
|
|
for (let grantedI = 0; grantedI < granted.length; grantedI++) {
|
|
const subackRC = granted[grantedI]
|
|
if ((subackRC & 0x80) !== 0) {
|
|
err = new Error(`Subscribe error: ${ReasonCodes[subackRC]}`)
|
|
err.code = subackRC
|
|
|
|
// suback with Failure status
|
|
const topics = client.messageIdToTopic[messageId]
|
|
if (topics) {
|
|
topics.forEach((topic) => {
|
|
delete client['_resubscribeTopics'][topic]
|
|
})
|
|
}
|
|
}
|
|
}
|
|
delete client.messageIdToTopic[messageId]
|
|
client['_invokeStoreProcessingQueue']()
|
|
cb(err, packet)
|
|
break
|
|
}
|
|
case 'unsuback': {
|
|
delete client.outgoing[messageId]
|
|
client.messageIdProvider.deallocate(messageId)
|
|
client['_invokeStoreProcessingQueue']()
|
|
cb(null, packet)
|
|
break
|
|
}
|
|
default:
|
|
client.emit('error', new Error('unrecognized packet type'))
|
|
}
|
|
|
|
if (client.disconnecting && Object.keys(client.outgoing).length === 0) {
|
|
client.emit('outgoingEmpty')
|
|
}
|
|
}
|
|
|
|
export default handleAck
|