Cleaned up the frame.ts by splitting payload parsing to subpackages

This commit is contained in:
2026-03-20 10:38:36 +01:00
parent 1aa8eb363f
commit 75e31c2008
26 changed files with 2695 additions and 2429 deletions

149
src/payload.item.ts Normal file
View File

@@ -0,0 +1,149 @@
import { FieldType, type Segment } from "@hamradio/packet";
import { DO_NOT_ARCHIVE_MARKER, DataType, type IPosition, type ItemPayload, type Payload } from "./frame.types";
import { attachExtras, decodeCommentExtras } from "./payload.extras";
import { isCompressedPosition, parseCompressedPosition, parseUncompressedPosition } from "./payload.position";
import Timestamp from "./timestamp";
export const decodeItemPayload = (
raw: string,
withStructure: boolean = false
): {
payload: Payload | null;
segment?: Segment[];
} => {
// Item format is similar to Object but name may be 3-9 chars (stored in a 9-char field)
// Example: )NNN... where ) is data type, next 9 chars are name, then state char, then timestamp, then position
if (raw.length < 12) return { payload: null }; // minimal: 1 + 3 + 1 + 7
let offset = 1; // skip data type identifier ')'
const segment: Segment[] = withStructure ? [] : [];
// Read 9-char name field (pad/truncate as present)
const rawName = raw.substring(offset, offset + 9);
const name = rawName.trimEnd();
if (withStructure) {
segment.push({
name: "item name",
data: new TextEncoder().encode(rawName).buffer,
isString: true,
fields: [{ type: FieldType.STRING, name: "name", length: 9 }]
});
}
offset += 9;
// State character: '*' = alive, '_' = killed
const stateChar = raw.charAt(offset);
if (stateChar !== "*" && stateChar !== "_") {
return { payload: null };
}
const alive = stateChar === "*";
if (withStructure) {
segment.push({
name: "item state",
data: new TextEncoder().encode(stateChar).buffer,
isString: true,
fields: [
{
type: FieldType.CHAR,
name: "State (* alive, _ killed)",
length: 1
}
]
});
}
offset += 1;
// Timestamp (7 chars)
const timeStr = raw.substring(offset, offset + 7);
const { timestamp, segment: timestampSection } = Timestamp.fromString(timeStr.substring(offset), withStructure);
if (!timestamp) return { payload: null };
if (timestampSection) segment.push(timestampSection);
offset += 7;
const isCompressed = isCompressedPosition(raw.substring(offset));
// eslint-disable-next-line no-useless-assignment
let position: IPosition | null = null;
// eslint-disable-next-line no-useless-assignment
let consumed = 0;
if (isCompressed) {
const { position: compressed, segment: compressedSection } = parseCompressedPosition(
raw.substring(offset),
withStructure
);
if (!compressed) return { payload: null };
position = {
latitude: compressed.latitude,
longitude: compressed.longitude,
symbol: compressed.symbol,
altitude: compressed.altitude
};
consumed = 13;
if (compressedSection) segment.push(compressedSection);
} else {
const { position: uncompressed, segment: uncompressedSection } = parseUncompressedPosition(
raw.substring(offset),
withStructure
);
if (!uncompressed) return { payload: null };
position = {
latitude: uncompressed.latitude,
longitude: uncompressed.longitude,
symbol: uncompressed.symbol,
ambiguity: uncompressed.ambiguity
};
consumed = 19;
if (uncompressedSection) segment.push(uncompressedSection);
}
offset += consumed;
const remainder = raw.substring(offset);
const doNotArchive = remainder.includes(DO_NOT_ARCHIVE_MARKER);
let comment = remainder;
const extras = decodeCommentExtras(comment, withStructure);
comment = extras.comment;
if (comment) {
position.comment = comment;
if (withStructure) {
segment.push({
name: "comment",
data: new TextEncoder().encode(remainder).buffer,
isString: true,
fields: extras.fields || []
});
}
} else if (withStructure && extras.fields) {
// No free-text comment, but extras fields exist: emit comment-only segment
segment.push({
name: "comment",
data: new TextEncoder().encode(remainder).buffer,
isString: true,
fields: extras.fields || []
});
}
const payload: ItemPayload = {
type: DataType.Item,
doNotArchive,
name,
alive,
position
};
attachExtras(payload, extras);
if (withStructure) {
return { payload, segment };
}
return { payload };
};
export default decodeItemPayload;