Skip to content

fix: message_id in submit_sm_resp + SMPP spec compliant DLR stat codes#68

Open
imidevops wants to merge 4 commits into
larvit:masterfrom
imidevops:master
Open

fix: message_id in submit_sm_resp + SMPP spec compliant DLR stat codes#68
imidevops wants to merge 4 commits into
larvit:masterfrom
imidevops:master

Conversation

@imidevops

@imidevops imidevops commented Apr 3, 2026

Copy link
Copy Markdown

Summary

This PR fixes DLR delivery when larvitsmpp is used as an SMPP server
connected to Kannel SMS Gateway with Redis DLR storage.

Problems & Fixes

1. submit_sm_resp missing message_id (single part)
Kannel requires a message_id in the submit_sm_resp to store the DLR
entry in Redis. Without it, DLRs can never be matched back.
Fix: generate UUID and return it in submit_sm_resp, store as smsId.

2. submit_sm_resp missing message_id (multipart)
longSms() returned early without ever sending submit_sm_resp.
Fix: generate UUID per part inside longSms(), send resp immediately,
store first part's ID on the group for the assembled smsObj.

3. smsId never set on server-side smsObj
smsDlr() silently bails out if smsId is undefined.
Fix: smsId now explicitly set on smsObj in both single and multipart paths.

4. stat:UNDELIVERABLE violates SMPP v3.4 spec
Spec requires stat field to be exactly 7 chars. UNDELIVERABLE is 13.
Kannel's sscanf parser rejects it and treats every DLR as DELIVRD.
Fix: changed to UNDELIV (7 chars) per spec.

Tested

  • Kannel + Redis DLR storage
  • Single and multipart SMS
  • DELIVRD and UNDELIV flows
  • Verified via Wireshark PCAP

Summary by CodeRabbit

Release Notes

  • Documentation

    • Added detailed documentation of this fork with SMS handling improvements and DLR compatibility updates.
  • Bug Fixes

    • Message IDs now properly generated and tracked for SMS submissions.
    • DLR status text corrected to specification-compliant format.
    • Improved multipart SMS delivery response handling.
  • Tests

    • Added test application demonstrating SMPP server integration with example scenarios.

Updated README to reflect critical fixes for Kannel SMS Gateway and Redis DLR compatibility, including detailed problem descriptions and solutions.
@coderabbitai

coderabbitai Bot commented Apr 3, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request refactors the SMPP library's logging infrastructure from larvitutils to winston, consolidates object definitions in lib/defs.js, updates function signatures across multiple modules to use standardized parameter naming, implements message ID generation for SMS responses, and adds a comprehensive test application demonstrating server-side SMS handling with Redis integration.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added fork notice and "What's Fixed in This Fork" section documenting four critical protocol behavior fixes: submit_sm_resp message ID generation for single and multipart SMS, smsId assignment to prevent DLR failures, and DLR stat text correction to spec-compliant stat:UNDELIV.
Definition Consolidation
lib/defs.js
Consolidated incremental object mutations into single object-literal assignments for consts, types, tlvs, cmds, and errors; switched logging from larvitutils to winston; refactored address-array TLV discrimination logic; updated types.tlv to use unquoted property identifiers.
Session Management & Message ID
lib/session.js
Refactored logging to module-level winston instance; changed factory signature from session(options) to session(sock); renamed callback parameters from cb to callback; implemented UUID-based message_id generation in longSms() and handleCmd.submit_sm; ensured smsId is always set on SMS objects to prevent DLR failures.
Utility Functions & Validation
lib/utils.js
Replaced larvitutils logging with winston; removed exports.log export; renamed callback parameters to callback across exported functions; added cmdLength validation in writeBuffer; enhanced error handling in pduToObj with header field validation; added encoding special-case handling in decodeMsg; refactored loop constructs to while patterns.
Test Application
test/app.js
New SMPP server test application on port 2770 with hardcoded username/password authentication, MNP lookup via Redis, HTTP forwarding of SMS with conditional delivery reporting, and structured logging to file using Bunyan logger.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes


Poem

🐰 Whiskers twitching with delight,
Messages now flow so right!
IDs born from UUID's spark,
Winston logs glow in the dark,
SMPP sings a cleaner song,
Fixes flowing all day long!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title precisely describes the main fixes: message_id in submit_sm_resp and SMPP-spec compliant DLR stat codes, matching the primary objectives.
Docstring Coverage ✅ Passed Docstring coverage is 88.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/utils.js (1)

