Open dotsinspace opened 6 months ago
I am exactly facing the same issue, although I don't receive an Already bound
response from SMSC, the bind_transiever request doesn't work on my end.
There is session.unbind()
function but I don't think that can be executed on smpp client side, instead, it is used by the SMPP server side to clean up sessions.
Mine is working fine now because i have extened it with Proxy and then making sure that server reconnect when ever possible on connect drops.
@dotsinspace proxy? how does that help in reconnecting to the SMSC again? Can you share the idea, please?
I had a hard time reconnecting when the connection got closed, I couldn't distinguish if it was only an unbind
issue or the close of the connection.
Here you go.
/*
* IMPORTS
*/
import Smpp from './node_modules/smpp' // Npm: SMPP library.
import Redis from 'ioredis' // Npm: Redis library.
import _ from 'underscore' // Npm: Utility library.
import { create as DeePool } from 'deepool' // Npm: Connection pool library.
import { Queue, Worker } from 'bullmq' // Npm: BullMQ library.
/*
* PACKAGES
*/
import Tag from 'tag'
/*
* GLOBALS
*/
const _functionName = 'SmppManager -> Session'
/*
* EXPORTS
*/
export default new Proxy(Smpp, {
'get': (__target, __name) => {
/*
* If the property is a function, return a function that will create a new connection pool.
* and property name is connect
*/
if (_.isFunction(__target[__name]) && 'connect' === __name) {
// Return pooled object.
return ($options, __cb) => {
// Local variable.
let _Session
// Variable assignment.
_Session = __target[__name]($options, r => {
// Send unbind pdu.
_Session.unbind()
// Bind given session.
_Session.bind_transceiver({ 'system_id': $options.username, 'password': $options.password }, j => (_Session.isBounded = 0 === j.command_status))
// Call the callback.
__cb(r)
})
// Update context.
_Session.context = $options.context
// Only proceed if session context is context
if (_Session && !_Session.context.isContext) return new Error('MISSING__CONTEXT')
// Create bull queue for given connection.
_Session.connection = new Redis({ 'connectTimeout': 10000, 'maxRetriesPerRequest': null })
_Session.id = String.random(32)
_Session.reconnectAttempts = 0
_Session.Queue = new Queue(_Session.id, { 'connection': _Session.connection })
_Session.Worker = new Worker(_Session.id, __job => _Session.isBounded ? _Session.submit_sm(__job.data) : new Error('SESSION_NOT_BOUNDED'), { 'connection': _Session.connection })
_Session.Reconnect = () => {
// Increment reconnect attempts
_Session.reconnectAttempts += 1
// Unbind previous connection.
_Session.unbind()
// Calculate reconnect delay with backoff factor
const calculatedDelay = ($options.reconnectBackoffFactor ** (_Session.reconnectAttempts - 1)) * $options.initialReconnectDelay
const reconnectDelay = Math.min(calculatedDelay, 10)
// Check if max reconnection attempts reached
if (_Session.reconnectAttempts <= $options.maxReconnectAttempts) {
// Reconnect socket with a delay.
const reconnectTimeout = setTimeout(() => {
// Check if the session is already connected; if yes, no need to reconnect
if (_Session.closed) {
// Reconnect session.
_Session.connect()
_Session.resume()
}
// Clear timeout.
clearTimeout(reconnectTimeout)
// Clear the reconnect timeout ID
_Session.reconnectTimeoutId = void 0
}, reconnectDelay)
// Save the reconnect timeout ID
_Session.reconnectTimeoutId = reconnectTimeout
} else {
// Destroy the existing session
_Session.close(() => _Session.destroy())
// Clear timeout.
clearTimeout(_Session.reconnectTimeoutId)
// Reinitialize the session
_Session = _Session.connect($options, () => {
// Make sure to unbind the previous session
_Session.unbind()
// Bind given session.
_Session.bind_transceiver({ 'system_id': $options.username, 'password': $options.password }, j => (_Session.isBounded = 0 === j.command_status))
})
}
}
// Event handlers.
_Session.on('enquire_link', __pdu => { _Session.send(__pdu.response()) })
_Session.on('unbind', () => { _Session.unbind_resp(); _Session.close() })
_Session.on('pdu', j => 0 === j.command_status ? void 0 : _Session.context.Debug({ 'message': JSON.stringify({ ip: _Session.remoteAddress, ...j }) }, _functionName))
_Session.on('debug', (e, j, k) => _Session.context.Pubsub.publish(Tag.Smpp.DebuggingIpPort(_Session.remoteAddress, _Session.remotePort), { ..._Session, 'logs': { 'type': e, 'msg': j, 'payload': k } }))
_Session.socket.on('timeout', () => { _Session.context.Debug({ 'message': `Smpp session with id: ${_Session.remoteAddress} got timeout.` }, _functionName) })
_Session.socket.on('close', () => { _Session.context.Debug({ 'message': `Smpp session with id: ${_Session.remoteAddress} got closed.` }, _functionName) })
_Session.socket.on('error', e => { _Session.context.Debug({ 'message': `Smpp session with id: ${_Session.remoteAddress} got error.`, 'error': e }, _functionName); _Session.Reconnect() })
/*
* If user hasn't asked for pooling.
* then return session as is.
*/
if ($options.noPooling) return _Session
// Const assignment.
const _DeePooledTarget_ = DeePool(() => _Session)
// Increase the pool size.
_DeePooledTarget_.grow($options.sessionAllowed)
// Return the pooled object.
return _DeePooledTarget_
}
}
// Otherwise, return the property.
return __target[__name]
}
})
Ability:
@dotsinspace thank you for sharing. It's different from the way I am implementing it, but I will use it as a reference thank you.
I have Pipeline ( Just imagine to ends of pipe with source smpp and destination smpp ) where destiantion smpp sometimes drops the connection for some reason and some time when i restart my server i start getting bind transrecivier flag with command status 5. Now how can i make sure to unbind destination smpp before sending bind request.