feat: surface transport scope on channel messages

Adds scope_name to GetChannelMessages (both SQLite and in-memory
store paths) and to the general packet response shape, then renders
it as "Scope: <name>" in the channel chat meta line so operators can
see which region scope a message was transported under.
This commit is contained in:
dborup
2026-07-17 14:20:19 +02:00
parent 8c3e397d39
commit c686ae3f37
4 changed files with 19 additions and 3 deletions
+13 -3
View File
@@ -1856,10 +1856,14 @@ func (db *DB) GetChannelMessages(channelHash string, limit, offset int, region .
idPlaceholders[i] = "?"
obsArgs[i] = id
}
scopeCol := ""
if db.hasScopeName {
scopeCol = ", t.scope_name"
}
var obsSQL string
if db.isV3 {
obsSQL = `SELECT o.id, t.id, t.hash, t.decoded_json, t.first_seen,
obs.id, obs.name, o.snr, o.path_json, o.timestamp
obs.id, obs.name, o.snr, o.path_json, o.timestamp` + scopeCol + `
FROM observations o
JOIN transmissions t ON t.id = o.transmission_id
LEFT JOIN observers obs ON obs.rowid = o.observer_idx
@@ -1867,7 +1871,7 @@ func (db *DB) GetChannelMessages(channelHash string, limit, offset int, region .
ORDER BY o.id ASC`
} else {
obsSQL = `SELECT o.id, t.id, t.hash, t.decoded_json, t.first_seen,
o.observer_id, o.observer_name, o.snr, o.path_json, o.timestamp
o.observer_id, o.observer_name, o.snr, o.path_json, o.timestamp` + scopeCol + `
FROM observations o
JOIN transmissions t ON t.id = o.transmission_id
WHERE t.id IN (` + strings.Join(idPlaceholders, ",") + `)
@@ -1892,7 +1896,12 @@ func (db *DB) GetChannelMessages(channelHash string, limit, offset int, region .
var pktHash, dj, fs, obsID, obsName, pathJSON sql.NullString
var snr sql.NullFloat64
var obsTs sql.NullInt64
rows.Scan(&pktID, &txID, &pktHash, &dj, &fs, &obsID, &obsName, &snr, &pathJSON, &obsTs)
var scopeName sql.NullString
scanArgs := []interface{}{&pktID, &txID, &pktHash, &dj, &fs, &obsID, &obsName, &snr, &pathJSON, &obsTs}
if db.hasScopeName {
scanArgs = append(scanArgs, &scopeName)
}
rows.Scan(scanArgs...)
if !dj.Valid {
continue
}
@@ -1943,6 +1952,7 @@ func (db *DB) GetChannelMessages(channelHash string, limit, offset int, region .
"observers": []string{},
"hops": hops,
"snr": nullFloat(snr),
"scope": nullStr(scopeName),
},
Repeats: 1,
}
+2
View File
@@ -3843,6 +3843,7 @@ func txToMap(tx *StoreTx, includeObservations ...bool) map[string]interface{} {
"rssi": floatPtrOrNil(tx.RSSI),
"path_json": strOrNil(tx.PathJSON),
"direction": strOrNil(tx.Direction),
"scope_name": strOrNil(tx.ScopeName),
}
// Include parsed path array to match Node.js output shape
if hops := txGetParsedPath(tx); len(hops) > 0 {
@@ -5515,6 +5516,7 @@ func (s *PacketStore) GetChannelMessages(channelHash string, limit, offset int,
"observers": observers,
"hops": hops,
"snr": snrVal,
"scope": strOrNil(tx.ScopeName),
},
Repeats: 1,
Observers: observers,
+1
View File
@@ -905,6 +905,7 @@ type ChannelMessageResp struct {
Observers []string `json:"observers"`
Hops int `json:"hops"`
SNR interface{} `json:"snr"`
Scope interface{} `json:"scope"`
}
type ChannelMessagesResponse struct {
+3
View File
@@ -663,6 +663,7 @@
packetHash: c.packet.hash, packetId: c.packet.id,
hops: d.path_len || 0, snr: c.packet.snr || null,
observers: c.packet.observer_name ? [c.packet.observer_name] : [],
scope: c.packet.scope_name || null,
repeats: 1
});
continue;
@@ -679,6 +680,7 @@
packetHash: c.packet.hash, packetId: c.packet.id,
hops: 0, snr: c.packet.snr || null,
observers: c.packet.observer_name ? [c.packet.observer_name] : [],
scope: c.packet.scope_name || null,
repeats: 1
});
} else {
@@ -2258,6 +2260,7 @@
if (msg.observers?.length > 1) meta.push(`${msg.observers.length} observers`);
if (msg.hops > 0) meta.push(`${msg.hops} hops`);
if (msg.snr !== null && msg.snr !== undefined) meta.push(`SNR ${msg.snr}`);
if (msg.scope) meta.push(`Scope: ${escapeHtml(msg.scope)}`);
const safeId = btoa(encodeURIComponent(sender));
// #1367: emit BOTH the new chat-app class names (.ch-message /