Add missing pathing information to frontend

This commit is contained in:
Jack Kingsman committed 2026-01-18 14:17:49 -08:00
1 parent b13af6433d
commit 13220c4a8f
19 files changed
+281 -40

No files matched your search

+1 -1
View File
@@ -188,7 +188,7 @@ messages (
text TEXT NOT NULL,
sender_timestamp INTEGER,
received_at INTEGER NOT NULL,
path_len INTEGER,
path TEXT, -- Hex-encoded routing path (2 chars per hop), null for outgoing
txt_type INTEGER DEFAULT 0,
signature TEXT,
outgoing INTEGER DEFAULT 0,
+1 -1
View File
@@ -37,7 +37,7 @@ CREATE TABLE IF NOT EXISTS messages (
text TEXT NOT NULL,
sender_timestamp INTEGER,
received_at INTEGER NOT NULL,
path_len INTEGER,
path TEXT,
txt_type INTEGER DEFAULT 0,
signature TEXT,
outgoing INTEGER DEFAULT 0,
+2 -2
View File
@@ -80,7 +80,7 @@ async def on_contact_message(event: "Event") -> None:
conversation_key=sender_pubkey,
sender_timestamp=payload.get("sender_timestamp"),
received_at=received_at,
path_len=payload.get("path_len"),
path=payload.get("path"),
txt_type=payload.get("txt_type", 0),
signature=payload.get("signature"),
)
@@ -100,7 +100,7 @@ async def on_contact_message(event: "Event") -> None:
"text": payload.get("text", ""),
"sender_timestamp": payload.get("sender_timestamp"),
"received_at": received_at,
"path_len": payload.get("path_len"),
"path": payload.get("path"),
"txt_type": payload.get("txt_type", 0),
"signature": payload.get("signature"),
"outgoing": False,
+166
View File
@@ -72,6 +72,20 @@ async def run_migrations(conn: aiosqlite.Connection) -> int:
await set_version(conn, 5)
applied += 1
# Migration 6: Replace path_len with path column in messages
if version < 6:
logger.info("Applying migration 6: replace path_len with path column")
await _migrate_006_replace_path_len_with_path(conn)
await set_version(conn, 6)
applied += 1
# Migration 7: Backfill path from raw_packets for decrypted messages
if version < 7:
logger.info("Applying migration 7: backfill path from raw_packets")
await _migrate_007_backfill_message_paths(conn)
await set_version(conn, 7)
applied += 1
if applied > 0:
logger.info(
"Applied %d migration(s), schema now at version %d", applied, await get_version(conn)
@@ -348,3 +362,155 @@ async def _migrate_005_backfill_payload_hashes(conn: aiosqlite.Connection) -> No
raise
await conn.commit()
async def _migrate_006_replace_path_len_with_path(conn: aiosqlite.Connection) -> None:
"""
Replace path_len INTEGER column with path TEXT column in messages table.
The path column stores the hex-encoded routing path bytes. Path length can
be derived from the hex string (2 chars per byte = 1 hop).
SQLite 3.35.0+ supports ALTER TABLE DROP COLUMN. For older versions,
we silently skip the drop (the column will remain but is unused).
"""
# First, add the new path column
try:
await conn.execute("ALTER TABLE messages ADD COLUMN path TEXT")
logger.debug("Added path column to messages table")
except aiosqlite.OperationalError as e:
if "duplicate column name" in str(e).lower():
logger.debug("messages.path already exists, skipping")
else:
raise
# Try to drop the old path_len column
try:
await conn.execute("ALTER TABLE messages DROP COLUMN path_len")
logger.debug("Dropped path_len from messages table")
except aiosqlite.OperationalError as e:
error_msg = str(e).lower()
if "no such column" in error_msg:
logger.debug("messages.path_len already dropped, skipping")
elif "syntax error" in error_msg or "drop column" in error_msg:
# SQLite version doesn't support DROP COLUMN - harmless, column stays
logger.debug("SQLite doesn't support DROP COLUMN, path_len column will remain")
else:
raise
await conn.commit()
def _extract_path_from_packet(raw_packet: bytes) -> str | None:
"""
Extract path hex string from a raw packet (migration-local copy of decoder logic).
Returns the path as a hex string, or None if packet is malformed.
"""
if len(raw_packet) < 2:
return None
try:
header = raw_packet[0]
route_type = header & 0x03
offset = 1
# Skip transport codes if present (TRANSPORT_FLOOD=0, TRANSPORT_DIRECT=3)
if route_type in (0x00, 0x03):
if len(raw_packet) < offset + 4:
return None
offset += 4
# Get path length
if len(raw_packet) < offset + 1:
return None
path_length = raw_packet[offset]
offset += 1
# Extract path bytes
if len(raw_packet) < offset + path_length:
return None
path_bytes = raw_packet[offset : offset + path_length]
return path_bytes.hex()
except (IndexError, ValueError):
return None
async def _migrate_007_backfill_message_paths(conn: aiosqlite.Connection) -> None:
"""
Backfill path column for messages that have linked raw_packets.
For each message with a linked raw_packet (via message_id), extract the
path from the raw packet and update the message.
Only updates incoming messages (outgoing=0) since outgoing messages
don't have meaningful path data.
"""
# Get count of messages that need backfill
cursor = await conn.execute(
"""
SELECT COUNT(*)
FROM messages m
JOIN raw_packets rp ON rp.message_id = m.id
WHERE m.path IS NULL AND m.outgoing = 0
"""
)
row = await cursor.fetchone()
total = row[0] if row else 0
if total == 0:
logger.debug("No messages need path backfill")
return
logger.info("Backfilling path for %d messages. This may take a while...", total)
# Process in batches
batch_size = 1000
processed = 0
updated = 0
cursor = await conn.execute(
"""
SELECT m.id, rp.data
FROM messages m
JOIN raw_packets rp ON rp.message_id = m.id
WHERE m.path IS NULL AND m.outgoing = 0
ORDER BY m.id ASC
"""
)
updates: list[tuple[str, int]] = [] # (path, message_id)
while True:
rows = await cursor.fetchmany(batch_size)
if not rows:
break
for row in rows:
message_id = row[0]
packet_data = bytes(row[1])
path_hex = _extract_path_from_packet(packet_data)
if path_hex is not None:
updates.append((path_hex, message_id))
processed += 1
if processed % 10000 == 0:
logger.info("Processed %d/%d messages...", processed, total)
# Apply updates in batches
if updates:
logger.info("Updating %d messages with path data...", len(updates))
for idx, (path_hex, message_id) in enumerate(updates, 1):
await conn.execute(
"UPDATE messages SET path = ? WHERE id = ?",
(path_hex, message_id),
)
updated += 1
if idx % 10000 == 0:
logger.info("Updated %d/%d messages...", idx, len(updates))
await conn.commit()
logger.info("Path backfill complete: %d messages updated", updated)
+1 -1
View File
@@ -74,7 +74,7 @@ class Message(BaseModel):
text: str
sender_timestamp: int | None = None
received_at: int
path_len: int | None = None
path: str | None = Field(default=None, description="Hex-encoded routing path (2 chars per hop)")
txt_type: int = 0
signature: str | None = None
outgoing: bool = False
+5 -4
View File
@@ -48,7 +48,7 @@ async def create_message_from_decrypted(
message_text: str,
timestamp: int,
received_at: int | None = None,
path_len: int | None = None,
path: str | None = None,
) -> int | None:
"""Create a message record from decrypted channel packet content.
@@ -62,7 +62,7 @@ async def create_message_from_decrypted(
message_text: The decrypted message content
timestamp: Sender timestamp from the packet
received_at: When the packet was received (defaults to now)
path_len: Path length from packet routing (None for historical decryption)
path: Hex-encoded routing path (None for historical decryption)
Returns the message ID if created, None if duplicate.
"""
@@ -83,6 +83,7 @@ async def create_message_from_decrypted(
conversation_key=channel_key_normalized,
sender_timestamp=timestamp,
received_at=received,
path=path,
)
if msg_id is None:
@@ -111,7 +112,7 @@ async def create_message_from_decrypted(
"text": text,
"sender_timestamp": timestamp,
"received_at": received,
"path_len": path_len,
"path": path,
"txt_type": 0,
"signature": None,
"outgoing": False,
@@ -283,7 +284,7 @@ async def _process_group_text(
message_text=decrypted.message,
timestamp=decrypted.timestamp,
received_at=timestamp,
path_len=packet_info.path_length if packet_info else None,
path=packet_info.path.hex() if packet_info else None,
)
return {
+5 -5
View File
@@ -293,7 +293,7 @@ class MessageRepository:
received_at: int,
conversation_key: str,
sender_timestamp: int | None = None,
path_len: int | None = None,
path: str | None = None,
txt_type: int = 0,
signature: str | None = None,
outgoing: bool = False,
@@ -307,7 +307,7 @@ class MessageRepository:
cursor = await db.conn.execute(
"""
INSERT OR IGNORE INTO messages (type, conversation_key, text, sender_timestamp,
received_at, path_len, txt_type, signature, outgoing)
received_at, path, txt_type, signature, outgoing)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
@@ -316,7 +316,7 @@ class MessageRepository:
text,
sender_timestamp,
received_at,
path_len,
path,
txt_type,
signature,
outgoing,
@@ -359,7 +359,7 @@ class MessageRepository:
text=row["text"],
sender_timestamp=row["sender_timestamp"],
received_at=row["received_at"],
path_len=row["path_len"],
path=row["path"],
txt_type=row["txt_type"],
signature=row["signature"],
outgoing=bool(row["outgoing"]),
@@ -419,7 +419,7 @@ class MessageRepository:
text=row["text"],
sender_timestamp=row["sender_timestamp"],
received_at=row["received_at"],
path_len=row["path_len"],
path=row["path"],
txt_type=row["txt_type"],
signature=row["signature"],
outgoing=bool(row["outgoing"]),
+6 -1
View File
@@ -5,7 +5,7 @@ from fastapi import APIRouter, BackgroundTasks
from pydantic import BaseModel, Field
from app.database import db
from app.decoder import try_decrypt_packet_with_channel_key
from app.decoder import parse_packet, try_decrypt_packet_with_channel_key
from app.packet_processor import create_message_from_decrypted
from app.repository import RawPacketRepository
@@ -65,6 +65,10 @@ async def _run_historical_decryption(channel_key_bytes: bytes, channel_key_hex:
result.message[:50] if result.message else "",
)
# Extract path from the raw packet for storage
packet_info = parse_packet(packet_data)
path_hex = packet_info.path.hex() if packet_info else None
msg_id = await create_message_from_decrypted(
packet_id=packet_id,
channel_key=channel_key_hex,
@@ -72,6 +76,7 @@ async def _run_historical_decryption(channel_key_bytes: bytes, channel_key_hex:
message_text=result.message,
timestamp=result.timestamp,
received_at=packet_timestamp, # Use original packet timestamp for correct ordering
path=path_hex,
)
if msg_id is not None:
@@ -38,12 +38,12 @@ var bv=Object.defineProperty;var Cv=(r,a,o)=>a in r?bv(r,a,{enumerable:!0,config
`).replace(K,"")}function ue(e,n,s){if(n=re(n),re(e)!==n&&s)throw Error(o(425))}function ve(){}var Pe=null,qe=null;function nt(e,n){return e==="textarea"||e==="noscript"||typeof n.children=="string"||typeof n.children=="number"||typeof n.dangerouslySetInnerHTML=="object"&&n.dangerouslySetInnerHTML!==null&&n.dangerouslySetInnerHTML.__html!=null}var rn=typeof setTimeout=="function"?setTimeout:void 0,Xt=typeof clearTimeout=="function"?clearTimeout:void 0,Dn=typeof Promise=="function"?Promise:void 0,wn=typeof queueMicrotask=="function"?queueMicrotask:typeof Dn<"u"?function(e){return Dn.resolve(null).then(e).catch(Zi)}:rn;function Zi(e){setTimeout(function(){throw e})}function ka(e,n){var s=n,c=0;do{var g=s.nextSibling;if(e.removeChild(s),g&&g.nodeType===8)if(s=g.data,s==="/$"){if(c===0){e.removeChild(g),$n(n);return}c--}else s!=="$"&&s!=="$?"&&s!=="$!"||c++;s=g}while(s);$n(n)}function Vr(e){for(;e!=null;e=e.nextSibling){var n=e.nodeType;if(n===1||n===3)break;if(n===8){if(n=e.data,n==="$"||n==="$!"||n==="$?")break;if(n==="/$")return null}}return e}function Hl(e){e=e.previousSibling;for(var n=0;e;){if(e.nodeType===8){var s=e.data;if(s==="$"||s==="$!"||s==="$?"){if(n===0)return e;n--}else s==="/$"&&n++}e=e.previousSibling}return null}var li=Math.random().toString(36).slice(2),dr="__reactFiber$"+li,Wi="__reactProps$"+li,_t="__reactContainer$"+li,qi="__reactEvents$"+li,pd="__reactListeners$"+li,md="__reactHandles$"+li;function ui(e){var n=e[dr];if(n)return n;for(var s=e.parentNode;s;){if(n=s[_t]||s[dr]){if(s=n.alternate,n.child!==null||s!==null&&s.child!==null)for(e=Hl(e);e!==null;){if(s=e[dr])return s;e=Hl(e)}return n}e=s,s=e.parentNode}return null}function ci(e){return e=e[dr]||e[_t],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function di(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(o(33))}function ys(e){return e[Wi]||null}var Ad=[],_s=-1;function Gi(e){return{current:e}}function Ft(e){0>_s||(e.current=Ad[_s],Ad[_s]=null,_s--)}function Tt(e,n){_s++,Ad[_s]=e.current,e.current=n}var Qi={},bn=Gi(Qi),On=Gi(!1),Eo=Qi;function ws(e,n){var s=e.type.contextTypes;if(!s)return Qi;var c=e.stateNode;if(c&&c.__reactInternalMemoizedUnmaskedChildContext===n)return c.__reactInternalMemoizedMaskedChildContext;var g={},_;for(_ in s)g[_]=n[_];return c&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=n,e.__reactInternalMemoizedMaskedChildContext=g),g}function zn(e){return e=e.childContextTypes,e!=null}function Vl(){Ft(On),Ft(bn)}function j0(e,n,s){if(bn.current!==Qi)throw Error(o(168));Tt(bn,n),Tt(On,s)}function H0(e,n,s){var c=e.stateNode;if(n=n.childContextTypes,typeof c.getChildContext!="function")return s;c=c.getChildContext();for(var g in c)if(!(g in n))throw Error(o(108,ke(e)||"Unknown",g));return W({},s,c)}function Ul(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Qi,Eo=bn.current,Tt(bn,e),Tt(On,On.current),!0}function V0(e,n,s){var c=e.stateNode;if(!c)throw Error(o(169));s?(e=H0(e,n,Eo),c.__reactInternalMemoizedMergedChildContext=e,Ft(On),Ft(bn),Tt(bn,e)):Ft(On),Tt(On,s)}var fi=null,Zl=!1,gd=!1;function U0(e){fi===null?fi=[e]:fi.push(e)}function Vx(e){Zl=!0,U0(e)}function Ki(){if(!gd&&fi!==null){gd=!0;var e=0,n=ht;try{var s=fi;for(ht=1;e<s.length;e++){var c=s[e];do c=c(!0);while(c!==null)}fi=null,Zl=!1}catch(g){throw fi!==null&&(fi=fi.slice(e+1)),gl(ta,Ki),g}finally{ht=n,gd=!1}}return null}var bs=[],Cs=0,Wl=null,ql=0,fr=[],hr=0,ko=null,hi=1,pi="";function So(e,n){bs[Cs++]=ql,bs[Cs++]=Wl,Wl=e,ql=n}function Z0(e,n,s){fr[hr++]=hi,fr[hr++]=pi,fr[hr++]=ko,ko=e;var c=hi;e=pi;var g=32-tt(c)-1;c&=~(1<<g),s+=1;var _=32-tt(n)+g;if(30<_){var I=g-g%5;_=(c&(1<<I)-1).toString(32),c>>=I,g-=I,hi=1<<32-tt(n)+g|s<<g|c,pi=_+e}else hi=1<<_|s<<g|c,pi=e}function xd(e){e.return!==null&&(So(e,1),Z0(e,1,0))}function vd(e){for(;e===Wl;)Wl=bs[--Cs],bs[Cs]=null,ql=bs[--Cs],bs[Cs]=null;for(;e===ko;)ko=fr[--hr],fr[hr]=null,pi=fr[--hr],fr[hr]=null,hi=fr[--hr],fr[hr]=null}var tr=null,nr=null,Mt=!1,Er=null;function W0(e,n){var s=gr(5,null,null,0);s.elementType="DELETED",s.stateNode=n,s.return=e,n=e.deletions,n===null?(e.deletions=[s],e.flags|=16):n.push(s)}function q0(e,n){switch(e.tag){case 5:var s=e.type;return n=n.nodeType!==1||s.toLowerCase()!==n.nodeName.toLowerCase()?null:n,n!==null?(e.stateNode=n,tr=e,nr=Vr(n.firstChild),!0):!1;case 6:return n=e.pendingProps===""||n.nodeType!==3?null:n,n!==null?(e.stateNode=n,tr=e,nr=null,!0):!1;case 13:return n=n.nodeType!==8?null:n,n!==null?(s=ko!==null?{id:hi,overflow:pi}:null,e.memoizedState={dehydrated:n,treeContext:s,retryLane:1073741824},s=gr(18,null,null,0),s.stateNode=n,s.return=e,e.child=s,tr=e,nr=null,!0):!1;default:return!1}}function yd(e){return(e.mode&1)!==0&&(e.flags&128)===0}function _d(e){if(Mt){var n=nr;if(n){var s=n;if(!q0(e,n)){if(yd(e))throw Error(o(418));n=Vr(s.nextSibling);var c=tr;n&&q0(e,n)?W0(c,s):(e.flags=e.flags&-4097|2,Mt=!1,tr=e)}}else{if(yd(e))throw Error(o(418));e.flags=e.flags&-4097|2,Mt=!1,tr=e}}}function G0(e){for(e=e.r Line truncated
Error generating stack: `+_.message+`
`+_.stack}return{value:e,source:n,stack:g,digest:null}}function Ud(e,n,s){return{value:e,source:null,stack:s??null,digest:n??null}}function Zd(e,n){try{console.error(n.value)}catch(s){setTimeout(function(){throw s})}}var Yx=typeof WeakMap=="function"?WeakMap:Map;function Sh(e,n,s){s=Ai(-1,s),s.tag=3,s.payload={element:null};var c=n.value;return s.callback=function(){fu||(fu=!0,sf=c),Zd(e,n)},s}function Bh(e,n,s){s=Ai(-1,s),s.tag=3;var c=e.type.getDerivedStateFromError;if(typeof c=="function"){var g=n.value;s.payload=function(){return c(g)},s.callback=function(){Zd(e,n)}}var _=e.stateNode;return _!==null&&typeof _.componentDidCatch=="function"&&(s.callback=function(){Zd(e,n),typeof c!="function"&&(Ji===null?Ji=new Set([this]):Ji.add(this));var I=n.stack;this.componentDidCatch(n.value,{componentStack:I!==null?I:""})}),s}function Ph(e,n,s){var c=e.pingCache;if(c===null){c=e.pingCache=new Yx;var g=new Set;c.set(n,g)}else g=c.get(n),g===void 0&&(g=new Set,c.set(n,g));g.has(s)||(g.add(s),e=dv.bind(null,e,n,s),n.then(e,e))}function Th(e){do{var n;if((n=e.tag===13)&&(n=e.memoizedState,n=n!==null?n.dehydrated!==null:!0),n)return e;e=e.return}while(e!==null);return null}function Dh(e,n,s,c,g){return(e.mode&1)===0?(e===n?e.flags|=65536:(e.flags|=128,s.flags|=131072,s.flags&=-52805,s.tag===1&&(s.alternate===null?s.tag=17:(n=Ai(-1,1),n.tag=2,Yi(s,n,1))),s.lanes|=1),e):(e.flags|=65536,e.lanes=g,e)}var Xx=N.ReactCurrentOwner,jn=!1;function Rn(e,n,s,c){n.child=e===null?Y0(n,null,s,c):ks(n,e.child,s,c)}function Rh(e,n,s,c,g){s=s.render;var _=n.ref;return Bs(n,g),c=Ld(e,n,s,c,_,g),s=Md(),e!==null&&!jn?(n.updateQueue=e.updateQueue,n.flags&=-2053,e.lanes&=~g,gi(e,n,g)):(Mt&&s&&xd(n),n.flags|=1,Rn(e,n,c,g),n.child)}function Fh(e,n,s,c,g){if(e===null){var _=s.type;return typeof _=="function"&&!hf(_)&&_.defaultProps===void 0&&s.compare===null&&s.defaultProps===void 0?(n.tag=15,n.type=_,Nh(e,n,_,c,g)):(e=xu(s.type,null,c,n,n.mode,g),e.ref=n.ref,e.return=n,n.child=e)}if(_=e.child,(e.lanes&g)===0){var I=_.memoizedProps;if(s=s.compare,s=s!==null?s:zr,s(I,c)&&e.ref===n.ref)return gi(e,n,g)}return n.flags|=1,e=ro(_,c),e.ref=n.ref,e.return=n,n.child=e}function Nh(e,n,s,c,g){if(e!==null){var _=e.memoizedProps;if(zr(_,c)&&e.ref===n.ref)if(jn=!1,n.pendingProps=c=_,(e.lanes&g)!==0)(e.flags&131072)!==0&&(jn=!0);else return n.lanes=e.lanes,gi(e,n,g)}return Wd(e,n,s,c,g)}function Lh(e,n,s){var c=n.pendingProps,g=c.children,_=e!==null?e.memoizedState:null;if(c.mode==="hidden")if((n.mode&1)===0)n.memoizedState={baseLanes:0,cachePool:null,transitions:null},Tt(Rs,rr),rr|=s;else{if((s&1073741824)===0)return e=_!==null?_.baseLanes|s:s,n.lanes=n.childLanes=1073741824,n.memoizedState={baseLanes:e,cachePool:null,transitions:null},n.updateQueue=null,Tt(Rs,rr),rr|=e,null;n.memoizedState={baseLanes:0,cachePool:null,transitions:null},c=_!==null?_.baseLanes:s,Tt(Rs,rr),rr|=c}else _!==null?(c=_.baseLanes|s,n.memoizedState=null):c=s,Tt(Rs,rr),rr|=c;return Rn(e,n,g,s),n.child}function Mh(e,n){var s=n.ref;(e===null&&s!==null||e!==null&&e.ref!==s)&&(n.flags|=512,n.flags|=2097152)}function Wd(e,n,s,c,g){var _=zn(s)?Eo:bn.current;return _=ws(n,_),Bs(n,g),s=Ld(e,n,s,c,_,g),c=Md(),e!==null&&!jn?(n.updateQueue=e.updateQueue,n.flags&=-2053,e.lanes&=~g,gi(e,n,g)):(Mt&&c&&xd(n),n.flags|=1,Rn(e,n,s,g),n.child)}function Ih(e,n,s,c,g){if(zn(s)){var _=!0;Ul(n)}else _=!1;if(Bs(n,g),n.stateNode===null)au(e,n),Eh(n,s,c),Vd(n,s,c,g),c=!0;else if(e===null){var I=n.stateNode,q=n.memoizedProps;I.props=q;var Y=I.context,ce=s.contextType;typeof ce=="object"&&ce!==null?ce=pr(ce):(ce=zn(s)?Eo:bn.current,ce=ws(n,ce));var _e=s.getDerivedStateFromProps,we=typeof _e=="function"||typeof I.getSnapshotBeforeUpdate=="function";we||typeof I.UNSAFE_componentWillReceiveProps!="function"&&typeof I.componentWillReceiveProps!="function"||(q!==c||Y!==ce)&&kh(n,I,c,ce),$i=!1;var ye=n.memoizedState;I.state=ye,Xl(n,c,I,g),Y=n.memoizedState,q!==c||ye!==Y||On.current||$i?(typeof _e=="function"&&(Hd(n,s,_e,c),Y=n.memoizedState),(q=$i||Ch(n,s,q,c,ye,Y,ce))?(we||typeof I.UNSAFE_componentWillMount!="function"&&typeof I.componentWillMount!="function"||(typeof I.componentWillMount=="function"&&I.componentWillMount(),typeof I.UNSAFE_componentWillMount=="function"&&I.UNSAFE_componentWillMount()),typeof I.componentDidMount=="function"&&(n.flags|=4194308)):(typeof I.componentDidMount=="function"&&(n.flags|=4194308),n.memoizedProps=c,n.memoizedState=Y),I.props=c,I.state=Y,I.context=ce,c=q):(typeof I.componentDidMount=="function"&&(n.flags|=4194308),c=!1)}else{I=n.stateNode,J0(e,n),q=n.memoizedProps,ce=n.type===n.elementType?q:kr(n.type,q),I.props=ce,we=n.pendingProps,ye=I.context,Y=s.contextType,typeof Y=="object"&&Y!==null?Y=pr(Y):(Y=zn(s)?Eo:bn.current,Y=ws(n,Y));var Fe=s.getDerivedStateFromProps;(_e=typeof Fe=="function"||typeof I.getSnapshotBeforeUpdate=="function")||typeof I.UNSAFE_componentWillReceiveProps!="function"&&typeof I.componentWillReceiveProps!="function"||(q!==we||ye!==Y)&&kh(n,I,c,Y),$i=!1,ye=n.m Line truncated
`)}function Ls(r,a,o){const l=Math.floor(Date.now()/1e3);return{id:-Date.now(),type:"PRIV",conversation_key:r,text:a,sender_timestamp:l,received_at:l,path_len:null,txt_type:0,signature:null,outgoing:o,acked:1}}function zv(r){const a=F.useRef(null),o=F.useRef(null),l=F.useCallback(()=>{o.current!==null&&(clearInterval(o.current),o.current=null),a.current=null},[]),d=F.useCallback(async()=>{const m=a.current;if(!m)return;let h=null,x=null;for(let p=0;p<2;p++)try{h=await st.requestTelemetry(m.conversationId,"");break}catch(y){x=y instanceof Error?y:new Error("Unknown error"),p===0&&await new Promise(v=>setTimeout(v,2e3))}if(h){const p=Ls(m.conversationId,Ov(m,h),!1);r(y=>[...y,p])}else{const p=Ls(m.conversationId,`Duty cycle poll failed after retry: ${(x==null?void 0:x.message)??"Unknown error"}`,!1);r(y=>[...y,p])}},[r]),f=F.useCallback(async(m,h)=>{const x=m.trim().toLowerCase();if(x==="dutycycle_start"){l();let p=null,y=null;for(let k=0;k<2;k++)try{p=await st.requestTelemetry(h,"");break}catch(T){y=T instanceof Error?T:new Error("Unknown error"),k===0&&await new Promise(C=>setTimeout(C,2e3))}if(!p){const k=Ls(h,`Failed to start duty cycle tracking after retry: ${(y==null?void 0:y.message)??"Unknown error"}`,!1);return r(T=>[...T,k]),!0}const v=Math.floor(Date.now()/1e3);a.current={startTime:v,uptime:p.uptime_seconds,txAirtime:p.airtime_seconds,rxAirtime:p.rx_airtime_seconds,sentFlood:p.sent_flood,sentDirect:p.sent_direct,recvFlood:p.recv_flood,recvDirect:p.recv_direct,conversationId:h};const w=Ls(h,`Airtime/duty cycle statistics gathering begins at ${v}. Logs will follow every 5 minutes. To stop, run dutycycle_stop or navigate away from this conversation.`,!1);return r(k=>[...k,w]),o.current=window.setInterval(d,Mv),!0}if(x==="dutycycle_stop"){if(a.current&&a.current.conversationId===h){await d(),l();const p=Ls(h,"Airtime/duty cycle statistics gathering stopped.",!1);r(y=>[...y,p])}else{const p=Ls(h,"Duty cycle tracking is not active.",!1);r(y=>[...y,p])}return!0}return!1},[r,l,d]);return F.useEffect(()=>()=>{o.current!==null&&clearInterval(o.current)},[]),{handleAirtimeCommand:f,stopTracking:l}}function Iu(r){if(r<60)return`${r}s`;const a=Math.floor(r/86400),o=Math.floor(r%86400/3600),l=Math.floor(r%3600/60);return a>0?o>0&&l>0?`${a}d${o}h${l}m`:o>0?`${a}d${o}h`:l>0?`${a}d${l}m`:`${a}d`:o>0?l>0?`${o}h${l}m`:`${o}h`:`${l}m`}function jv(r){return["Telemetry",`Battery Voltage: ${r.battery_volts.toFixed(3)}V`,`Uptime: ${Iu(r.uptime_seconds)}`,`TX Airtime: ${Iu(r.airtime_seconds)}`,`RX Airtime: ${Iu(r.rx_airtime_seconds)}`,"",`Noise Floor: ${r.noise_floor_dbm} dBm`,`Last RSSI: ${r.last_rssi_dbm} dBm`,`Last SNR: ${r.last_snr_db.toFixed(1)} dB`,"",`Packets: ${r.packets_received.toLocaleString()} rx / ${r.packets_sent.toLocaleString()} tx`,`Flood: ${r.recv_flood.toLocaleString()} rx / ${r.sent_flood.toLocaleString()} tx`,`Direct: ${r.recv_direct.toLocaleString()} rx / ${r.sent_direct.toLocaleString()} tx`,`Duplicates: ${r.flood_dups.toLocaleString()} flood / ${r.direct_dups.toLocaleString()} direct`,"",`TX Queue: ${r.tx_queue_len}`,`Debug Flags: ${r.full_events}`].join(`
`)}function Ls(r,a,o){const l=Math.floor(Date.now()/1e3);return{id:-Date.now(),type:"PRIV",conversation_key:r,text:a,sender_timestamp:l,received_at:l,path:null,txt_type:0,signature:null,outgoing:o,acked:1}}function zv(r){const a=F.useRef(null),o=F.useRef(null),l=F.useCallback(()=>{o.current!==null&&(clearInterval(o.current),o.current=null),a.current=null},[]),d=F.useCallback(async()=>{const m=a.current;if(!m)return;let h=null,x=null;for(let p=0;p<2;p++)try{h=await st.requestTelemetry(m.conversationId,"");break}catch(y){x=y instanceof Error?y:new Error("Unknown error"),p===0&&await new Promise(v=>setTimeout(v,2e3))}if(h){const p=Ls(m.conversationId,Ov(m,h),!1);r(y=>[...y,p])}else{const p=Ls(m.conversationId,`Duty cycle poll failed after retry: ${(x==null?void 0:x.message)??"Unknown error"}`,!1);r(y=>[...y,p])}},[r]),f=F.useCallback(async(m,h)=>{const x=m.trim().toLowerCase();if(x==="dutycycle_start"){l();let p=null,y=null;for(let k=0;k<2;k++)try{p=await st.requestTelemetry(h,"");break}catch(T){y=T instanceof Error?T:new Error("Unknown error"),k===0&&await new Promise(C=>setTimeout(C,2e3))}if(!p){const k=Ls(h,`Failed to start duty cycle tracking after retry: ${(y==null?void 0:y.message)??"Unknown error"}`,!1);return r(T=>[...T,k]),!0}const v=Math.floor(Date.now()/1e3);a.current={startTime:v,uptime:p.uptime_seconds,txAirtime:p.airtime_seconds,rxAirtime:p.rx_airtime_seconds,sentFlood:p.sent_flood,sentDirect:p.sent_direct,recvFlood:p.recv_flood,recvDirect:p.recv_direct,conversationId:h};const w=Ls(h,`Airtime/duty cycle statistics gathering begins at ${v}. Logs will follow every 5 minutes. To stop, run dutycycle_stop or navigate away from this conversation.`,!1);return r(k=>[...k,w]),o.current=window.setInterval(d,Mv),!0}if(x==="dutycycle_stop"){if(a.current&&a.current.conversationId===h){await d(),l();const p=Ls(h,"Airtime/duty cycle statistics gathering stopped.",!1);r(y=>[...y,p])}else{const p=Ls(h,"Duty cycle tracking is not active.",!1);r(y=>[...y,p])}return!0}return!1},[r,l,d]);return F.useEffect(()=>()=>{o.current!==null&&clearInterval(o.current)},[]),{handleAirtimeCommand:f,stopTracking:l}}function Iu(r){if(r<60)return`${r}s`;const a=Math.floor(r/86400),o=Math.floor(r%86400/3600),l=Math.floor(r%3600/60);return a>0?o>0&&l>0?`${a}d${o}h${l}m`:o>0?`${a}d${o}h`:l>0?`${a}d${l}m`:`${a}d`:o>0?l>0?`${o}h${l}m`:`${o}h`:`${l}m`}function jv(r){return["Telemetry",`Battery Voltage: ${r.battery_volts.toFixed(3)}V`,`Uptime: ${Iu(r.uptime_seconds)}`,`TX Airtime: ${Iu(r.airtime_seconds)}`,`RX Airtime: ${Iu(r.rx_airtime_seconds)}`,"",`Noise Floor: ${r.noise_floor_dbm} dBm`,`Last RSSI: ${r.last_rssi_dbm} dBm`,`Last SNR: ${r.last_snr_db.toFixed(1)} dB`,"",`Packets: ${r.packets_received.toLocaleString()} rx / ${r.packets_sent.toLocaleString()} tx`,`Flood: ${r.recv_flood.toLocaleString()} rx / ${r.sent_flood.toLocaleString()} tx`,`Direct: ${r.recv_direct.toLocaleString()} rx / ${r.sent_direct.toLocaleString()} tx`,`Duplicates: ${r.flood_dups.toLocaleString()} flood / ${r.direct_dups.toLocaleString()} direct`,"",`TX Queue: ${r.tx_queue_len}`,`Debug Flags: ${r.full_events}`].join(`
`)}function Hv(r){if(r.length===0)return`Neighbors
No neighbors reported`;const a=[...r].sort((l,d)=>d.snr-l.snr),o=[`Neighbors (${a.length})`];for(const l of a){const d=l.name||l.pubkey_prefix,f=l.snr>=0?`+${l.snr.toFixed(1)}`:l.snr.toFixed(1);o.push(`${d}, ${f} dB [${Iu(l.last_heard_seconds)} ago]`)}return o.join(`
`)}function Vv(r){if(r.length===0)return`ACL
No ACL entries`;const a=[`ACL (${r.length})`];for(const o of r){const l=o.name||o.pubkey_prefix;a.push(`${l}: ${o.permission_name}`)}return a.join(`
`)}function Io(r,a,o,l=0){const d=Math.floor(Date.now()/1e3);return{id:-Date.now()-l,type:"PRIV",conversation_key:r,text:a,sender_timestamp:d,received_at:d,path_len:null,txt_type:0,signature:null,outgoing:o,acked:1}}function Uv(r,a,o){const[l,d]=F.useState(!1),{handleAirtimeCommand:f,stopTracking:m}=zv(o);F.useEffect(()=>{d(!1),m()},[r==null?void 0:r.id,m]);const h=F.useMemo(()=>{if(!r||r.type!=="contact")return!1;const y=a.find(v=>v.public_key===r.id);return(y==null?void 0:y.type)===l0},[r,a]),x=F.useCallback(async y=>{if(!(!r||r.type!=="contact")&&h)try{const v=await st.requestTelemetry(r.id,y),w=Io(r.id,jv(v),!1,0),k=Io(r.id,Hv(v.neighbors),!1,1),T=Io(r.id,Vv(v.acl),!1,2);o(C=>[...C,w,k,T]),d(!0)}catch(v){const w=Io(r.id,`Telemetry request failed: ${v instanceof Error?v.message:"Unknown error"}`,!1,0);o(k=>[...k,w])}},[r,h,o]),p=F.useCallback(async y=>{if(!r||r.type!=="contact"||!h||!l||await f(y,r.id))return;const w=Io(r.id,`> ${y}`,!0,0);o(k=>[...k,w]);try{const k=await st.sendRepeaterCommand(r.id,y),T=Io(r.id,k.response,!1,1);k.sender_timestamp&&(T.sender_timestamp=k.sender_timestamp),o(C=>[...C,T])}catch(k){const T=Io(r.id,`Command failed: ${k instanceof Error?k.message:"Unknown error"}`,!1,1);o(C=>[...C,T])}},[r,h,l,o,f]);return{repeaterLoggedIn:l,activeContactIsRepeater:h,handleTelemetryRequest:x,handleRepeaterCommand:p}}const Zv=12;function Ws(r){return r.slice(0,Zv)}function BA(r,a){return!r||!a?!1:Ws(r)===Ws(a)}function bi(r,a){return r||Ws(a)}const Uf="remoteterm-lastMessageTime";function PA(r){try{const a=localStorage.getItem(r);return a?JSON.parse(a):{}}catch{return{}}}function Wv(r,a){try{localStorage.setItem(r,JSON.stringify(a))}catch{}}function Bp(){return PA(Uf)}function kf(r,a){const o=PA(Uf);return(!o[r]||a>o[r])&&(o[r]=a,Wv(Uf,o)),o}function Dr(r,a){return r==="channel"?`channel-${a}`:`contact-${Ws(a)}`}function Pp(r,a){if(!a)return!1;const o=a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`@\\[${o}\\]`,"i").test(r)}function qv(r,a,o,l=null){const[d,f]=F.useState({}),[m,h]=F.useState({}),[x,p]=F.useState(Bp),y=F.useRef(l);F.useEffect(()=>{y.current=l},[l]);const v=F.useRef(new Set),w=F.useRef(new Set);F.useEffect(()=>{const D=r.filter(N=>!v.current.has(N.key)),P=a.filter(N=>N.public_key&&!w.current.has(N.public_key));if(D.length===0&&P.length===0)return;D.forEach(N=>v.current.add(N.key)),P.forEach(N=>w.current.add(N.public_key)),(async()=>{const N=[...D.map(M=>({type:"CHAN",conversation_key:M.key})),...P.map(M=>({type:"PRIV",conversation_key:M.public_key}))];if(N.length!==0)try{const M=await st.getMessagesBulk(N,a0),z={},j={},H={};for(const te of D){const Z=M[`CHAN:${te.key}`]||[];if(Z.length>0){const V=Dr("channel",te.key),ne=te.last_read_at||0,X=Z.filter(de=>!de.outgoing&&de.received_at>ne);X.length>0&&(z[V]=X.length,X.some(de=>Pp(de.text,y.current))&&(j[V]=!0));const ie=Math.max(...Z.map(de=>de.received_at));H[V]=ie,kf(V,ie)}}for(const te of P){const Z=M[`PRIV:${te.public_key}`]||[];if(Z.length>0){const V=Dr("contact",te.public_key),ne=te.last_read_at||0,X=Z.filter(de=>!de.outgoing&&de.received_at>ne);X.length>0&&(z[V]=X.length,X.some(de=>Pp(de.text,y.current))&&(j[V]=!0));const ie=Math.max(...Z.map(de=>de.received_at));H[V]=ie,kf(V,ie)}}Object.keys(z).length>0&&f(te=>({...te,...z})),Object.keys(j).length>0&&h(te=>({...te,...j})),p(Bp())}catch(M){console.error("Failed to fetch messages bulk:",M)}})()},[r,a]),F.useEffect(()=>{if(o&&o.type!=="raw"&&o.type!=="map"){const D=Dr(o.type,o.id);f(P=>{if(P[D]){const S={...P};return delete S[D],S}return P}),h(P=>{if(P[D]){const S={...P};return delete S[D],S}return P}),o.type==="channel"?st.markChannelRead(o.id).catch(P=>{console.error("Failed to mark channel as read on server:",P)}):o.type==="contact"&&st.markContactRead(o.id).catch(P=>{console.error("Failed to mark contact as read on server:",P)})}},[o]);const k=F.useCallback((D,P)=>{f(S=>({...S,[D]:(S[D]||0)+1})),P&&h(S=>({...S,[D]:!0}))},[]),T=F.useCallback(()=>{f({}),h({}),st.markAllRead().catch(D=>{console.error("Failed to mark all as read on server:",D)})},[]),C=F.useCallback(D=>{if(D.type==="raw"||D.type==="map")return;const P=Dr(D.type,D.id);f(S=>{if(S[P]){const N={...S};return delete N[P],N}return S}),h(S=>{if(S[P]){const N={...S};return delete N[P],N}return S}),D.type==="channel"?st.markChannelRead(D.id).catch(S=>{console.error("Failed to mark channel as read on server:",S)}):D.type==="contact"&&st.markContactRead(D.id).catch(S=>{console.error("Failed to mark contact as read on server:",S)})},[]),E=F.useCallback(D=>{let P=null;if(D.type==="CHAN"&&D.conversation_key?P=Dr("channel",D.conversation_key):D.type==="PRIV"&&D.conversation_key&&(P=Dr("contact",D.conversation_key)),P){const S=D.received_at||Math.floor(Date.now()/1e3),N=kf(P,S);p(N)}},[]);return{unreadCounts:d,mentions:m,lastMessageTimes:x,incrementUnread:k,markAllRead:T,markConversationRead:C,trackNewMessage:E}}var u0=SA();const TA=Qs(u0);function Gv(r){if(typeof document>"u")return;let a=document.head||document.get Line truncated
`)}function Io(r,a,o,l=0){const d=Math.floor(Date.now()/1e3);return{id:-Date.now()-l,type:"PRIV",conversation_key:r,text:a,sender_timestamp:d,received_at:d,path:null,txt_type:0,signature:null,outgoing:o,acked:1}}function Uv(r,a,o){const[l,d]=F.useState(!1),{handleAirtimeCommand:f,stopTracking:m}=zv(o);F.useEffect(()=>{d(!1),m()},[r==null?void 0:r.id,m]);const h=F.useMemo(()=>{if(!r||r.type!=="contact")return!1;const y=a.find(v=>v.public_key===r.id);return(y==null?void 0:y.type)===l0},[r,a]),x=F.useCallback(async y=>{if(!(!r||r.type!=="contact")&&h)try{const v=await st.requestTelemetry(r.id,y),w=Io(r.id,jv(v),!1,0),k=Io(r.id,Hv(v.neighbors),!1,1),T=Io(r.id,Vv(v.acl),!1,2);o(C=>[...C,w,k,T]),d(!0)}catch(v){const w=Io(r.id,`Telemetry request failed: ${v instanceof Error?v.message:"Unknown error"}`,!1,0);o(k=>[...k,w])}},[r,h,o]),p=F.useCallback(async y=>{if(!r||r.type!=="contact"||!h||!l||await f(y,r.id))return;const w=Io(r.id,`> ${y}`,!0,0);o(k=>[...k,w]);try{const k=await st.sendRepeaterCommand(r.id,y),T=Io(r.id,k.response,!1,1);k.sender_timestamp&&(T.sender_timestamp=k.sender_timestamp),o(C=>[...C,T])}catch(k){const T=Io(r.id,`Command failed: ${k instanceof Error?k.message:"Unknown error"}`,!1,1);o(C=>[...C,T])}},[r,h,l,o,f]);return{repeaterLoggedIn:l,activeContactIsRepeater:h,handleTelemetryRequest:x,handleRepeaterCommand:p}}const Zv=12;function Ws(r){return r.slice(0,Zv)}function BA(r,a){return!r||!a?!1:Ws(r)===Ws(a)}function bi(r,a){return r||Ws(a)}const Uf="remoteterm-lastMessageTime";function PA(r){try{const a=localStorage.getItem(r);return a?JSON.parse(a):{}}catch{return{}}}function Wv(r,a){try{localStorage.setItem(r,JSON.stringify(a))}catch{}}function Bp(){return PA(Uf)}function kf(r,a){const o=PA(Uf);return(!o[r]||a>o[r])&&(o[r]=a,Wv(Uf,o)),o}function Dr(r,a){return r==="channel"?`channel-${a}`:`contact-${Ws(a)}`}function Pp(r,a){if(!a)return!1;const o=a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`@\\[${o}\\]`,"i").test(r)}function qv(r,a,o,l=null){const[d,f]=F.useState({}),[m,h]=F.useState({}),[x,p]=F.useState(Bp),y=F.useRef(l);F.useEffect(()=>{y.current=l},[l]);const v=F.useRef(new Set),w=F.useRef(new Set);F.useEffect(()=>{const D=r.filter(N=>!v.current.has(N.key)),P=a.filter(N=>N.public_key&&!w.current.has(N.public_key));if(D.length===0&&P.length===0)return;D.forEach(N=>v.current.add(N.key)),P.forEach(N=>w.current.add(N.public_key)),(async()=>{const N=[...D.map(M=>({type:"CHAN",conversation_key:M.key})),...P.map(M=>({type:"PRIV",conversation_key:M.public_key}))];if(N.length!==0)try{const M=await st.getMessagesBulk(N,a0),z={},j={},H={};for(const te of D){const Z=M[`CHAN:${te.key}`]||[];if(Z.length>0){const V=Dr("channel",te.key),ne=te.last_read_at||0,X=Z.filter(de=>!de.outgoing&&de.received_at>ne);X.length>0&&(z[V]=X.length,X.some(de=>Pp(de.text,y.current))&&(j[V]=!0));const ie=Math.max(...Z.map(de=>de.received_at));H[V]=ie,kf(V,ie)}}for(const te of P){const Z=M[`PRIV:${te.public_key}`]||[];if(Z.length>0){const V=Dr("contact",te.public_key),ne=te.last_read_at||0,X=Z.filter(de=>!de.outgoing&&de.received_at>ne);X.length>0&&(z[V]=X.length,X.some(de=>Pp(de.text,y.current))&&(j[V]=!0));const ie=Math.max(...Z.map(de=>de.received_at));H[V]=ie,kf(V,ie)}}Object.keys(z).length>0&&f(te=>({...te,...z})),Object.keys(j).length>0&&h(te=>({...te,...j})),p(Bp())}catch(M){console.error("Failed to fetch messages bulk:",M)}})()},[r,a]),F.useEffect(()=>{if(o&&o.type!=="raw"&&o.type!=="map"){const D=Dr(o.type,o.id);f(P=>{if(P[D]){const S={...P};return delete S[D],S}return P}),h(P=>{if(P[D]){const S={...P};return delete S[D],S}return P}),o.type==="channel"?st.markChannelRead(o.id).catch(P=>{console.error("Failed to mark channel as read on server:",P)}):o.type==="contact"&&st.markContactRead(o.id).catch(P=>{console.error("Failed to mark contact as read on server:",P)})}},[o]);const k=F.useCallback((D,P)=>{f(S=>({...S,[D]:(S[D]||0)+1})),P&&h(S=>({...S,[D]:!0}))},[]),T=F.useCallback(()=>{f({}),h({}),st.markAllRead().catch(D=>{console.error("Failed to mark all as read on server:",D)})},[]),C=F.useCallback(D=>{if(D.type==="raw"||D.type==="map")return;const P=Dr(D.type,D.id);f(S=>{if(S[P]){const N={...S};return delete N[P],N}return S}),h(S=>{if(S[P]){const N={...S};return delete N[P],N}return S}),D.type==="channel"?st.markChannelRead(D.id).catch(S=>{console.error("Failed to mark channel as read on server:",S)}):D.type==="contact"&&st.markContactRead(D.id).catch(S=>{console.error("Failed to mark contact as read on server:",S)})},[]),E=F.useCallback(D=>{let P=null;if(D.type==="CHAN"&&D.conversation_key?P=Dr("channel",D.conversation_key):D.type==="PRIV"&&D.conversation_key&&(P=Dr("contact",D.conversation_key)),P){const S=D.received_at||Math.floor(Date.now()/1e3),N=kf(P,S);p(N)}},[]);return{unreadCounts:d,mentions:m,lastMessageTimes:x,incrementUnread:k,markAllRead:T,markConversationRead:C,trackNewMessage:E}}var u0=SA();const TA=Qs(u0);function Gv(r){if(typeof document>"u")return;let a=document.head||document.getElem Line truncated
* @license lucide-react v0.562.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -539,4 +539,4 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
`),this.noSleepTimer=window.setInterval(()=>{document.hidden||(window.location.href=window.location.href.split("#")[0],window.setTimeout(window.stop,0))},15e3),this.enabled=!0,Promise.resolve()):this.noSleepVideo.play().then(h=>(this.enabled=!0,h)).catch(h=>{throw this.enabled=!1,h})}disable(){l()?(this._wakeLock&&this._wakeLock.release(),this._wakeLock=null):o()?this.noSleepTimer&&(console.warn(`
NoSleep now disabled for older iOS devices.
`),window.clearInterval(this.noSleepTimer),this.noSleepTimer=null):this.noSleepVideo.pause(),this.enabled=!1}}return Vf=d,Vf}var tE=eE();const nE=Qs(tE);function rE(r){if(r.length<4)return null;try{const o=parseInt(r.slice(0,2),16)&3;let l=2;if(o===0||o===3){if(r.length<l+8)return null;l+=8}if(r.length<l+2)return null;const d=parseInt(r.slice(l,l+2),16);l+=2;const f=d*2;return r.length<l+f?null:(l+=f,r.slice(l))}catch{return null}}function iE({packets:r,channels:a,onChannelCreate:o,onRunningChange:l,visible:d=!1}){const[f,m]=F.useState(!1),[h,x]=F.useState(6),[p,y]=F.useState(!1),[v,w]=F.useState(!0),[k,T]=F.useState(!1),[C,E]=F.useState(null),[D,P]=F.useState(new Map),[S,N]=F.useState([]),[M,z]=F.useState(!1),[j,H]=F.useState(null),[te,Z]=F.useState(null),[V,ne]=F.useState(0),X=F.useRef(null),ie=F.useRef(null),de=F.useRef(!1),ae=F.useRef(!1),pe=F.useRef(!1),G=F.useRef(new Map),J=F.useRef(!1),W=F.useRef(6),R=F.useRef(!0),U=F.useRef(!1),Q=F.useRef(new Set),$=F.useRef(new Set);F.useEffect(()=>{const Se=new XC;X.current=Se,H(Se.isGpuAvailable());const Ue=new nE;return ie.current=Ue,()=>{Se.destroy(),X.current=null,Ue.disable(),ie.current=null}},[]),F.useEffect(()=>{!d||M||$2(async()=>{const{ENGLISH_WORDLIST:Se}=await import("./wordlist-BtmChKSf.js");return{ENGLISH_WORDLIST:Se}},[]).then(({ENGLISH_WORDLIST:Se})=>{X.current&&(X.current.setWordlist(Se),z(!0))}).catch(Se=>{console.error("Failed to load wordlist:",Se),kt.error("Failed to load wordlist",{description:"Cracking will not be available"})})},[d,M]),F.useEffect(()=>{const Se=()=>{st.getUndecryptedPacketCount().then(({count:lt})=>Z(lt)).catch(()=>Z(null))};Se();const Ue=setInterval(Se,3e4);return()=>clearInterval(Ue)},[]);const he=F.useMemo(()=>new Set(a.map(Se=>Se.key.toUpperCase())),[a]),fe=r.filter(Se=>Se.payload_type==="GROUP_TEXT"&&!Se.decrypted);F.useEffect(()=>{let Se=0;P(Ue=>{const lt=new Map(Ue);let Dt=!1;for(const xt of fe)if(!lt.has(xt.id)){const it=rE(xt.data);if(it&&$.current.has(it)){Se++;continue}it&&$.current.add(it),lt.set(xt.id,{packet:xt,attempts:0,lastAttemptLength:0,status:"pending"}),Dt=!0}return Dt?(G.current=lt,lt):Ue}),Se>0&&ne(Ue=>Ue+Se)},[fe.length]),F.useEffect(()=>{G.current=D},[D]),F.useEffect(()=>{J.current=p},[p]),F.useEffect(()=>{W.current=h},[h]),F.useEffect(()=>{R.current=v},[v]),F.useEffect(()=>{U.current=k},[k]),F.useEffect(()=>{Q.current=new Set(fe.map(Se=>Se.id))},[fe]),F.useEffect(()=>{l==null||l(f)},[f,l]);const ke=Array.from(D.values()).filter(Se=>Se.status==="pending").length,ge=Array.from(D.values()).filter(Se=>Se.status==="cracked").length,Ze=Array.from(D.values()).filter(Se=>Se.status==="failed").length,$e=F.useCallback(async()=>{if(pe.current||!X.current||!de.current)return;const Se=G.current;let Ue=null,lt=null;for(const[Ke,Xe]of Se.entries())if(Xe.status==="pending"){Ue=Xe,lt=Ke;break}if(!Ue&&J.current){const Ke=Array.from(Se.entries()).filter(([,Xe])=>Xe.status==="failed"&&Xe.lastAttemptLength<10);Ke.length>0&&(Ke.sort((Xe,xe)=>Xe[1].lastAttemptLength-xe[1].lastAttemptLength),[lt,Ue]=Ke[0])}if(!Ue||lt===null){de.current&&setTimeout(()=>$e(),1e3);return}if(!Q.current.has(lt)){P(Ke=>{const Xe=new Map(Ke);return Xe.delete(lt),Xe}),de.current&&setTimeout(()=>$e(),10);return}pe.current=!0;const Dt=W.current,xt=Ue.lastAttemptLength>0,it=xt?Ue.lastAttemptLength+1:Dt;try{const Ke=await X.current.crack(Ue.packet.data,{maxLength:it,useSenderFilter:!0,useTimestampFilter:!0,useUtf8Filter:!0,...U.current&&{gpuDispatchMs:1e4},...xt&&{useDictionary:!1,startingLength:it}},Xe=>{E(Xe)});if(ae.current){ae.current=!1,pe.current=!1,E(null);return}if(Ke.found&&Ke.roomName&&Ke.key){P(Te=>{const pt=new Map(Te),Ut=pt.get(lt);return Ut&&pt.set(lt,{...Ut,status:"cracked",attempts:Ut.attempts+1,lastAttemptLength:it}),pt});const Xe={roomName:Ke.roomName,key:Ke.key,packetId:lt,message:Ke.decryptedMessage||"",crackedAt:Date.now()};N(Te=>[...Te,Xe]);const xe=Ke.key.toUpperCase();if(!he.has(xe))try{const Te="#"+Ke.roomName;await o(Te,Ke.key),R.current&&await st.decryptHistoricalPackets({key_type:"channel",channel_name:Te})}catch(Te){console.error("Failed to create channel or decrypt historical:",Te),kt.error("Failed to save cracked channel",{description:Te instanceof Error?Te.message:"Channel discovered but could not be saved"})}}else P(Xe=>{const xe=new Map(Xe),Te=xe.get(lt);return Te&&xe.set(lt,{...Te,status:"failed",attempts:Te.attempts+1,lastAttemptLength:it}),xe})}catch(Ke){console.error("Cracking error:",Ke),P(Xe=>{const xe=new Map(Xe),Te=xe.get(lt);return Te&&xe.set(lt,{...Te,status:"failed",attempts:Te.attempts+1,lastAttemptLength:it}),xe})}pe.current=!1,E(null),de.current&&setTimeout(()=>$e(),100)},[he,o]),wt=()=>{var Se;if(!j){kt.error("WebGPU not available",{description:"Cracking requires Chrome 113+ or Edge 113+ with WebGPU support."});return}m(!0),de.current=!0,ae.current=!1,(Se=ie.current)==null||Se.enable(),$e()},ft=()=>{var Se,Ue;m(!1),de.current=!1,ae.current=!0,(Se=X.current)==null||Se.abort(),(Ue=ie.current)==nu Line truncated
//# sourceMappingURL=index-5vg6E5ET.js.map
//# sourceMappingURL=index-DRuKVg0T.js.map
@@ -1 +1 @@
{"version":3,"mappings":"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 truncated
{"version":3,"mappings":"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 truncated
+1 -1
View File
@@ -13,7 +13,7 @@
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<script type="module" crossorigin src="/assets/index-5vg6E5ET.js"></script>
<script type="module" crossorigin src="/assets/index-DRuKVg0T.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D9NEpiho.css">
</head>
<body>
+1 -1
View File
@@ -100,7 +100,7 @@ function createLocalMessage(conversationKey: string, text: string, outgoing: boo
text,
sender_timestamp: now,
received_at: now,
path_len: null,
path: null,
txt_type: 0,
signature: null,
outgoing,
+1 -1
View File
@@ -99,7 +99,7 @@ function createLocalMessage(
text,
sender_timestamp: now,
received_at: now,
path_len: null,
path: null,
txt_type: 0,
signature: null,
outgoing,
+2 -2
View File
@@ -268,7 +268,7 @@ describe('Integration: ACK Events', () => {
text: 'Hello',
sender_timestamp: 1700000000,
received_at: 1700000000,
path_len: null,
path: null,
txt_type: 0,
signature: null,
outgoing: true,
@@ -301,7 +301,7 @@ describe('Integration: ACK Events', () => {
text: 'Hello',
sender_timestamp: 1700000000,
received_at: 1700000000,
path_len: null,
path: null,
txt_type: 0,
signature: null,
outgoing: true,
+1 -1
View File
@@ -72,7 +72,7 @@ describe('shouldIncrementUnread', () => {
text: 'Test',
sender_timestamp: null,
received_at: Date.now(),
path_len: null,
path: null,
txt_type: 0,
signature: null,
outgoing: false,
@@ -16,7 +16,7 @@ function createMessage(overrides: Partial<Message> = {}): Message {
text: 'Hello world',
sender_timestamp: 1700000000,
received_at: 1700000001,
path_len: null,
path: null,
txt_type: 0,
signature: null,
outgoing: false,
+2 -1
View File
@@ -82,7 +82,8 @@ export interface Message {
text: string;
sender_timestamp: number | null;
received_at: number;
path_len: number | null;
/** Hex-encoded routing path (2 chars per hop). Null for outgoing messages. */
path: string | null;
txt_type: number;
signature: string | null;
outgoing: boolean;
+75 -7
View File
@@ -78,13 +78,30 @@ class TestMigration001:
)
""")
await conn.execute("CREATE INDEX idx_raw_packets_decrypted ON raw_packets(decrypted)")
# Messages table with old schema (for migrations 6 and 7)
await conn.execute("""
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
conversation_key TEXT NOT NULL,
text TEXT NOT NULL,
sender_timestamp INTEGER,
received_at INTEGER NOT NULL,
path_len INTEGER,
txt_type INTEGER DEFAULT 0,
signature TEXT,
outgoing INTEGER DEFAULT 0,
acked INTEGER DEFAULT 0,
UNIQUE(type, conversation_key, text, sender_timestamp)
)
""")
await conn.commit()
# Run migrations
applied = await run_migrations(conn)
assert applied == 5 # All 5 migrations run
assert await get_version(conn) == 5
assert applied == 7 # All 7 migrations run
assert await get_version(conn) == 7
# Verify columns exist by inserting and selecting
await conn.execute(
@@ -143,15 +160,32 @@ class TestMigration001:
)
""")
await conn.execute("CREATE INDEX idx_raw_packets_decrypted ON raw_packets(decrypted)")
# Messages table with old schema (for migrations 6 and 7)
await conn.execute("""
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
conversation_key TEXT NOT NULL,
text TEXT NOT NULL,
sender_timestamp INTEGER,
received_at INTEGER NOT NULL,
path_len INTEGER,
txt_type INTEGER DEFAULT 0,
signature TEXT,
outgoing INTEGER DEFAULT 0,
acked INTEGER DEFAULT 0,
UNIQUE(type, conversation_key, text, sender_timestamp)
)
""")
await conn.commit()
# Run migrations twice
applied1 = await run_migrations(conn)
applied2 = await run_migrations(conn)
assert applied1 == 5 # All 5 migrations run
assert applied1 == 7 # All 7 migrations run
assert applied2 == 0 # No migrations on second run
assert await get_version(conn) == 5
assert await get_version(conn) == 7
finally:
await conn.close()
@@ -189,14 +223,31 @@ class TestMigration001:
)
""")
await conn.execute("CREATE INDEX idx_raw_packets_decrypted ON raw_packets(decrypted)")
# Messages table with old schema (for migrations 6 and 7)
await conn.execute("""
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
conversation_key TEXT NOT NULL,
text TEXT NOT NULL,
sender_timestamp INTEGER,
received_at INTEGER NOT NULL,
path_len INTEGER,
txt_type INTEGER DEFAULT 0,
signature TEXT,
outgoing INTEGER DEFAULT 0,
acked INTEGER DEFAULT 0,
UNIQUE(type, conversation_key, text, sender_timestamp)
)
""")
await conn.commit()
# Run migrations - should not fail
applied = await run_migrations(conn)
# All 5 migrations applied (version incremented) but no error
assert applied == 5
assert await get_version(conn) == 5
# All 7 migrations applied (version incremented) but no error
assert applied == 7
assert await get_version(conn) == 7
finally:
await conn.close()
@@ -234,6 +285,23 @@ class TestMigration001:
)
""")
await conn.execute("CREATE INDEX idx_raw_packets_decrypted ON raw_packets(decrypted)")
# Messages table with old schema (for migrations 6 and 7)
await conn.execute("""
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
conversation_key TEXT NOT NULL,
text TEXT NOT NULL,
sender_timestamp INTEGER,
received_at INTEGER NOT NULL,
path_len INTEGER,
txt_type INTEGER DEFAULT 0,
signature TEXT,
outgoing INTEGER DEFAULT 0,
acked INTEGER DEFAULT 0,
UNIQUE(type, conversation_key, text, sender_timestamp)
)
""")
await conn.execute(
"INSERT INTO contacts (public_key, name, type) VALUES (?, ?, ?)",
("existingkey", "ExistingContact", 1),
+6 -6
View File
@@ -430,7 +430,7 @@ class TestCreateMessageFromDecrypted:
assert broadcast["text"] == "TestSender: Hello world"
assert broadcast["sender_timestamp"] == 1700000000
assert broadcast["received_at"] == 1700000001
assert broadcast["path_len"] is None # Historical decryption has no path info
assert broadcast["path"] is None # Historical decryption has no path info
assert broadcast["outgoing"] is False
assert broadcast["acked"] == 0
@@ -529,8 +529,8 @@ class TestMessageBroadcastStructure:
"""Test that message broadcasts have the correct structure for frontend."""
@pytest.mark.asyncio
async def test_realtime_broadcast_includes_path_len(self, test_db, captured_broadcasts):
"""Real-time packet processing includes path_len in broadcast."""
async def test_realtime_broadcast_includes_path(self, test_db, captured_broadcasts):
"""Real-time packet processing includes path in broadcast."""
from app.packet_processor import process_raw_packet
fixture = FIXTURES["channel_message"]
@@ -550,9 +550,9 @@ class TestMessageBroadcastStructure:
assert len(message_broadcasts) == 1
broadcast = message_broadcasts[0]["data"]
# Real-time processing extracts path_len from packet (flood packets have path_len=0)
assert "path_len" in broadcast
# The test packet is a flood packet, so path_len should be 0 or None depending on packet structure
# Real-time processing extracts path from packet (flood packets have empty path)
assert "path" in broadcast
# The test packet is a flood packet, so path should be empty string ""
class TestRawPacketStorage: