diff --git a/cmd/server/db.go b/cmd/server/db.go index 4226c5fe..14cac6fb 100644 --- a/cmd/server/db.go +++ b/cmd/server/db.go @@ -1863,7 +1863,7 @@ func (db *DB) GetChannelMessages(channelHash string, limit, offset int, region . 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` + scopeCol + ` + obs.id, obs.name, o.snr, o.path_json, o.timestamp, t.route_type` + scopeCol + ` FROM observations o JOIN transmissions t ON t.id = o.transmission_id LEFT JOIN observers obs ON obs.rowid = o.observer_idx @@ -1871,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` + scopeCol + ` + o.observer_id, o.observer_name, o.snr, o.path_json, o.timestamp, t.route_type` + scopeCol + ` FROM observations o JOIN transmissions t ON t.id = o.transmission_id WHERE t.id IN (` + strings.Join(idPlaceholders, ",") + `) @@ -1896,8 +1896,9 @@ 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 + var routeType sql.NullInt64 var scopeName sql.NullString - scanArgs := []interface{}{&pktID, &txID, &pktHash, &dj, &fs, &obsID, &obsName, &snr, &pathJSON, &obsTs} + scanArgs := []interface{}{&pktID, &txID, &pktHash, &dj, &fs, &obsID, &obsName, &snr, &pathJSON, &obsTs, &routeType} if db.hasScopeName { scanArgs = append(scanArgs, &scopeName) } @@ -1953,6 +1954,7 @@ func (db *DB) GetChannelMessages(channelHash string, limit, offset int, region . "hops": hops, "snr": nullFloat(snr), "scope": nullStr(scopeName), + "routeType": nullInt(routeType), }, Repeats: 1, } diff --git a/cmd/server/store.go b/cmd/server/store.go index 5ad623c0..d7ad4307 100644 --- a/cmd/server/store.go +++ b/cmd/server/store.go @@ -5519,6 +5519,7 @@ func (s *PacketStore) GetChannelMessages(channelHash string, limit, offset int, "hops": hops, "snr": snrVal, "scope": strOrNil(tx.ScopeName), + "routeType": intPtrOrNil(tx.RouteType), }, Repeats: 1, Observers: observers, diff --git a/cmd/server/types.go b/cmd/server/types.go index 54738397..28537bb6 100644 --- a/cmd/server/types.go +++ b/cmd/server/types.go @@ -906,6 +906,7 @@ type ChannelMessageResp struct { Hops int `json:"hops"` SNR interface{} `json:"snr"` Scope interface{} `json:"scope"` + RouteType interface{} `json:"routeType"` } type ChannelMessagesResponse struct { diff --git a/public/channels.js b/public/channels.js index 23205825..40abb9db 100644 --- a/public/channels.js +++ b/public/channels.js @@ -664,6 +664,7 @@ 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, + routeType: c.packet.route_type ?? null, repeats: 1 }); continue; @@ -681,6 +682,7 @@ hops: 0, snr: c.packet.snr || null, observers: c.packet.observer_name ? [c.packet.observer_name] : [], scope: c.packet.scope_name || null, + routeType: c.packet.route_type ?? null, repeats: 1 }); } else { @@ -1420,6 +1422,7 @@ var snr = m.data?.snr ?? m.data?.packet?.snr ?? payload.SNR ?? null; var observer = m.data?.packet?.observer_name || m.data?.observer || null; var scope = m.data?.scope_name || m.data?.packet?.scope_name || null; + var routeType = m.data?.route_type ?? m.data?.packet?.route_type ?? null; // Update channel list entry — only once per unique packet hash var isFirstObservation = pktHash && !seenHashes.has(pktHash + ':' + channelKey); @@ -1472,6 +1475,7 @@ hops: payload.path_len || 0, snr: snr, scope: scope, + routeType: routeType, // #1498: mark as WS-pushed so a later REST replacement // (selectChannel / refreshMessages) can merge instead of // stomp. Without this flag the REST response wipes any @@ -2262,7 +2266,15 @@ 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}`); + // Scope only applies to TRANSPORT_FLOOD(0)/TRANSPORT_DIRECT(3) routes. + // Plain FLOOD(1)/DIRECT(2) never carry a scope — show nothing for those. + // A transport-eligible message with an empty scope means the region + // couldn't be determined (no configured region matched, or an + // HMAC collision made the match ambiguous) — show it as unknown + // rather than silently omitting the tag. + const isTransportRoute = msg.routeType === 0 || msg.routeType === 3; if (msg.scope) meta.push(`Scope: ${escapeHtml(msg.scope)}`); + else if (isTransportRoute) meta.push('Scope: unknown'); const safeId = btoa(encodeURIComponent(sender)); // #1367: emit BOTH the new chat-app class names (.ch-message /