645-665: ⚠️ Potential issue | 🟠 Major

splitMsg() now drops one character from every full segment.

When i2 === partCharLimit, the code pushes msgPart.slice(0, -1) and then reprocesses the removed character. That turns each “full” part into partCharLimit - 1 chars, so a 305-char GSM message becomes 152/152/1 instead of 153/152.

🛠️ Suggested fix
 if (i2 === partCharLimit) {
-  msgs.push(defs.encodings[encoding].encode(msgPart.slice(0, - 1)));
+  msgs.push(defs.encodings[encoding].encode(msgPart));
   msgPart = '';
-  i --;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils.js` around lines 645 - 665, The splitMsg() loop drops one character
from every full segment because when i2 === partCharLimit it pushes
defs.encodings[encoding].encode(msgPart.slice(0, -1)) and then decrements i to
reprocess the removed char; change this so it pushes the entire msgPart (no
slice) for full segments and do not decrement i—i.e., remove the .slice(0, -1)
and the i-- adjustment so msgs receives partCharLimit characters and the loop
proceeds correctly using msgPart, i2, partCharLimit, msgs and
defs.encodings[encoding].encode.
lib/defs.js (1)

631-639: ⚠️ Potential issue | 🟠 Major

Normalize the input before reading its fields.

These encoders allocate result from value.data / value.number / value.display before the Buffer.isBuffer() and string-normalization branches run. broadcast_area_identifier.encode('foo') and any Buffer pass-through will currently throw before they hit the intended fast path.

🛠️ Suggested pattern
 encode: function(value) {
-  var result = new Buffer(value.data.length + 1);
   if (Buffer.isBuffer(value)) {
     return value;
   }
   if (typeof value === 'string') {
     value = {
       format: consts.BROADCAST_AREA_FORMAT.NAME,
       data: value
     };
   }
+  var result = new Buffer(value.data.length + 1);

Apply the same reordering in billing_identification.encode(), callback_num.encode(), and callback_num_atag.encode().

Also applies to: 657-673, 752-795

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/defs.js` around lines 631 - 639, The encoders (e.g.,
broadcast_area_identifier.encode, billing_identification.encode,
callback_num.encode, callback_num_atag.encode) currently allocate a result
Buffer using value.data/number/display before checking Buffer.isBuffer(value) or
normalizing string inputs, which causes throws for raw Buffers or string inputs;
fix each encode function by first normalizing the input (handle string -> object
with .data/.number/.display as needed) and performing the Buffer.isBuffer(value)
pass-through check before allocating result, then allocate result and write
format/other fields and copy value.data/number/display into it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/session.js`:
- Around line 385-386: The multipart submit flow currently returns two
conflicting responses per part: the inline sendReturn call in the submit path
(use of sendReturn with generated msgId) and the downstream checkLongSmses path
which emits an sms object containing sendResp/smsResp and pduObjs; update the
code so only one response path is used—remove or guard the per-part sendReturn
(the sendReturn call that sends 'ESME_ROK' with message_id) when
checkLongSmses/longSms handling will emit the sms event, and instead ensure
checkLongSmses emits the correct UUIDs (or returns the pdu-level ids) so
handlers calling sms.sendResp() receive the same IDs; specifically modify the
logic around sendReturn in the submit handler and the emission in checkLongSmses
(and related smsResp/pduObjs formation) so multipart messages send a single
consistent submit_sm_resp per part with matching IDs.
- Around line 349-357: In sendLongSms's inner that.send callback, guard against
err before dereferencing retPduObj by short-circuiting on error: if (err) { if
(typeof callback === 'function') callback(err); return; } — only then access
retPduObj.params.message_id and push to smsIds/retPduObjs and continue to call
the parent callback when smsIds.length === msgs.length; update the callback
invocation to ensure errors are propagated once and no undefined retPduObj is
accessed.
- Around line 496-507: session currently assumes its argument is a raw socket
and immediately reads sock.remoteAddress and registers sock.on, breaking callers
that pass an options wrapper (e.g., { sock, log }). Add a compatibility shim at
the start of the session function: detect if the argument is an object with a
.sock property and, if so, extract sock = arg.sock and optionally log = arg.log
(or preserve existing log variable), otherwise keep the existing behavior;
ensure you guard uses of sock (remoteAddress/remotePort) and event registration
against undefined to maintain backward compatibility with callers still passing
a plain socket and with existing callers that pass { sock, log } and expect
session() to behave the same.

In `@lib/utils.js`:
- Around line 714-750: The status parsing in smsDlr is collapsing legacy aliases
(e.g. "UNDELIVERABLE") into numeric 2 and then hardcoding the receipt body;
update smsDlr's status normalization to (1) accept legacy string aliases by
mapping known old names to the canonical text/ID (use defs.consts.MESSAGE_STATE
and defs.constsById.MESSAGE_STATE to resolve both directions), (2) keep status
as either a numeric id or canonical text as needed, and (3) when composing
shortMessage use the canonical status text (e.g. compute a serializedStat
variable from defs.constsById.MESSAGE_STATE[status] or
defs.consts.MESSAGE_STATE[status] and append ' stat:'+serializedStat+' err:...'
instead of always using DELIVRD/UNDELIV) while still defaulting to the
UNDELIVERABLE value if resolution fails; update the stat/err branches to use
that serializedStat rather than hardcoded values.

In `@test/app.js`:
- Around line 14-15: Add redis to package.json dependencies and update the
client initialization in test/app.js to use the v4 API: replace the legacy
redis.createClient('6379','host-IP') pattern with creating the client via
redis.createClient({ host: 'host-IP', port: 6379 }) and call await
client.connect() after creation; ensure the module import variable name (redis)
remains used and handle the returned client (client) lifetime and errors
appropriately.
- Around line 95-111: The request callback currently only calls
sms.sendDlr('DELIVRD') on a 200 body match and logs errors otherwise; when
sms.dlr is true you must also send a terminal DLR for failures. Update the
request(...) callback (the anonymous function handling error, response, body) so
that in the else branch (non-200 responses or request errors) you set
accessLog.apiResponse to the error/response info and, if sms.dlr === true, call
sms.sendDlr('UNDELIV') before log.info(accessLog) to ensure transport failures
trigger the UNDELIV delivery receipt.

---

Outside diff comments:
In `@lib/defs.js`:
- Around line 631-639: The encoders (e.g., broadcast_area_identifier.encode,
billing_identification.encode, callback_num.encode, callback_num_atag.encode)
currently allocate a result Buffer using value.data/number/display before
checking Buffer.isBuffer(value) or normalizing string inputs, which causes
throws for raw Buffers or string inputs; fix each encode function by first
normalizing the input (handle string -> object with .data/.number/.display as
needed) and performing the Buffer.isBuffer(value) pass-through check before
allocating result, then allocate result and write format/other fields and copy
value.data/number/display into it.

In `@lib/utils.js`:
- Around line 645-665: The splitMsg() loop drops one character from every full
segment because when i2 === partCharLimit it pushes
defs.encodings[encoding].encode(msgPart.slice(0, -1)) and then decrements i to
reprocess the removed char; change this so it pushes the entire msgPart (no
slice) for full segments and do not decrement i—i.e., remove the .slice(0, -1)
and the i-- adjustment so msgs receives partCharLimit characters and the loop
proceeds correctly using msgPart, i2, partCharLimit, msgs and
defs.encodings[encoding].encode.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ea9321d-62a3-4755-a202-f6a0b26cccb5

📥 Commits

Reviewing files that changed from the base of the PR and between 2e4d1c4 and b80a812.

📒 Files selected for processing (5)
  • README.md
  • lib/defs.js
  • lib/session.js
  • lib/utils.js
  • test/app.js

Comment thread lib/session.js
Comment on lines +349 to +357
that.send(pduObj, function(err, retPduObj) {
smsIds.push(retPduObj.params.message_id);
retPduObjs.push(retPduObj);

that.log.silly(logPrefix + 'Got cb from that.send()');
log.silly('larvitsmpp: lib/session.js: sendLongSms() - Got callback from that.send()');

if (typeof cb === 'function' && smsIds.length === msgs.length) {
that.log.silly(logPrefix + 'All cbs returned, run the parent cb.');
cb(err, smsIds, retPduObjs);
if (typeof callback === 'function' && smsIds.length === msgs.length) {
log.silly('larvitsmpp: lib/session.js: sendLongSms() - All callbacks returned, run the parent callback.');
callback(err, smsIds, retPduObjs);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard the multipart callback path before dereferencing retPduObj.

If one that.send() call fails, retPduObj is undefined here and this callback throws before the original error can propagate. Please short-circuit on err before touching retPduObj.params.message_id.

🛠️ Minimum fix
 that.send(pduObj, function(err, retPduObj) {
+  if (err) {
+    if (typeof callback === 'function') {
+      callback(err);
+    }
+    return;
+  }
   smsIds.push(retPduObj.params.message_id);
   retPduObjs.push(retPduObj);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
that.send(pduObj, function(err, retPduObj) {
smsIds.push(retPduObj.params.message_id);
retPduObjs.push(retPduObj);
that.log.silly(logPrefix + 'Got cb from that.send()');
log.silly('larvitsmpp: lib/session.js: sendLongSms() - Got callback from that.send()');
if (typeof cb === 'function' && smsIds.length === msgs.length) {
that.log.silly(logPrefix + 'All cbs returned, run the parent cb.');
cb(err, smsIds, retPduObjs);
if (typeof callback === 'function' && smsIds.length === msgs.length) {
log.silly('larvitsmpp: lib/session.js: sendLongSms() - All callbacks returned, run the parent callback.');
callback(err, smsIds, retPduObjs);
that.send(pduObj, function(err, retPduObj) {
if (err) {
if (typeof callback === 'function') {
callback(err);
}
return;
}
smsIds.push(retPduObj.params.message_id);
retPduObjs.push(retPduObj);
log.silly('larvitsmpp: lib/session.js: sendLongSms() - Got callback from that.send()');
if (typeof callback === 'function' && smsIds.length === msgs.length) {
log.silly('larvitsmpp: lib/session.js: sendLongSms() - All callbacks returned, run the parent callback.');
callback(err, smsIds, retPduObjs);
🧰 Tools
🪛 ESLint

[error] 349-349: Missing space before function parentheses.

(space-before-function-paren)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/session.js` around lines 349 - 357, In sendLongSms's inner that.send
callback, guard against err before dereferencing retPduObj by short-circuiting
on error: if (err) { if (typeof callback === 'function') callback(err); return;
} — only then access retPduObj.params.message_id and push to smsIds/retPduObjs
and continue to call the parent callback when smsIds.length === msgs.length;
update the callback invocation to ensure errors are propagated once and no
undefined retPduObj is accessed.

Comment thread lib/session.js
Comment on lines +385 to +386
var msgId = require('uuid').v4();
this.sendReturn(pduObj, 'ESME_ROK', {'message_id': msgId}, false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Multipart submits now have two conflicting response paths.

Line 386 already sends a submit_sm_resp for each part, but checkLongSmses() still emits an sms object with sendResp: smsResp and the original pduObjs. Existing handlers in README.md Line 185 and test/app.js Line 68 call sms.sendResp() on every sms event, so long messages will send a second response per part—and the later ids (smsId-2, smsId-3, …) will not match the UUIDs already returned from longSms().

Also applies to: 452-465

🧰 Tools
🪛 ESLint

[error] 385-385: Combine this with the previous 'var' statement.

(one-var)


[error] 385-385: Unexpected var, use let or const instead.

(no-var)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/session.js` around lines 385 - 386, The multipart submit flow currently
returns two conflicting responses per part: the inline sendReturn call in the
submit path (use of sendReturn with generated msgId) and the downstream
checkLongSmses path which emits an sms object containing sendResp/smsResp and
pduObjs; update the code so only one response path is used—remove or guard the
per-part sendReturn (the sendReturn call that sends 'ESME_ROK' with message_id)
when checkLongSmses/longSms handling will emit the sms event, and instead ensure
checkLongSmses emits the correct UUIDs (or returns the pdu-level ids) so
handlers calling sms.sendResp() receive the same IDs; specifically modify the
logic around sendReturn in the submit handler and the emission in checkLongSmses
(and related smsResp/pduObjs formation) so multipart messages send a single
consistent submit_sm_resp per part with matching IDs.

Comment thread lib/session.js
Comment on lines +496 to +507
function session(sock) {
var returnObj = new events.EventEmitter();

if (! options.log) {
options.log = new lUtils.Log();
}
utils.log = options.log;
log.silly('larvitsmpp: lib/session.js: session() - New session started from ' + sock.remoteAddress + ':' + sock.remotePort);

returnObj.log = options.log;
returnObj.loggedIn = false;

returnObj.log.silly(logPrefix + 'New session started');
// Sequence number used for commands initiated from us
returnObj.ourSeqNr = 1;

returnObj.loggedIn = false;
returnObj.ourSeqNr = 1; // Sequence number used for commands initiated from us
returnObj.sock = options.sock; // Make the socket transparent via the returned emitter
returnObj.incOurSeqNr = incOurSeqNr;
returnObj.closeSocket = closeSocket;
returnObj.sockWrite = sockWrite;
returnObj.send = send;
returnObj.sendReturn = sendReturn;
returnObj.sendSms = sendSms;
returnObj.utils = utils;
// Make the socket transparent via the returned emitter
returnObj.sock = sock;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Keep session() compatible with the current callers.

lib/client.js Line 64 and lib/server.js Line 100 still pass session({ sock, log }). This implementation immediately reads sock.remoteAddress and then registers sock.on(...), so those callers will now hand in a plain wrapper object and fail during session setup.

🛠️ Suggested compatibility shim
 function session(sock) {
-  var returnObj = new events.EventEmitter();
+  var returnObj = new events.EventEmitter();
+  sock = sock.sock || sock;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function session(sock) {
var returnObj = new events.EventEmitter();
if (! options.log) {
options.log = new lUtils.Log();
}
utils.log = options.log;
log.silly('larvitsmpp: lib/session.js: session() - New session started from ' + sock.remoteAddress + ':' + sock.remotePort);
returnObj.log = options.log;
returnObj.loggedIn = false;
returnObj.log.silly(logPrefix + 'New session started');
// Sequence number used for commands initiated from us
returnObj.ourSeqNr = 1;
returnObj.loggedIn = false;
returnObj.ourSeqNr = 1; // Sequence number used for commands initiated from us
returnObj.sock = options.sock; // Make the socket transparent via the returned emitter
returnObj.incOurSeqNr = incOurSeqNr;
returnObj.closeSocket = closeSocket;
returnObj.sockWrite = sockWrite;
returnObj.send = send;
returnObj.sendReturn = sendReturn;
returnObj.sendSms = sendSms;
returnObj.utils = utils;
// Make the socket transparent via the returned emitter
returnObj.sock = sock;
function session(sock) {
var returnObj = new events.EventEmitter();
sock = sock.sock || sock;
log.silly('larvitsmpp: lib/session.js: session() - New session started from ' + sock.remoteAddress + ':' + sock.remotePort);
returnObj.loggedIn = false;
// Sequence number used for commands initiated from us
returnObj.ourSeqNr = 1;
// Make the socket transparent via the returned emitter
returnObj.sock = sock;
🧰 Tools
🪛 ESLint

[error] 497-497: Unexpected var, use let or const instead.

(no-var)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/session.js` around lines 496 - 507, session currently assumes its
argument is a raw socket and immediately reads sock.remoteAddress and registers
sock.on, breaking callers that pass an options wrapper (e.g., { sock, log }).
Add a compatibility shim at the start of the session function: detect if the
argument is an object with a .sock property and, if so, extract sock = arg.sock
and optionally log = arg.log (or preserve existing log variable), otherwise keep
the existing behavior; ensure you guard uses of sock (remoteAddress/remotePort)
and event registration against undefined to maintain backward compatibility with
callers still passing a plain socket and with existing callers that pass { sock,
log } and expect session() to behave the same.

Comment thread lib/utils.js
Comment on lines 714 to 750
if (status === undefined || status === true || status === 2 || status === 'true') {
status = 2;
status = 2;
} else if (defs.consts.MESSAGE_STATE[status] !== undefined) {
status = defs.consts.MESSAGE_STATE[status];
status = defs.consts.MESSAGE_STATE[status];
} else if (defs.constsById.MESSAGE_STATE[status]) {
status = parseInt(status);
status = parseInt(status);
} else {
status = 5; // UNDELIVERABLE
status = 2; // UNDELIVERABLE
}

if (typeof cb !== 'function') {
cb = function () {};
if (typeof callback !== 'function') {
callback = function() {};
}

if (sms.smsId === undefined) {
const err = new Error('Trying to send DLR with no smsId.');
exports.log.warn(logPrefix + err.message);
return cb(err);
err = new Error('Trying to send DLR with no smsId.');
log.warn('larvitsmpp: lib/utils.js: smsDlr() - ' + err.message);
callback(err);
return;
}

shortMessage = 'id:' + sms.smsId + ' sub:001 ';

if (status === 2) {
shortMessage += 'dlvrd:1 ';
shortMessage += 'dlvrd:1 ';
} else {
shortMessage += 'dlvrd:0 ';
shortMessage += 'dlvrd:0 ';
}

shortMessage += 'submit date:' + smppDate(sms.submitTime);
shortMessage += ' done date:' + smppDate(new Date());
shortMessage += 'submit date:' + smppDate(sms.submitTime);
shortMessage += ' done date:' + smppDate(new Date());

if (status === 2) {
shortMessage += ' stat:DELIVRD err:0 text:xxx';
shortMessage += ' stat:DELIVRD err:000';
} else {
shortMessage += ' stat:UNDELIVERABLE err:1 text:xxx';
shortMessage += ' stat:UNDELIV err:001';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Keep the old DLR aliases and serialize the actual status text.

After the MESSAGE_STATE.UNDELIV rename, sms.sendDlr('UNDELIVERABLE') now falls through to status = 2, so it emits a delivered receipt. The same block also hardcodes every non-2 body to stat:UNDELIV, which mis-serializes EXPIRED, REJECTED, ENROUTE, and other valid states. README.md Line 198 still documents UNDELIVERABLE, so this is also a backwards-compat break.

🧰 Tools
🪛 ESLint

[error] 725-725: Missing space before function parentheses.

(space-before-function-paren)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils.js` around lines 714 - 750, The status parsing in smsDlr is
collapsing legacy aliases (e.g. "UNDELIVERABLE") into numeric 2 and then
hardcoding the receipt body; update smsDlr's status normalization to (1) accept
legacy string aliases by mapping known old names to the canonical text/ID (use
defs.consts.MESSAGE_STATE and defs.constsById.MESSAGE_STATE to resolve both
directions), (2) keep status as either a numeric id or canonical text as needed,
and (3) when composing shortMessage use the canonical status text (e.g. compute
a serializedStat variable from defs.constsById.MESSAGE_STATE[status] or
defs.consts.MESSAGE_STATE[status] and append ' stat:'+serializedStat+' err:...'
instead of always using DELIVRD/UNDELIV) while still defaulting to the
UNDELIVERABLE value if resolution fails; update the stat/err branches to use
that serializedStat rather than hardcoded values.

Comment thread test/app.js
Comment on lines +14 to +15
var redis = require("redis"),
client = redis.createClient('6379','host-IP');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a '^package\.json$' | while read -r file; do
  printf '%s -> redis=%s\n' "$file" "$(jq -r '.dependencies.redis // .devDependencies.redis // "not declared"' "$file")"
done

echo
rg -n --type js '\bredis\.createClient\s*\('

Repository: larvit/larvitsmpp

Length of output: 185


Add redis as a dependency and update the client initialization to use the current API.

redis is not declared in package.json, so the code won't run as-is. Additionally, redis.createClient('6379', 'host-IP') is the legacy v3 API. Modern redis versions (v4+) require a different approach:

const client = redis.createClient({
  host: 'host-IP',
  port: 6379
});
await client.connect();

Add redis to dependencies in package.json and update the client initialization accordingly.

🧰 Tools
🪛 ESLint

[error] 14-15: Combine this with the previous 'var' statement.

(one-var)


[error] 14-15: All 'var' declarations must be at the top of the function scope.

(vars-on-top)


[error] 14-15: Unexpected var, use let or const instead.

(no-var)


[error] 15-15: Expected indentation of 1 tab but found 8 spaces.

(indent)


[error] 15-15: A space is required after ','.

(comma-spacing)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/app.js` around lines 14 - 15, Add redis to package.json dependencies and
update the client initialization in test/app.js to use the v4 API: replace the
legacy redis.createClient('6379','host-IP') pattern with creating the client via
redis.createClient({ host: 'host-IP', port: 6379 }) and call await
client.connect() after creation; ensure the module import variable name (redis)
remains used and handle the returned client (client) lifetime and errors
appropriately.

Comment thread test/app.js
Comment on lines +95 to +111
request(url, {timeout: 60000} ,function (error, response, body) {
console.log('body:', body);
if (!error && response.statusCode == 200) {
accessLog.apiResponse = body;
if (sms.dlr === true) {
if (body && body.indexOf('Successful') !== -1) {
console.log('DELIVRD');
sms.sendDlr('DELIVRD');
} else {
sms.sendDlr('UNDELIV');
console.log('UNDELIV');
}
}
} else {
accessLog.apiResponse = error;
}
log.info(accessLog);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Send UNDELIV for transport failures too.

The else branch only logs the failed request. When the submitter asked for a DLR, non-200 responses and request errors currently leave the message without any terminal receipt.

🛠️ Suggested fix
 } else {
-  accessLog.apiResponse = error;
+  accessLog.apiResponse = error || body;
+  if (sms.dlr === true) {
+    sms.sendDlr('UNDELIV');
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
request(url, {timeout: 60000} ,function (error, response, body) {
console.log('body:', body);
if (!error && response.statusCode == 200) {
accessLog.apiResponse = body;
if (sms.dlr === true) {
if (body && body.indexOf('Successful') !== -1) {
console.log('DELIVRD');
sms.sendDlr('DELIVRD');
} else {
sms.sendDlr('UNDELIV');
console.log('UNDELIV');
}
}
} else {
accessLog.apiResponse = error;
}
log.info(accessLog);
request(url, {timeout: 60000} ,function (error, response, body) {
console.log('body:', body);
if (!error && response.statusCode == 200) {
accessLog.apiResponse = body;
if (sms.dlr === true) {
if (body && body.indexOf('Successful') !== -1) {
console.log('DELIVRD');
sms.sendDlr('DELIVRD');
} else {
sms.sendDlr('UNDELIV');
console.log('UNDELIV');
}
}
} else {
accessLog.apiResponse = error || body;
if (sms.dlr === true) {
sms.sendDlr('UNDELIV');
}
}
log.info(accessLog);
🧰 Tools
🪛 ESLint

[error] 95-95: Expected indentation of 2 tabs but found 16 spaces.

(indent)


[error] 95-95: There should be no space before ','.

(comma-spacing)


[error] 95-95: A space is required after ','.

(comma-spacing)


[error] 96-96: Expected indentation of 3 tabs but found 24 spaces.

(indent)


[error] 97-97: Expected indentation of 3 tabs but found 24 spaces.

(indent)


[error] 98-98: Expected indentation of 4 tabs but found 32 spaces.

(indent)


[error] 99-99: Expected indentation of 4 tabs but found 40 spaces.

(indent)


[error] 100-100: Expected indentation of 5 tabs but found 43 spaces.

(indent)


[error] 101-101: Expected indentation of 6 tabs but found 51 spaces.

(indent)


[error] 102-102: Expected indentation of 6 tabs but found 49 spaces.

(indent)


[error] 103-103: Expected indentation of 5 tabs but found 48 spaces.

(indent)


[error] 104-104: Expected indentation of 6 tabs but found 57 spaces.

(indent)


[error] 105-105: Expected indentation of 6 tabs but found 56 spaces.

(indent)


[error] 106-106: Expected indentation of 5 tabs but found 48 spaces.

(indent)


[error] 107-107: Expected indentation of 4 tabs but found 41 spaces.

(indent)


[error] 108-108: Expected indentation of 3 tabs but found 24 spaces.

(indent)


[error] 109-109: Expected indentation of 4 tabs but found 32 spaces.

(indent)


[error] 110-110: Expected indentation of 3 tabs but found 24 spaces.

(indent)


[error] 111-111: Expected indentation of 3 tabs but found 24 spaces.

(indent)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/app.js` around lines 95 - 111, The request callback currently only calls
sms.sendDlr('DELIVRD') on a 200 body match and logs errors otherwise; when
sms.dlr is true you must also send a terminal DLR for failures. Update the
request(...) callback (the anonymous function handling error, response, body) so
that in the else branch (non-200 responses or request errors) you set
accessLog.apiResponse to the error/response info and, if sms.dlr === true, call
sms.sendDlr('UNDELIV') before log.info(accessLog) to ensure transport failures
trigger the UNDELIV delivery receipt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant