Summary
lib/plugins/Label_1M_Slash.ts splits a /-delimited 1M ETA message and unconditionally indexes into results[0..9]. The only guard — if (results) — is a no-op because String.prototype.split(...).slice(1) always returns an array (truthy, possibly empty). Any 1M message that has fewer than 9 slash-or-newline-separated fields throws TypeError: Cannot read properties of undefined (reading 'replace') at line 41. MessageDecoder.decode() does not wrap plugin calls in try/catch, so the exception aborts the entire decode call for that message — the caller receives a thrown error, not a decoded: false result.
Location
lib/plugins/Label_1M_Slash.ts:23-55
const results = message.text.split(/\n|\//).slice(1);
if (results) { // <-- always truthy
...
decodeResult.raw.flight_number = results[0];
ResultFormatter.departureAirport(decodeResult, results[3]);
ResultFormatter.arrivalAirport(decodeResult, results[4]);
ResultFormatter.alternateAirport(decodeResult, results[5]);
ResultFormatter.arrivalRunway(
decodeResult,
results[8].replace(results[4], ''), // <-- throws when results.length <= 8
);
...
}
Reproduction
const decoder = new MessageDecoder();
decoder.decode({ label: '1M', text: '/BA0843' });
// Uncaught TypeError: Cannot read properties of undefined (reading 'replace')
// at Label_1M_Slash.decode (Label_1M_Slash.ts:41)
// at MessageDecoder.decode (MessageDecoder.ts:198)
Any 1M message shorter than the fully-fleshed variant-1 form (11 fields including the leading empty string) triggers this. Truncated messages are common on lossy ACARS links, so this is easily reachable in production data.
Root causes:
split(...).slice(1) always returns an array. if (results) is unconditionally true — it isn't guarding against anything and it doesn't check .length.
MessageDecoder.decode() (lib/MessageDecoder.ts:193-203) calls entry.plugin.decode(message, options) without a try/catch, so a thrown plugin error kills the whole decode call rather than falling through to the next candidate plugin.
Expected
decoder.decode({ label: '1M', text: '/BA0843' }) should return a DecodeResult (typically decoded: false) — never throw.
Actual
Uncaught TypeError propagates out of MessageDecoder.decode(). All subsequent (later-registered) plugins in the candidates list are skipped for this message.
Recommendation
Replace the dead guard with a real length check and, ideally, only mark decoded: true once you've committed to reading the required indices:
const results = message.text.split(/\n|\//).slice(1);
if (results.length < 10) {
decodeResult.decoded = false;
return decodeResult;
}
// … existing decode …
decodeResult.decoded = true;
decodeResult.decoder.decodeLevel = 'partial';
return decodeResult;
Also worth considering: harden MessageDecoder.decode() to catch (or at least log-and-skip) plugin exceptions so a single buggy plugin can't take down the whole decoder for a message. Filed as a separate concern; the primary fix belongs in this plugin.
Notes
- The existing test only covers the fully-fleshed happy-path variant-1 message; no truncated / short-form test exists.
decodeResult.decoded = true is set unconditionally after the if (results) block on line 57, so even if the exception were caught, the current shape marks the result as successfully decoded regardless of what actually happened above.
Summary
lib/plugins/Label_1M_Slash.tssplits a/-delimited 1M ETA message and unconditionally indexes intoresults[0..9]. The only guard —if (results)— is a no-op becauseString.prototype.split(...).slice(1)always returns an array (truthy, possibly empty). Any 1M message that has fewer than 9 slash-or-newline-separated fields throwsTypeError: Cannot read properties of undefined (reading 'replace')at line 41.MessageDecoder.decode()does not wrap plugin calls intry/catch, so the exception aborts the entire decode call for that message — the caller receives a thrown error, not adecoded: falseresult.Location
lib/plugins/Label_1M_Slash.ts:23-55Reproduction
Any 1M message shorter than the fully-fleshed variant-1 form (11 fields including the leading empty string) triggers this. Truncated messages are common on lossy ACARS links, so this is easily reachable in production data.
Root causes:
split(...).slice(1)always returns an array.if (results)is unconditionally true — it isn't guarding against anything and it doesn't check.length.MessageDecoder.decode()(lib/MessageDecoder.ts:193-203) callsentry.plugin.decode(message, options)without atry/catch, so a thrown plugin error kills the whole decode call rather than falling through to the next candidate plugin.Expected
decoder.decode({ label: '1M', text: '/BA0843' })should return aDecodeResult(typicallydecoded: false) — never throw.Actual
Uncaught
TypeErrorpropagates out ofMessageDecoder.decode(). All subsequent (later-registered) plugins in the candidates list are skipped for this message.Recommendation
Replace the dead guard with a real length check and, ideally, only mark
decoded: trueonce you've committed to reading the required indices:Also worth considering: harden
MessageDecoder.decode()to catch (or at least log-and-skip) plugin exceptions so a single buggy plugin can't take down the whole decoder for a message. Filed as a separate concern; the primary fix belongs in this plugin.Notes
decodeResult.decoded = trueis set unconditionally after theif (results)block on line 57, so even if the exception were caught, the current shape marks the result as successfully decoded regardless of what actually happened above.