mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-10-23 01:41:15 +00:00
Add historical DM decryption
This commit is contained in:
1 parent
30e3eb47be
commit
42572aa234
22 files changed
+1761
-53
No files matched your search
@@ -4,6 +4,20 @@
|
||||
|
||||
**NEVER make git commits.** A human must make all commits. You may stage files and prepare commit messages, but do not run `git commit`.
|
||||
|
||||
If instructed to "run all tests" or "get ready for a commit" or other summative, work ending directives, make sure you run the following and that they all pass green:
|
||||
|
||||
```bash
|
||||
uv run ruff check app/ tests/ --fix # check for python violations
|
||||
uv run ruff format app/ tests/ # format python
|
||||
uv run pyright app/ # type check python
|
||||
PYTHONPATH=. uv run pytest tests/ -v # test python
|
||||
|
||||
cd frontend/ # move to frontend directory
|
||||
npm run lint:fix # fix lint violations
|
||||
npm run format # format the code
|
||||
npm run build # run a frontend build
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
A web interface for MeshCore mesh radio networks. The backend connects to a MeshCore-compatible radio over serial and exposes REST/WebSocket APIs. The React frontend provides real-time messaging and radio configuration.
|
||||
@@ -204,6 +218,7 @@ All endpoints are prefixed with `/api` (e.g., `/api/health`).
|
||||
| POST | `/api/radio/reboot` | Reboot radio or reconnect if disconnected |
|
||||
| PUT | `/api/radio/private-key` | Import private key to radio |
|
||||
| GET | `/api/contacts` | List contacts |
|
||||
| POST | `/api/contacts` | Create contact (optionally trigger historical DM decrypt) |
|
||||
| POST | `/api/contacts/sync` | Pull from radio |
|
||||
| POST | `/api/contacts/{key}/telemetry` | Request telemetry from repeater |
|
||||
| POST | `/api/contacts/{key}/command` | Send CLI command to repeater |
|
||||
@@ -265,11 +280,11 @@ Read state (`last_read_at`) is tracked **server-side** for consistency across de
|
||||
|
||||
### Server-Side Decryption
|
||||
|
||||
The server can decrypt historical channel packets using stored channel keys.
|
||||
The server can decrypt packets using stored keys, both in real-time and for historical packets.
|
||||
|
||||
**Channel messages**: Decrypted automatically when a matching channel key is available.
|
||||
|
||||
**Direct messages**: Currently decrypted only by the MeshCore library on the radio itself. Server-side direct message decryption is not yet implemented.
|
||||
**Direct messages**: Decrypted server-side using the private key exported from the radio on startup. This enables DM decryption even when the contact isn't loaded on the radio. The private key is stored in memory only (see `keystore.py`).
|
||||
|
||||
## MeshCore Library
|
||||
|
||||
|
||||
+24
-5
@@ -291,12 +291,30 @@ if result:
|
||||
|
||||
### Direct Message Decryption
|
||||
|
||||
Direct messages use ECDH key exchange (Ed25519 → X25519). Server-side decryption
|
||||
of direct messages is **not yet implemented**. Currently, direct messages are
|
||||
decrypted by the MeshCore library on the radio itself.
|
||||
Direct messages use ECDH key exchange (Ed25519 → X25519) for shared secret derivation.
|
||||
|
||||
The decoder module contains a `try_decrypt_packet_with_contact_key()` function
|
||||
that could support this feature in the future.
|
||||
**Key storage**: The private key is exported from the radio on startup and stored in memory
|
||||
via `keystore.py`. This enables server-side DM decryption even when contacts aren't loaded
|
||||
on the radio.
|
||||
|
||||
**Real-time decryption**: When a `RAW_DATA` event contains a `TEXT_MESSAGE` packet, the
|
||||
`packet_processor.py` attempts to decrypt it using known contact public keys and the
|
||||
stored private key.
|
||||
|
||||
**Historical decryption**: When creating a contact with `try_historical=True`, the server
|
||||
attempts to decrypt all stored `TEXT_MESSAGE` packets for that contact.
|
||||
|
||||
**Direction detection**: The decoder uses the 1-byte dest_hash and src_hash to determine
|
||||
if a message is incoming or outgoing. Edge case: when both bytes match (1/256 chance),
|
||||
defaults to treating as incoming.
|
||||
|
||||
```python
|
||||
from app.decoder import try_decrypt_dm
|
||||
|
||||
result = try_decrypt_dm(raw_bytes, private_key, contact_public_key)
|
||||
if result:
|
||||
print(f"{result.message} (timestamp={result.timestamp})")
|
||||
```
|
||||
|
||||
## Advertisement Parsing (`decoder.py`)
|
||||
|
||||
@@ -407,6 +425,7 @@ All endpoints are prefixed with `/api`.
|
||||
### Contacts
|
||||
- `GET /api/contacts` - List from database
|
||||
- `GET /api/contacts/{key}` - Get by public key or prefix
|
||||
- `POST /api/contacts` - Create contact (optionally trigger historical DM decryption)
|
||||
- `POST /api/contacts/sync` - Pull from radio to database
|
||||
- `POST /api/contacts/{key}/add-to-radio` - Push to radio
|
||||
- `POST /api/contacts/{key}/remove-from-radio` - Remove from radio
|
||||
|
||||
+225
@@ -9,6 +9,7 @@ import logging
|
||||
from dataclasses import dataclass
|
||||
from enum import IntEnum
|
||||
|
||||
import nacl.bindings
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -48,6 +49,17 @@ class DecryptedGroupText:
|
||||
channel_hash: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class DecryptedDirectMessage:
|
||||
"""Result of decrypting a TEXT_MESSAGE (direct message)."""
|
||||
|
||||
timestamp: int
|
||||
flags: int
|
||||
message: str
|
||||
dest_hash: str # First byte of destination pubkey as hex
|
||||
src_hash: str # First byte of sender pubkey as hex
|
||||
|
||||
|
||||
@dataclass
|
||||
class ParsedAdvertisement:
|
||||
"""Result of parsing an advertisement packet."""
|
||||
@@ -394,3 +406,216 @@ def try_parse_advertisement(raw_packet: bytes) -> ParsedAdvertisement | None:
|
||||
return None
|
||||
|
||||
return parse_advertisement(packet_info.payload)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Direct Message (TEXT_MESSAGE) Decryption
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def _clamp_scalar(k: bytes) -> bytes:
|
||||
"""
|
||||
Clamp a 32-byte scalar for X25519.
|
||||
|
||||
This applies the standard X25519 clamping to ensure the scalar
|
||||
is in the correct form for elliptic curve operations.
|
||||
|
||||
Note: MeshCore private keys are already clamped (they store the post-SHA-512
|
||||
scalar directly rather than a seed). Clamping is idempotent, so this is safe.
|
||||
"""
|
||||
clamped = bytearray(k[:32])
|
||||
clamped[0] &= 248
|
||||
clamped[31] &= 63
|
||||
clamped[31] |= 64
|
||||
return bytes(clamped)
|
||||
|
||||
|
||||
def derive_public_key(private_key: bytes) -> bytes:
|
||||
"""
|
||||
Derive the Ed25519 public key from a MeshCore private key.
|
||||
|
||||
**MeshCore Key Format:**
|
||||
MeshCore stores a non-standard Ed25519 private key format:
|
||||
- First 32 bytes: The scalar (already post-SHA-512 and clamped)
|
||||
- Last 32 bytes: The signing prefix (used during signature generation)
|
||||
|
||||
Standard Ed25519 libraries expect a 32-byte seed and derive the scalar via
|
||||
SHA-512. Using `SigningKey(private_bytes)` will produce the WRONG public key.
|
||||
|
||||
To derive the correct public key, we use direct scalar × basepoint multiplication
|
||||
with the noclamp variant (since the scalar is already clamped).
|
||||
|
||||
Args:
|
||||
private_key: 64-byte MeshCore private key (or just the first 32 bytes)
|
||||
|
||||
Returns:
|
||||
32-byte Ed25519 public key
|
||||
"""
|
||||
scalar = private_key[:32]
|
||||
# Use noclamp because MeshCore stores already-clamped scalars
|
||||
return nacl.bindings.crypto_scalarmult_ed25519_base_noclamp(scalar)
|
||||
|
||||
|
||||
def derive_shared_secret(our_private_key: bytes, their_public_key: bytes) -> bytes:
|
||||
"""
|
||||
Derive ECDH shared secret from Ed25519 keys.
|
||||
|
||||
MeshCore uses Ed25519 keys, but ECDH requires X25519. This function:
|
||||
1. Clamps our private key scalar for X25519 (idempotent since already clamped)
|
||||
2. Converts their Ed25519 public key to X25519
|
||||
3. Performs X25519 scalar multiplication to get the shared secret
|
||||
|
||||
**MeshCore Key Format:**
|
||||
MeshCore private keys store the scalar directly (not a seed), so the first
|
||||
32 bytes are already the post-SHA-512 clamped scalar. See `derive_public_key`
|
||||
for details.
|
||||
|
||||
Args:
|
||||
our_private_key: 64-byte MeshCore private key (only first 32 bytes used)
|
||||
their_public_key: Their 32-byte Ed25519 public key
|
||||
|
||||
Returns:
|
||||
32-byte shared secret
|
||||
"""
|
||||
# Clamp the first 32 bytes of our private key (idempotent for MeshCore keys)
|
||||
clamped = _clamp_scalar(our_private_key[:32])
|
||||
|
||||
# Convert their Ed25519 public key to X25519
|
||||
x25519_pub = nacl.bindings.crypto_sign_ed25519_pk_to_curve25519(their_public_key)
|
||||
|
||||
# Perform X25519 ECDH
|
||||
return nacl.bindings.crypto_scalarmult(clamped, x25519_pub)
|
||||
|
||||
|
||||
def decrypt_direct_message(payload: bytes, shared_secret: bytes) -> DecryptedDirectMessage | None:
|
||||
"""
|
||||
Decrypt a TEXT_MESSAGE payload using the ECDH shared secret.
|
||||
|
||||
TEXT_MESSAGE payload structure:
|
||||
- dest_hash (1 byte): First byte of destination public key
|
||||
- src_hash (1 byte): First byte of sender public key
|
||||
- mac (2 bytes): First 2 bytes of HMAC-SHA256(shared_secret, ciphertext)
|
||||
- ciphertext (rest): AES-128-ECB encrypted content
|
||||
|
||||
Decrypted content structure:
|
||||
- timestamp (4 bytes, little-endian)
|
||||
- flags (1 byte)
|
||||
- message text (null-padded)
|
||||
|
||||
Args:
|
||||
payload: The TEXT_MESSAGE payload bytes
|
||||
shared_secret: 32-byte ECDH shared secret
|
||||
|
||||
Returns:
|
||||
DecryptedDirectMessage if successful, None otherwise
|
||||
"""
|
||||
if len(payload) < 4:
|
||||
return None
|
||||
|
||||
dest_hash = format(payload[0], "02x")
|
||||
src_hash = format(payload[1], "02x")
|
||||
mac = payload[2:4]
|
||||
ciphertext = payload[4:]
|
||||
|
||||
if len(ciphertext) == 0 or len(ciphertext) % 16 != 0:
|
||||
# AES requires 16-byte blocks
|
||||
return None
|
||||
|
||||
# Verify MAC: HMAC-SHA256(shared_secret, ciphertext)[:2]
|
||||
calculated_mac = hmac.new(shared_secret, ciphertext, hashlib.sha256).digest()[:2]
|
||||
if calculated_mac != mac:
|
||||
return None
|
||||
|
||||
# Decrypt using AES-128-ECB with shared_secret[:16]
|
||||
try:
|
||||
cipher = AES.new(shared_secret[:16], AES.MODE_ECB)
|
||||
decrypted = cipher.decrypt(ciphertext)
|
||||
except Exception as e:
|
||||
logger.debug("AES decryption failed for DM: %s", e)
|
||||
return None
|
||||
|
||||
if len(decrypted) < 5:
|
||||
return None
|
||||
|
||||
# Parse decrypted content
|
||||
timestamp = int.from_bytes(decrypted[0:4], "little")
|
||||
flags = decrypted[4]
|
||||
|
||||
# Extract message text (UTF-8, null-padded)
|
||||
message_bytes = decrypted[5:]
|
||||
try:
|
||||
message_text = message_bytes.decode("utf-8")
|
||||
# Remove null terminator and any padding
|
||||
message_text = message_text.rstrip("\x00")
|
||||
except UnicodeDecodeError:
|
||||
return None
|
||||
|
||||
return DecryptedDirectMessage(
|
||||
timestamp=timestamp,
|
||||
flags=flags,
|
||||
message=message_text,
|
||||
dest_hash=dest_hash,
|
||||
src_hash=src_hash,
|
||||
)
|
||||
|
||||
|
||||
def try_decrypt_dm(
|
||||
raw_packet: bytes,
|
||||
our_private_key: bytes,
|
||||
their_public_key: bytes,
|
||||
our_public_key: bytes | None = None,
|
||||
) -> DecryptedDirectMessage | None:
|
||||
"""
|
||||
Try to decrypt a raw packet as a direct message.
|
||||
|
||||
This performs several checks before attempting expensive ECDH:
|
||||
1. Packet must be TEXT_MESSAGE type
|
||||
2. dest_hash must match first byte of our public key (or their key for outbound)
|
||||
3. src_hash must match first byte of their public key (or our key for outbound)
|
||||
|
||||
Args:
|
||||
raw_packet: The complete raw packet bytes
|
||||
our_private_key: Our 64-byte Ed25519 private key
|
||||
their_public_key: Their 32-byte Ed25519 public key
|
||||
our_public_key: Our 32-byte Ed25519 public key (optional, for bidirectional check)
|
||||
|
||||
Returns:
|
||||
DecryptedDirectMessage if successful, None otherwise
|
||||
"""
|
||||
packet_info = parse_packet(raw_packet)
|
||||
if packet_info is None:
|
||||
return None
|
||||
|
||||
# Only TEXT_MESSAGE packets can be decrypted as DMs
|
||||
if packet_info.payload_type != PayloadType.TEXT_MESSAGE:
|
||||
return None
|
||||
|
||||
if len(packet_info.payload) < 4:
|
||||
return None
|
||||
|
||||
# Extract dest/src hashes from payload
|
||||
dest_hash = packet_info.payload[0]
|
||||
src_hash = packet_info.payload[1]
|
||||
|
||||
# Check if this packet is for us (inbound: them -> us)
|
||||
their_first_byte = their_public_key[0]
|
||||
is_inbound = src_hash == their_first_byte
|
||||
|
||||
# Check if this packet is from us (outbound: us -> them)
|
||||
is_outbound = False
|
||||
if our_public_key is not None:
|
||||
our_first_byte = our_public_key[0]
|
||||
is_outbound = src_hash == our_first_byte and dest_hash == their_first_byte
|
||||
|
||||
if not is_inbound and not is_outbound:
|
||||
# Packet doesn't match this contact conversation
|
||||
return None
|
||||
|
||||
# Derive shared secret and attempt decryption
|
||||
try:
|
||||
shared_secret = derive_shared_secret(our_private_key, their_public_key)
|
||||
except Exception as e:
|
||||
logger.debug("Failed to derive shared secret: %s", e)
|
||||
return None
|
||||
|
||||
return decrypt_direct_message(packet_info.payload, shared_secret)
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
Ephemeral keystore for storing sensitive keys in memory.
|
||||
|
||||
The private key is stored in memory only and is never persisted to disk.
|
||||
It's exported from the radio on startup and reconnect, then used for
|
||||
server-side decryption of direct messages.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from meshcore import EventType
|
||||
|
||||
from app.decoder import derive_public_key
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from meshcore import MeshCore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# In-memory storage for the private key and derived public key
|
||||
_private_key: bytes | None = None
|
||||
_public_key: bytes | None = None
|
||||
|
||||
|
||||
def set_private_key(key: bytes) -> None:
|
||||
"""Store the private key in memory and derive the public key.
|
||||
|
||||
Args:
|
||||
key: 64-byte Ed25519 private key in MeshCore format
|
||||
"""
|
||||
global _private_key, _public_key
|
||||
if len(key) != 64:
|
||||
raise ValueError(f"Private key must be 64 bytes, got {len(key)}")
|
||||
_private_key = key
|
||||
_public_key = derive_public_key(key)
|
||||
logger.info("Private key stored in keystore (public key: %s...)", _public_key.hex()[:12])
|
||||
|
||||
|
||||
def get_private_key() -> bytes | None:
|
||||
"""Get the stored private key.
|
||||
|
||||
Returns:
|
||||
The 64-byte private key, or None if not set
|
||||
"""
|
||||
return _private_key
|
||||
|
||||
|
||||
def get_public_key() -> bytes | None:
|
||||
"""Get the derived public key.
|
||||
|
||||
Returns:
|
||||
The 32-byte public key derived from the private key, or None if not set
|
||||
"""
|
||||
return _public_key
|
||||
|
||||
|
||||
def has_private_key() -> bool:
|
||||
"""Check if a private key is stored.
|
||||
|
||||
Returns:
|
||||
True if a private key is available
|
||||
"""
|
||||
return _private_key is not None
|
||||
|
||||
|
||||
def clear_private_key() -> None:
|
||||
"""Clear the stored private key from memory."""
|
||||
global _private_key, _public_key
|
||||
_private_key = None
|
||||
_public_key = None
|
||||
logger.info("Private key cleared from keystore")
|
||||
|
||||
|
||||
async def export_and_store_private_key(mc: "MeshCore") -> bool:
|
||||
"""Export private key from the radio and store it in the keystore.
|
||||
|
||||
This should be called on startup and after each reconnect.
|
||||
|
||||
Args:
|
||||
mc: Connected MeshCore instance
|
||||
|
||||
Returns:
|
||||
True if the private key was successfully exported and stored
|
||||
"""
|
||||
logger.info("Exporting private key from radio...")
|
||||
try:
|
||||
result = await mc.commands.export_private_key()
|
||||
|
||||
if result.type == EventType.PRIVATE_KEY:
|
||||
private_key_bytes = result.payload["private_key"]
|
||||
set_private_key(private_key_bytes)
|
||||
return True
|
||||
elif result.type == EventType.DISABLED:
|
||||
logger.warning(
|
||||
"Private key export disabled on radio firmware. "
|
||||
"Server-side DM decryption will not be available. "
|
||||
"Enable ENABLE_PRIVATE_KEY_EXPORT=1 in firmware to enable this feature."
|
||||
)
|
||||
return False
|
||||
else:
|
||||
logger.error("Failed to export private key: %s", result.payload)
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error("Error exporting private key: %s", e)
|
||||
return False
|
||||
@@ -48,6 +48,11 @@ async def lifespan(app: FastAPI):
|
||||
if radio_manager.meshcore:
|
||||
register_event_handlers(radio_manager.meshcore)
|
||||
|
||||
# Export and store private key for server-side DM decryption
|
||||
from app.keystore import export_and_store_private_key
|
||||
|
||||
await export_and_store_private_key(radio_manager.meshcore)
|
||||
|
||||
# Sync radio clock with system time
|
||||
await sync_radio_time()
|
||||
|
||||
|
||||
@@ -55,6 +55,17 @@ class Contact(BaseModel):
|
||||
}
|
||||
|
||||
|
||||
class CreateContactRequest(BaseModel):
|
||||
"""Request to create a new contact."""
|
||||
|
||||
public_key: str = Field(min_length=64, max_length=64, description="Public key (64-char hex)")
|
||||
name: str | None = Field(default=None, description="Display name for the contact")
|
||||
try_historical: bool = Field(
|
||||
default=False,
|
||||
description="Attempt to decrypt historical DM packets for this contact",
|
||||
)
|
||||
|
||||
|
||||
# Contact type constants
|
||||
CONTACT_TYPE_REPEATER = 2
|
||||
|
||||
|
||||
+255
-5
@@ -17,12 +17,15 @@ import logging
|
||||
import time
|
||||
|
||||
from app.decoder import (
|
||||
DecryptedDirectMessage,
|
||||
PacketInfo,
|
||||
PayloadType,
|
||||
parse_advertisement,
|
||||
parse_packet,
|
||||
try_decrypt_dm,
|
||||
try_decrypt_packet_with_channel_key,
|
||||
)
|
||||
from app.keystore import get_private_key, get_public_key, has_private_key
|
||||
from app.models import CONTACT_TYPE_REPEATER, RawPacketBroadcast, RawPacketDecryptedInfo
|
||||
from app.repository import (
|
||||
ChannelRepository,
|
||||
@@ -161,6 +164,133 @@ async def create_message_from_decrypted(
|
||||
return msg_id
|
||||
|
||||
|
||||
async def create_dm_message_from_decrypted(
|
||||
packet_id: int,
|
||||
decrypted: DecryptedDirectMessage,
|
||||
their_public_key: str,
|
||||
our_public_key: str | None,
|
||||
received_at: int | None = None,
|
||||
path: str | None = None,
|
||||
outgoing: bool = False,
|
||||
) -> int | None:
|
||||
"""Create a message record from decrypted direct message packet content.
|
||||
|
||||
This is the shared logic for storing decrypted direct messages,
|
||||
used by both real-time packet processing and historical decryption.
|
||||
|
||||
Args:
|
||||
packet_id: ID of the raw packet being processed
|
||||
decrypted: DecryptedDirectMessage from decoder
|
||||
their_public_key: The contact's full 64-char public key (conversation_key)
|
||||
our_public_key: Our public key (to determine direction), or None
|
||||
received_at: When the packet was received (defaults to now)
|
||||
path: Hex-encoded routing path (None for historical decryption)
|
||||
outgoing: Whether this is an outgoing message (we sent it)
|
||||
|
||||
Returns the message ID if created, None if duplicate.
|
||||
"""
|
||||
received = received_at or int(time.time())
|
||||
|
||||
# conversation_key is always the other party's public key
|
||||
conversation_key = their_public_key.lower()
|
||||
|
||||
# Try to create message - INSERT OR IGNORE handles duplicates atomically
|
||||
msg_id = await MessageRepository.create(
|
||||
msg_type="PRIV",
|
||||
text=decrypted.message,
|
||||
conversation_key=conversation_key,
|
||||
sender_timestamp=decrypted.timestamp,
|
||||
received_at=received,
|
||||
path=path,
|
||||
outgoing=outgoing,
|
||||
)
|
||||
|
||||
if msg_id is None:
|
||||
# Duplicate message detected
|
||||
existing_msg = await MessageRepository.get_by_content(
|
||||
msg_type="PRIV",
|
||||
conversation_key=conversation_key,
|
||||
text=decrypted.message,
|
||||
sender_timestamp=decrypted.timestamp,
|
||||
)
|
||||
if not existing_msg:
|
||||
logger.warning(
|
||||
"Duplicate DM for contact %s but couldn't find existing",
|
||||
conversation_key[:12],
|
||||
)
|
||||
return None
|
||||
|
||||
logger.debug(
|
||||
"Duplicate DM for contact %s (msg_id=%d, outgoing=%s) - adding path",
|
||||
conversation_key[:12],
|
||||
existing_msg.id,
|
||||
existing_msg.outgoing,
|
||||
)
|
||||
|
||||
# Add path if provided
|
||||
if path is not None:
|
||||
paths = await MessageRepository.add_path(existing_msg.id, path, received)
|
||||
else:
|
||||
paths = existing_msg.paths or []
|
||||
|
||||
# Increment ack count for outgoing messages (echo confirmation)
|
||||
if existing_msg.outgoing:
|
||||
ack_count = await MessageRepository.increment_ack_count(existing_msg.id)
|
||||
else:
|
||||
ack_count = await MessageRepository.get_ack_count(existing_msg.id)
|
||||
|
||||
# Broadcast updated paths
|
||||
broadcast_event(
|
||||
"message_acked",
|
||||
{
|
||||
"message_id": existing_msg.id,
|
||||
"ack_count": ack_count,
|
||||
"paths": [p.model_dump() for p in paths] if paths else [],
|
||||
},
|
||||
)
|
||||
|
||||
# Mark this packet as decrypted
|
||||
await RawPacketRepository.mark_decrypted(packet_id, existing_msg.id)
|
||||
|
||||
return None
|
||||
|
||||
logger.info(
|
||||
"Stored direct message %d for contact %s (outgoing=%s)",
|
||||
msg_id,
|
||||
conversation_key[:12],
|
||||
outgoing,
|
||||
)
|
||||
|
||||
# Mark the raw packet as decrypted
|
||||
await RawPacketRepository.mark_decrypted(packet_id, msg_id)
|
||||
|
||||
# Build paths array for broadcast
|
||||
paths = [{"path": path or "", "received_at": received}] if path is not None else None
|
||||
|
||||
# Broadcast new message to connected clients
|
||||
broadcast_event(
|
||||
"message",
|
||||
{
|
||||
"id": msg_id,
|
||||
"type": "PRIV",
|
||||
"conversation_key": conversation_key,
|
||||
"text": decrypted.message,
|
||||
"sender_timestamp": decrypted.timestamp,
|
||||
"received_at": received,
|
||||
"paths": paths,
|
||||
"txt_type": 0,
|
||||
"signature": None,
|
||||
"outgoing": outgoing,
|
||||
"acked": 0,
|
||||
},
|
||||
)
|
||||
|
||||
# Update contact's last_contacted timestamp (for sorting)
|
||||
await ContactRepository.update_last_contacted(conversation_key, received)
|
||||
|
||||
return msg_id
|
||||
|
||||
|
||||
async def process_raw_packet(
|
||||
raw_bytes: bytes,
|
||||
timestamp: int | None = None,
|
||||
@@ -223,11 +353,11 @@ async def process_raw_packet(
|
||||
# Only process new advertisements (duplicates don't add value)
|
||||
await _process_advertisement(raw_bytes, ts, packet_info)
|
||||
|
||||
# TODO: Add TEXT_MESSAGE (direct message) decryption when private key is available
|
||||
# elif payload_type == PayloadType.TEXT_MESSAGE:
|
||||
# decrypt_result = await _process_direct_message(raw_bytes, packet_id, ts, packet_info)
|
||||
# if decrypt_result:
|
||||
# result.update(decrypt_result)
|
||||
elif payload_type == PayloadType.TEXT_MESSAGE:
|
||||
# Try to decrypt direct messages using stored private key and known contacts
|
||||
decrypt_result = await _process_direct_message(raw_bytes, packet_id, ts, packet_info)
|
||||
if decrypt_result:
|
||||
result.update(decrypt_result)
|
||||
|
||||
# Always broadcast raw packet for the packet feed UI (even duplicates)
|
||||
# This enables the frontend cracker to see all incoming packets in real-time
|
||||
@@ -416,3 +546,123 @@ async def _process_advertisement(
|
||||
from app.radio_sync import sync_recent_contacts_to_radio
|
||||
|
||||
asyncio.create_task(sync_recent_contacts_to_radio())
|
||||
|
||||
|
||||
async def _process_direct_message(
|
||||
raw_bytes: bytes,
|
||||
packet_id: int,
|
||||
timestamp: int,
|
||||
packet_info: PacketInfo | None,
|
||||
) -> dict | None:
|
||||
"""
|
||||
Process a TEXT_MESSAGE (direct message) packet.
|
||||
|
||||
Uses the stored private key and tries to decrypt with known contacts.
|
||||
The src_hash (first byte of sender's public key) is used to narrow down
|
||||
candidate contacts for decryption.
|
||||
"""
|
||||
if not has_private_key():
|
||||
# No private key available - can't decrypt DMs
|
||||
return None
|
||||
|
||||
private_key = get_private_key()
|
||||
our_public_key = get_public_key()
|
||||
if private_key is None or our_public_key is None:
|
||||
return None
|
||||
|
||||
# Parse packet to get the payload for src_hash extraction
|
||||
if packet_info is None:
|
||||
packet_info = parse_packet(raw_bytes)
|
||||
if packet_info is None or packet_info.payload is None:
|
||||
return None
|
||||
|
||||
# Extract src_hash from payload (second byte: [dest_hash:1][src_hash:1][MAC:2][ciphertext])
|
||||
if len(packet_info.payload) < 4:
|
||||
return None
|
||||
|
||||
dest_hash = format(packet_info.payload[0], "02x").lower()
|
||||
src_hash = format(packet_info.payload[1], "02x").lower()
|
||||
|
||||
# Check if this message involves us (either as sender or recipient)
|
||||
our_first_byte = format(our_public_key[0], "02x").lower()
|
||||
|
||||
# Determine direction based on which hash matches us:
|
||||
# - dest_hash == us AND src_hash != us -> incoming (addressed to us from someone else)
|
||||
# - src_hash == us AND dest_hash != us -> outgoing (we sent to someone else)
|
||||
# - Both match us -> ambiguous (our first byte matches contact's), default to incoming
|
||||
# - Neither matches us -> not our message
|
||||
if dest_hash == our_first_byte and src_hash != our_first_byte:
|
||||
is_outgoing = False # Definitely incoming
|
||||
elif src_hash == our_first_byte and dest_hash != our_first_byte:
|
||||
is_outgoing = True # Definitely outgoing
|
||||
elif dest_hash == our_first_byte and src_hash == our_first_byte:
|
||||
# Ambiguous: our first byte matches contact's first byte (1/256 chance)
|
||||
# Default to incoming since dest_hash matching us is more indicative
|
||||
is_outgoing = False
|
||||
logger.debug("Ambiguous DM direction (first bytes match), defaulting to incoming")
|
||||
else:
|
||||
# Neither hash matches us - not our message
|
||||
return None
|
||||
|
||||
# Find candidate contacts based on the relevant hash
|
||||
# For incoming: match src_hash (sender's first byte)
|
||||
# For outgoing: match dest_hash (recipient's first byte)
|
||||
match_hash = dest_hash if is_outgoing else src_hash
|
||||
|
||||
# Get all contacts and filter by first byte of public key
|
||||
contacts = await ContactRepository.get_all(limit=1000)
|
||||
candidate_contacts = [c for c in contacts if c.public_key.lower().startswith(match_hash)]
|
||||
|
||||
if not candidate_contacts:
|
||||
logger.debug(
|
||||
"No contacts found matching hash %s for DM decryption",
|
||||
match_hash,
|
||||
)
|
||||
return None
|
||||
|
||||
# Try decrypting with each candidate contact
|
||||
for contact in candidate_contacts:
|
||||
try:
|
||||
contact_public_key = bytes.fromhex(contact.public_key)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# For incoming messages, pass our_public_key to enable the dest_hash filter
|
||||
# For outgoing messages, skip the filter (dest_hash is the recipient, not us)
|
||||
result = try_decrypt_dm(
|
||||
raw_bytes,
|
||||
private_key,
|
||||
contact_public_key,
|
||||
our_public_key=our_public_key if not is_outgoing else None,
|
||||
)
|
||||
|
||||
if result is not None:
|
||||
# Successfully decrypted!
|
||||
logger.debug(
|
||||
"Decrypted DM %s contact %s: %s",
|
||||
"to" if is_outgoing else "from",
|
||||
contact.name or contact.public_key[:12],
|
||||
result.message[:50] if result.message else "",
|
||||
)
|
||||
|
||||
# Create message (or add path to existing if duplicate)
|
||||
msg_id = await create_dm_message_from_decrypted(
|
||||
packet_id=packet_id,
|
||||
decrypted=result,
|
||||
their_public_key=contact.public_key,
|
||||
our_public_key=our_public_key.hex(),
|
||||
received_at=timestamp,
|
||||
path=packet_info.path.hex() if packet_info.path else None,
|
||||
outgoing=is_outgoing,
|
||||
)
|
||||
|
||||
return {
|
||||
"decrypted": True,
|
||||
"contact_name": contact.name,
|
||||
"sender": contact.name or contact.public_key[:12],
|
||||
"message_id": msg_id,
|
||||
}
|
||||
|
||||
# Couldn't decrypt with any known contact
|
||||
logger.debug("Could not decrypt DM with any of %d candidate contacts", len(candidate_contacts))
|
||||
return None
|
||||
@@ -231,9 +231,11 @@ class RadioManager:
|
||||
if await self.reconnect():
|
||||
# Re-register event handlers after successful reconnect
|
||||
from app.event_handlers import register_event_handlers
|
||||
from app.keystore import export_and_store_private_key
|
||||
|
||||
if self._meshcore:
|
||||
register_event_handlers(self._meshcore)
|
||||
await export_and_store_private_key(self._meshcore)
|
||||
await self._meshcore.start_auto_message_fetching()
|
||||
logger.info("Event handlers re-registered after auto-reconnect")
|
||||
|
||||
|
||||
+23
-1
@@ -6,7 +6,7 @@ from hashlib import sha256
|
||||
from typing import Any
|
||||
|
||||
from app.database import db
|
||||
from app.decoder import extract_payload
|
||||
from app.decoder import PayloadType, extract_payload, get_packet_payload_type
|
||||
from app.models import Channel, Contact, Message, MessagePath, RawPacket
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -681,3 +681,25 @@ class RawPacketRepository:
|
||||
)
|
||||
await db.conn.commit()
|
||||
return cursor.rowcount
|
||||
|
||||
@staticmethod
|
||||
async def get_undecrypted_text_messages() -> list[tuple[int, bytes, int]]:
|
||||
"""Get all undecrypted TEXT_MESSAGE packets as (id, data, timestamp) tuples.
|
||||
|
||||
Filters raw packets to only include those with PayloadType.TEXT_MESSAGE (0x02).
|
||||
These are direct messages that can be decrypted with contact ECDH keys.
|
||||
"""
|
||||
cursor = await db.conn.execute(
|
||||
"SELECT id, data, timestamp FROM raw_packets WHERE message_id IS NULL ORDER BY timestamp ASC"
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
|
||||
# Filter for TEXT_MESSAGE packets
|
||||
result = []
|
||||
for row in rows:
|
||||
data = bytes(row["data"])
|
||||
payload_type = get_packet_payload_type(data)
|
||||
if payload_type == PayloadType.TEXT_MESSAGE:
|
||||
result.append((row["id"], data, row["timestamp"]))
|
||||
|
||||
return result
|
||||
+108
-1
@@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi import APIRouter, BackgroundTasks, HTTPException, Query
|
||||
from meshcore import EventType
|
||||
|
||||
from app.dependencies import require_connected
|
||||
@@ -11,6 +11,7 @@ from app.models import (
|
||||
CommandRequest,
|
||||
CommandResponse,
|
||||
Contact,
|
||||
CreateContactRequest,
|
||||
NeighborInfo,
|
||||
TelemetryRequest,
|
||||
TelemetryResponse,
|
||||
@@ -18,6 +19,7 @@ from app.models import (
|
||||
from app.radio import radio_manager
|
||||
from app.radio_sync import pause_polling
|
||||
from app.repository import ContactRepository
|
||||
from app.routers.packets import _run_historical_dm_decryption
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -70,6 +72,111 @@ async def list_contacts(
|
||||
return await ContactRepository.get_all(limit=limit, offset=offset)
|
||||
|
||||
|
||||
@router.post("", response_model=Contact)
|
||||
async def create_contact(
|
||||
request: CreateContactRequest, background_tasks: BackgroundTasks
|
||||
) -> Contact:
|
||||
"""Create a new contact in the database.
|
||||
|
||||
If the contact already exists, updates the name (if provided).
|
||||
If try_historical is True, attempts to decrypt historical DM packets.
|
||||
"""
|
||||
# Validate hex format
|
||||
try:
|
||||
contact_public_key_bytes = bytes.fromhex(request.public_key)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail="Invalid public key: must be valid hex") from e
|
||||
|
||||
# Check if contact already exists
|
||||
existing = await ContactRepository.get_by_key_or_prefix(request.public_key)
|
||||
if existing:
|
||||
# Update name if provided
|
||||
if request.name:
|
||||
await ContactRepository.upsert(
|
||||
{
|
||||
"public_key": existing.public_key,
|
||||
"name": request.name,
|
||||
"type": existing.type,
|
||||
"flags": existing.flags,
|
||||
"last_path": existing.last_path,
|
||||
"last_path_len": existing.last_path_len,
|
||||
"last_advert": existing.last_advert,
|
||||
"lat": existing.lat,
|
||||
"lon": existing.lon,
|
||||
"last_seen": existing.last_seen,
|
||||
"on_radio": existing.on_radio,
|
||||
"last_contacted": existing.last_contacted,
|
||||
}
|
||||
)
|
||||
existing.name = request.name
|
||||
|
||||
# Trigger historical decryption if requested (even for existing contacts)
|
||||
if request.try_historical:
|
||||
await _start_historical_dm_decryption(
|
||||
background_tasks, contact_public_key_bytes, request.public_key
|
||||
)
|
||||
|
||||
return existing
|
||||
|
||||
# Create new contact
|
||||
contact_data = {
|
||||
"public_key": request.public_key,
|
||||
"name": request.name,
|
||||
"type": 0, # Unknown
|
||||
"flags": 0,
|
||||
"last_path": None,
|
||||
"last_path_len": -1,
|
||||
"last_advert": None,
|
||||
"lat": None,
|
||||
"lon": None,
|
||||
"last_seen": None,
|
||||
"on_radio": False,
|
||||
"last_contacted": None,
|
||||
}
|
||||
await ContactRepository.upsert(contact_data)
|
||||
logger.info("Created contact %s", request.public_key[:12])
|
||||
|
||||
# Trigger historical decryption if requested
|
||||
if request.try_historical:
|
||||
await _start_historical_dm_decryption(
|
||||
background_tasks, contact_public_key_bytes, request.public_key
|
||||
)
|
||||
|
||||
return Contact(**contact_data)
|
||||
|
||||
|
||||
async def _start_historical_dm_decryption(
|
||||
background_tasks: BackgroundTasks,
|
||||
contact_public_key_bytes: bytes,
|
||||
contact_public_key_hex: str,
|
||||
) -> None:
|
||||
"""Start historical DM decryption using the stored private key."""
|
||||
from app.keystore import get_private_key, has_private_key
|
||||
from app.websocket import broadcast_error
|
||||
|
||||
if not has_private_key():
|
||||
logger.warning(
|
||||
"Cannot start historical DM decryption: private key not available. "
|
||||
"Ensure radio firmware has ENABLE_PRIVATE_KEY_EXPORT=1."
|
||||
)
|
||||
broadcast_error(
|
||||
"Cannot decrypt historical DMs",
|
||||
"Private key not available. Radio firmware may need ENABLE_PRIVATE_KEY_EXPORT=1.",
|
||||
)
|
||||
return
|
||||
|
||||
private_key_bytes = get_private_key()
|
||||
assert private_key_bytes is not None # Guaranteed by has_private_key check
|
||||
|
||||
logger.info("Starting historical DM decryption for contact %s", contact_public_key_hex[:12])
|
||||
background_tasks.add_task(
|
||||
_run_historical_dm_decryption,
|
||||
private_key_bytes,
|
||||
contact_public_key_bytes,
|
||||
contact_public_key_hex.lower(),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{public_key}", response_model=Contact)
|
||||
async def get_contact(public_key: str) -> Contact:
|
||||
"""Get a specific contact by public key or prefix."""
|
||||
|
||||
+165
-4
@@ -5,8 +5,13 @@ from fastapi import APIRouter, BackgroundTasks
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.database import db
|
||||
from app.decoder import parse_packet, try_decrypt_packet_with_channel_key
|
||||
from app.packet_processor import create_message_from_decrypted
|
||||
from app.decoder import (
|
||||
derive_public_key,
|
||||
parse_packet,
|
||||
try_decrypt_dm,
|
||||
try_decrypt_packet_with_channel_key,
|
||||
)
|
||||
from app.packet_processor import create_dm_message_from_decrypted, create_message_from_decrypted
|
||||
from app.repository import RawPacketRepository
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -21,6 +26,14 @@ class DecryptRequest(BaseModel):
|
||||
channel_name: str | None = Field(
|
||||
default=None, description="Channel name (for hashtag channels, key derived from name)"
|
||||
)
|
||||
# Fields for contact (DM) decryption
|
||||
private_key: str | None = Field(
|
||||
default=None,
|
||||
description="Our private key as hex (64 bytes = 128 chars, Ed25519 seed + pubkey)",
|
||||
)
|
||||
contact_public_key: str | None = Field(
|
||||
default=None, description="Contact's public key as hex (32 bytes = 64 chars)"
|
||||
)
|
||||
|
||||
|
||||
class DecryptResult(BaseModel):
|
||||
@@ -94,6 +107,84 @@ async def _run_historical_decryption(channel_key_bytes: bytes, channel_key_hex:
|
||||
logger.info("Historical decryption complete: %d/%d packets decrypted", decrypted_count, total)
|
||||
|
||||
|
||||
async def _run_historical_dm_decryption(
|
||||
private_key_bytes: bytes,
|
||||
contact_public_key_bytes: bytes,
|
||||
contact_public_key_hex: str,
|
||||
) -> None:
|
||||
"""Background task to decrypt historical DM packets with contact's key."""
|
||||
global _decrypt_progress
|
||||
|
||||
# Get only TEXT_MESSAGE packets (undecrypted)
|
||||
packets = await RawPacketRepository.get_undecrypted_text_messages()
|
||||
total = len(packets)
|
||||
processed = 0
|
||||
decrypted_count = 0
|
||||
|
||||
_decrypt_progress = DecryptProgress(total=total, processed=0, decrypted=0, in_progress=True)
|
||||
|
||||
logger.info("Starting historical DM decryption of %d TEXT_MESSAGE packets", total)
|
||||
|
||||
# Derive our public key from the private key using Ed25519 scalar multiplication.
|
||||
# Note: MeshCore stores the scalar directly (not a seed), so we use noclamp variant.
|
||||
# See derive_public_key() for details on the MeshCore key format.
|
||||
our_public_key_bytes = derive_public_key(private_key_bytes)
|
||||
|
||||
for packet_id, packet_data, packet_timestamp in packets:
|
||||
# Don't pass our_public_key - we want to decrypt both incoming AND outgoing messages.
|
||||
# The our_public_key filter in try_decrypt_dm only matches incoming (dest_hash == us),
|
||||
# which would skip outgoing messages (where dest_hash == contact).
|
||||
result = try_decrypt_dm(
|
||||
packet_data,
|
||||
private_key_bytes,
|
||||
contact_public_key_bytes,
|
||||
our_public_key=None,
|
||||
)
|
||||
|
||||
if result is not None:
|
||||
# Successfully decrypted - determine if inbound or outbound by checking src_hash
|
||||
src_hash = result.src_hash.lower()
|
||||
our_first_byte = format(our_public_key_bytes[0], "02x").lower()
|
||||
outgoing = src_hash == our_first_byte
|
||||
|
||||
logger.debug(
|
||||
"Decrypted DM packet %d: message=%s (outgoing=%s)",
|
||||
packet_id,
|
||||
result.message[:50] if result.message else "",
|
||||
outgoing,
|
||||
)
|
||||
|
||||
# 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_dm_message_from_decrypted(
|
||||
packet_id=packet_id,
|
||||
decrypted=result,
|
||||
their_public_key=contact_public_key_hex,
|
||||
our_public_key=our_public_key_bytes.hex(),
|
||||
received_at=packet_timestamp,
|
||||
path=path_hex,
|
||||
outgoing=outgoing,
|
||||
)
|
||||
|
||||
if msg_id is not None:
|
||||
decrypted_count += 1
|
||||
|
||||
processed += 1
|
||||
_decrypt_progress = DecryptProgress(
|
||||
total=total, processed=processed, decrypted=decrypted_count, in_progress=True
|
||||
)
|
||||
|
||||
_decrypt_progress = DecryptProgress(
|
||||
total=total, processed=processed, decrypted=decrypted_count, in_progress=False
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Historical DM decryption complete: %d/%d packets decrypted", decrypted_count, total
|
||||
)
|
||||
|
||||
|
||||
@router.get("/undecrypted/count")
|
||||
async def get_undecrypted_count() -> dict:
|
||||
"""Get the count of undecrypted packets."""
|
||||
@@ -151,12 +242,82 @@ async def decrypt_historical_packets(
|
||||
total_packets=0,
|
||||
message="Must provide channel_key or channel_name",
|
||||
)
|
||||
elif request.key_type == "contact":
|
||||
# Validate required fields for contact decryption
|
||||
if not request.private_key:
|
||||
return DecryptResult(
|
||||
started=False,
|
||||
total_packets=0,
|
||||
message="Must provide private_key for contact decryption",
|
||||
)
|
||||
if not request.contact_public_key:
|
||||
return DecryptResult(
|
||||
started=False,
|
||||
total_packets=0,
|
||||
message="Must provide contact_public_key for contact decryption",
|
||||
)
|
||||
|
||||
# Parse private key
|
||||
try:
|
||||
private_key_bytes = bytes.fromhex(request.private_key)
|
||||
if len(private_key_bytes) != 64:
|
||||
return DecryptResult(
|
||||
started=False,
|
||||
total_packets=0,
|
||||
message="Private key must be 64 bytes (128 hex chars)",
|
||||
)
|
||||
except ValueError:
|
||||
return DecryptResult(
|
||||
started=False,
|
||||
total_packets=0,
|
||||
message="Invalid hex string for private key",
|
||||
)
|
||||
|
||||
# Parse contact public key
|
||||
try:
|
||||
contact_public_key_bytes = bytes.fromhex(request.contact_public_key)
|
||||
if len(contact_public_key_bytes) != 32:
|
||||
return DecryptResult(
|
||||
started=False,
|
||||
total_packets=0,
|
||||
message="Contact public key must be 32 bytes (64 hex chars)",
|
||||
)
|
||||
contact_public_key_hex = request.contact_public_key.lower()
|
||||
except ValueError:
|
||||
return DecryptResult(
|
||||
started=False,
|
||||
total_packets=0,
|
||||
message="Invalid hex string for contact public key",
|
||||
)
|
||||
|
||||
# Get count of undecrypted TEXT_MESSAGE packets
|
||||
packets = await RawPacketRepository.get_undecrypted_text_messages()
|
||||
count = len(packets)
|
||||
if count == 0:
|
||||
return DecryptResult(
|
||||
started=False,
|
||||
total_packets=0,
|
||||
message="No undecrypted TEXT_MESSAGE packets to process",
|
||||
)
|
||||
|
||||
# Start background decryption
|
||||
background_tasks.add_task(
|
||||
_run_historical_dm_decryption,
|
||||
private_key_bytes,
|
||||
contact_public_key_bytes,
|
||||
contact_public_key_hex,
|
||||
)
|
||||
|
||||
return DecryptResult(
|
||||
started=True,
|
||||
total_packets=count,
|
||||
message=f"Started DM decryption of {count} TEXT_MESSAGE packets in background",
|
||||
)
|
||||
else:
|
||||
# Contact decryption not yet supported (requires Ed25519 shared secret)
|
||||
return DecryptResult(
|
||||
started=False,
|
||||
total_packets=0,
|
||||
message="Contact key decryption not yet supported",
|
||||
message="key_type must be 'channel' or 'contact'",
|
||||
)
|
||||
|
||||
# Get count of undecrypted packets
|
||||
|
||||
@@ -137,6 +137,7 @@ await api.sendAdvertisement(true);
|
||||
|
||||
// Contacts/Channels
|
||||
await api.getContacts();
|
||||
await api.createContact(publicKey, name, tryHistorical); // Create contact, optionally decrypt historical DMs
|
||||
await api.getChannels();
|
||||
await api.createChannel('#test');
|
||||
|
||||
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"mappings":"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 truncated
|
||||
+11
-11
@@ -34,16 +34,16 @@ var Dv=Object.defineProperty;var Rv=(r,a,o)=>a in r?Dv(r,a,{enumerable:!0,config
|
||||
`+R+e}var K=!1;function $(e,n){if(!e||K)return"";K=!0;var s=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(n)if(n=function(){throw Error()},Object.defineProperty(n.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(n,[])}catch(ce){var c=ce}Reflect.construct(e,[],n)}else{try{n.call()}catch(ce){c=ce}e.call(n.prototype)}else{try{throw Error()}catch(ce){c=ce}e()}}catch(ce){if(ce&&c&&typeof ce.stack=="string"){for(var x=ce.stack.split(`
|
||||
`),_=c.stack.split(`
|
||||
`),M=x.length-1,W=_.length-1;1<=M&&0<=W&&x[M]!==_[W];)W--;for(;1<=M&&0<=W;M--,W--)if(x[M]!==_[W]){if(M!==1||W!==1)do if(M--,W--,0>W||x[M]!==_[W]){var X=`
|
||||
`+x[M].replace(" at new "," at ");return e.displayName&&X.includes("<anonymous>")&&(X=X.replace("<anonymous>",e.displayName)),X}while(1<=M&&0<=W);break}}}finally{K=!1,Error.prepareStackTrace=s}return(e=e?e.displayName||e.name:"")?U(e):""}function de(e){switch(e.tag){case 5:return U(e.type);case 16:return U("Lazy");case 13:return U("Suspense");case 19:return U("SuspenseList");case 0:case 2:case 15:return e=$(e.type,!1),e;case 11:return e=$(e.type.render,!1),e;case 1:return e=$(e.type,!0),e;default:return""}}function fe(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case H:return"Fragment";case z:return"Portal";case ee:return"Profiler";case j:return"StrictMode";case J:return"Suspense";case ie:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case V:return(e.displayName||"Context")+".Consumer";case Z:return(e._context.displayName||"Context")+".Provider";case ne:var n=e.render;return e=e.displayName,e||(e=n.displayName||n.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case he:return n=e.displayName||null,n!==null?n:fe(e.type)||"Memo";case ae:n=e._payload,e=e._init;try{return fe(e(n))}catch{}}return null}function ke(e){var n=e.type;switch(e.tag){case 24:return"Cache";case 9:return(n.displayName||"Context")+".Consumer";case 10:return(n._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=n.render,e=e.displayName||e.name||"",n.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return n;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return fe(n);case 8:return n===j?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof n=="function")return n.displayName||n.name||null;if(typeof n=="string")return n}return null}function Ae(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function We(e){var n=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(n==="checkbox"||n==="radio")}function $e(e){var n=We(e)?"checked":"value",s=Object.getOwnPropertyDescriptor(e.constructor.prototype,n),c=""+e[n];if(!e.hasOwnProperty(n)&&typeof s<"u"&&typeof s.get=="function"&&typeof s.set=="function"){var x=s.get,_=s.set;return Object.defineProperty(e,n,{configurable:!0,get:function(){return x.call(this)},set:function(M){c=""+M,_.call(this,M)}}),Object.defineProperty(e,n,{enumerable:s.enumerable}),{getValue:function(){return c},setValue:function(M){c=""+M},stopTracking:function(){e._valueTracker=null,delete e[n]}}}}function wt(e){e._valueTracker||(e._valueTracker=$e(e))}function ft(e){if(!e)return!1;var n=e._valueTracker;if(!n)return!0;var s=n.getValue(),c="";return e&&(c=We(e)?e.checked?"true":"false":e.value),e=c,e!==s?(n.setValue(e),!0):!1}function Be(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Ze(e,n){var s=n.checked;return q({},n,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:s??e._wrapperState.initialChecked})}function lt(e,n){var s=n.defaultValue==null?"":n.defaultValue,c=n.checked!=null?n.checked:n.defaultChecked;s=Ae(n.value!=null?n.value:s),e._wrapperState={initialChecked:c,initialValue:s,controlled:n.type==="checkbox"||n.type==="radio"?n.checked!=null:n.value!=null}}function Rt(e,n){n=n.checked,n!=null&&S(e,"checked",n,!1)}function xt(e,n){Rt(e,n);var s=Ae(n.value),c=n.type;if(s!=null)c==="number"?(s===0&&e.value===""||e.value!=s)&&(e.value=""+s):e.value!==""+s&&(e.value=""+s);else if(c==="submit"||c==="reset"){e.removeAttribute("value");return}n.hasOwnProperty("value")?Qe(e,n.type,s):n.hasOwnProperty("defaultValue")&&Qe(e,n.type,Ae(n.defaultValue)),n.checked==null&&n.defaultChecked!=null&&(e.defaultChecked=!!n.defaultChecked)}function it(e,n,s){if(n.hasOwnProperty("value")||n.hasOwnProperty("defaultValue")){var c=n.type;if(!(c!=="submit"&&c!=="reset"||n.value!==void 0&&n.value!==null))return;n=""+e._wrapperState.initialValue,s||n===e.value||(e.value=n),e.defaultValue=n}s=e.name,s!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,s!==""&&(e.name=s)}function Qe(e,n,s){(n!=="number"||Be(e.ownerDocument)!==e)&&(s==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+s&&(e.defaultValue=""+s))}var Xe=Array.isArray;function xe(e,n,s,c){if(e=e.options,n){n={};for(var x=0;x<s.length;x++)n["$"+s[x]]=!0;for(s=0;s<e.length;s++)x=n.hasOwnProperty("$"+e[s].value),e[s].selected!==x&&(e[s].selected=x),x&&c&&(e[s].defaultSelected=!0)}else{for(s=""+Ae(s),n=null,x=0;x<e.length;x++){if(e[x].value===s){e[x].selected=!0,c&&(e[x].defaultSelected=!0);return}n!==null||e[x].disabled||(n=e[x])}n!==null&&(n.selected=!0)}}function Te(e,n){if(n.dangerouslySetInnerHT
Line truncated
|
||||
`).replace(Q,"")}function ue(e,n,s){if(n=oe(n),oe(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 on=typeof setTimeout=="function"?setTimeout:void 0,Jt=typeof clearTimeout=="function"?clearTimeout:void 0,Fn=typeof Promise=="function"?Promise:void 0,bn=typeof queueMicrotask=="function"?queueMicrotask:typeof Fn<"u"?function(e){return Fn.resolve(null).then(e).catch(qi)}:on;function qi(e){setTimeout(function(){throw e})}function Ta(e,n){var s=n,c=0;do{var x=s.nextSibling;if(e.removeChild(s),x&&x.nodeType===8)if(s=x.data,s==="/$"){if(c===0){e.removeChild(x),Yn(n);return}c--}else s!=="$"&&s!=="$?"&&s!=="$!"||c++;s=x}while(s);Yn(n)}function Zr(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 Zl(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 ci=Math.random().toString(36).slice(2),fr="__reactFiber$"+ci,Gi="__reactProps$"+ci,_t="__reactContainer$"+ci,Ki="__reactEvents$"+ci,bd="__reactListeners$"+ci,Cd="__reactHandles$"+ci;function di(e){var n=e[fr];if(n)return n;for(var s=e.parentNode;s;){if(n=s[_t]||s[fr]){if(s=n.alternate,n.child!==null||s!==null&&s.child!==null)for(e=Zl(e);e!==null;){if(s=e[fr])return s;e=Zl(e)}return n}e=s,s=e.parentNode}return null}function fi(e){return e=e[fr]||e[_t],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function hi(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(o(33))}function bs(e){return e[Gi]||null}var Ed=[],Cs=-1;function Qi(e){return{current:e}}function Nt(e){0>Cs||(e.current=Ed[Cs],Ed[Cs]=null,Cs--)}function Dt(e,n){Cs++,Ed[Cs]=e.current,e.current=n}var $i={},Cn=Qi($i),jn=Qi(!1),So=$i;function Es(e,n){var s=e.type.contextTypes;if(!s)return $i;var c=e.stateNode;if(c&&c.__reactInternalMemoizedUnmaskedChildContext===n)return c.__reactInternalMemoizedMaskedChildContext;var x={},_;for(_ in s)x[_]=n[_];return c&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=n,e.__reactInternalMemoizedMaskedChildContext=x),x}function zn(e){return e=e.childContextTypes,e!=null}function Wl(){Nt(jn),Nt(Cn)}function K0(e,n,s){if(Cn.current!==$i)throw Error(o(168));Dt(Cn,n),Dt(jn,s)}function Q0(e,n,s){var c=e.stateNode;if(n=n.childContextTypes,typeof c.getChildContext!="function")return s;c=c.getChildContext();for(var x in c)if(!(x in n))throw Error(o(108,ke(e)||"Unknown",x));return q({},s,c)}function ql(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||$i,So=Cn.current,Dt(Cn,e),Dt(jn,jn.current),!0}function $0(e,n,s){var c=e.stateNode;if(!c)throw Error(o(169));s?(e=Q0(e,n,So),c.__reactInternalMemoizedMergedChildContext=e,Nt(jn),Nt(Cn),Dt(Cn,e)):Nt(jn),Dt(jn,s)}var pi=null,Gl=!1,kd=!1;function Y0(e){pi===null?pi=[e]:pi.push(e)}function $x(e){Gl=!0,Y0(e)}function Yi(){if(!kd&&pi!==null){kd=!0;var e=0,n=ht;try{var s=pi;for(ht=1;e<s.length;e++){var c=s[e];do c=c(!0);while(c!==null)}pi=null,Gl=!1}catch(x){throw pi!==null&&(pi=pi.slice(e+1)),yl(oa,Yi),x}finally{ht=n,kd=!1}}return null}var ks=[],Ss=0,Kl=null,Ql=0,hr=[],pr=0,Bo=null,mi=1,gi="";function Po(e,n){ks[Ss++]=Ql,ks[Ss++]=Kl,Kl=e,Ql=n}function X0(e,n,s){hr[pr++]=mi,hr[pr++]=gi,hr[pr++]=Bo,Bo=e;var c=mi;e=gi;var x=32-tt(c)-1;c&=~(1<<x),s+=1;var _=32-tt(n)+x;if(30<_){var M=x-x%5;_=(c&(1<<M)-1).toString(32),c>>=M,x-=M,mi=1<<32-tt(n)+x|s<<x|c,gi=_+e}else mi=1<<_|s<<x|c,gi=e}function Sd(e){e.return!==null&&(Po(e,1),X0(e,1,0))}function Bd(e){for(;e===Kl;)Kl=ks[--Ss],ks[Ss]=null,Ql=ks[--Ss],ks[Ss]=null;for(;e===Bo;)Bo=hr[--pr],hr[pr]=null,gi=hr[--pr],hr[pr]=null,mi=hr[--pr],hr[pr]=null}var nr=null,rr=null,It=!1,kr=null;function J0(e,n){var s=xr(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 eh(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,nr=e,rr=Zr(n.firstChild),!0):!1;case 6:return n=e.pendingProps===""||n.nodeType!==3?null:n,n!==null?(e.stateNode=n,nr=e,rr=null,!0):!1;case 13:return n=n.nodeType!==8?null:n,n!==null?(s=Bo!==null?{id:mi,overflow:gi}:null,e.memoizedState={dehydrated:n,treeContext:s,retryLane:1073741824},s=xr(18,null,null,0),s.stateNode=n,s.return=e,e.child=s,nr=e,rr=null,!0):!1;default:return!1}}function Pd(e){return(e.mode&1)!==0&&(e.flags&128)===0}function Td(e){if(It){var n=rr;if(n){var s=n;if(!eh(e,n)){if(Pd(e))throw Error(o(418));n=Zr(s.nextSibling);var c=nr;n&&eh(e,n)?J0(c,s):(e.flags=e.flags&-4097|2,It=!1,nr=e)}}else{if(Pd(e))throw Error(o(418));e.flags=e.flags&-4097|2,It=!1,nr=e}}}function th(e){for(e=e.r
Line truncated
|
||||
`+x[M].replace(" at new "," at ");return e.displayName&&X.includes("<anonymous>")&&(X=X.replace("<anonymous>",e.displayName)),X}while(1<=M&&0<=W);break}}}finally{K=!1,Error.prepareStackTrace=s}return(e=e?e.displayName||e.name:"")?U(e):""}function de(e){switch(e.tag){case 5:return U(e.type);case 16:return U("Lazy");case 13:return U("Suspense");case 19:return U("SuspenseList");case 0:case 2:case 15:return e=$(e.type,!1),e;case 11:return e=$(e.type.render,!1),e;case 1:return e=$(e.type,!0),e;default:return""}}function fe(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case H:return"Fragment";case z:return"Portal";case ee:return"Profiler";case j:return"StrictMode";case J:return"Suspense";case ie:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case V:return(e.displayName||"Context")+".Consumer";case Z:return(e._context.displayName||"Context")+".Provider";case ne:var n=e.render;return e=e.displayName,e||(e=n.displayName||n.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case he:return n=e.displayName||null,n!==null?n:fe(e.type)||"Memo";case ae:n=e._payload,e=e._init;try{return fe(e(n))}catch{}}return null}function ke(e){var n=e.type;switch(e.tag){case 24:return"Cache";case 9:return(n.displayName||"Context")+".Consumer";case 10:return(n._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=n.render,e=e.displayName||e.name||"",n.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return n;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return fe(n);case 8:return n===j?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof n=="function")return n.displayName||n.name||null;if(typeof n=="string")return n}return null}function Ae(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function We(e){var n=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(n==="checkbox"||n==="radio")}function $e(e){var n=We(e)?"checked":"value",s=Object.getOwnPropertyDescriptor(e.constructor.prototype,n),c=""+e[n];if(!e.hasOwnProperty(n)&&typeof s<"u"&&typeof s.get=="function"&&typeof s.set=="function"){var x=s.get,_=s.set;return Object.defineProperty(e,n,{configurable:!0,get:function(){return x.call(this)},set:function(M){c=""+M,_.call(this,M)}}),Object.defineProperty(e,n,{enumerable:s.enumerable}),{getValue:function(){return c},setValue:function(M){c=""+M},stopTracking:function(){e._valueTracker=null,delete e[n]}}}}function wt(e){e._valueTracker||(e._valueTracker=$e(e))}function ft(e){if(!e)return!1;var n=e._valueTracker;if(!n)return!0;var s=n.getValue(),c="";return e&&(c=We(e)?e.checked?"true":"false":e.value),e=c,e!==s?(n.setValue(e),!0):!1}function Be(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Ze(e,n){var s=n.checked;return q({},n,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:s??e._wrapperState.initialChecked})}function lt(e,n){var s=n.defaultValue==null?"":n.defaultValue,c=n.checked!=null?n.checked:n.defaultChecked;s=Ae(n.value!=null?n.value:s),e._wrapperState={initialChecked:c,initialValue:s,controlled:n.type==="checkbox"||n.type==="radio"?n.checked!=null:n.value!=null}}function Rt(e,n){n=n.checked,n!=null&&S(e,"checked",n,!1)}function vt(e,n){Rt(e,n);var s=Ae(n.value),c=n.type;if(s!=null)c==="number"?(s===0&&e.value===""||e.value!=s)&&(e.value=""+s):e.value!==""+s&&(e.value=""+s);else if(c==="submit"||c==="reset"){e.removeAttribute("value");return}n.hasOwnProperty("value")?Qe(e,n.type,s):n.hasOwnProperty("defaultValue")&&Qe(e,n.type,Ae(n.defaultValue)),n.checked==null&&n.defaultChecked!=null&&(e.defaultChecked=!!n.defaultChecked)}function ot(e,n,s){if(n.hasOwnProperty("value")||n.hasOwnProperty("defaultValue")){var c=n.type;if(!(c!=="submit"&&c!=="reset"||n.value!==void 0&&n.value!==null))return;n=""+e._wrapperState.initialValue,s||n===e.value||(e.value=n),e.defaultValue=n}s=e.name,s!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,s!==""&&(e.name=s)}function Qe(e,n,s){(n!=="number"||Be(e.ownerDocument)!==e)&&(s==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+s&&(e.defaultValue=""+s))}var Xe=Array.isArray;function xe(e,n,s,c){if(e=e.options,n){n={};for(var x=0;x<s.length;x++)n["$"+s[x]]=!0;for(s=0;s<e.length;s++)x=n.hasOwnProperty("$"+e[s].value),e[s].selected!==x&&(e[s].selected=x),x&&c&&(e[s].defaultSelected=!0)}else{for(s=""+Ae(s),n=null,x=0;x<e.length;x++){if(e[x].value===s){e[x].selected=!0,c&&(e[x].defaultSelected=!0);return}n!==null||e[x].disabled||(n=e[x])}n!==null&&(n.selected=!0)}}function Te(e,n){if(n.dangerouslySetInnerHT
Line truncated
|
||||
`).replace(Q,"")}function ue(e,n,s){if(n=oe(n),oe(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 on=typeof setTimeout=="function"?setTimeout:void 0,Jt=typeof clearTimeout=="function"?clearTimeout:void 0,Fn=typeof Promise=="function"?Promise:void 0,bn=typeof queueMicrotask=="function"?queueMicrotask:typeof Fn<"u"?function(e){return Fn.resolve(null).then(e).catch(qi)}:on;function qi(e){setTimeout(function(){throw e})}function Ta(e,n){var s=n,c=0;do{var x=s.nextSibling;if(e.removeChild(s),x&&x.nodeType===8)if(s=x.data,s==="/$"){if(c===0){e.removeChild(x),Yn(n);return}c--}else s!=="$"&&s!=="$?"&&s!=="$!"||c++;s=x}while(s);Yn(n)}function Zr(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 Zl(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 ci=Math.random().toString(36).slice(2),fr="__reactFiber$"+ci,Gi="__reactProps$"+ci,_t="__reactContainer$"+ci,Ki="__reactEvents$"+ci,bd="__reactListeners$"+ci,Cd="__reactHandles$"+ci;function di(e){var n=e[fr];if(n)return n;for(var s=e.parentNode;s;){if(n=s[_t]||s[fr]){if(s=n.alternate,n.child!==null||s!==null&&s.child!==null)for(e=Zl(e);e!==null;){if(s=e[fr])return s;e=Zl(e)}return n}e=s,s=e.parentNode}return null}function fi(e){return e=e[fr]||e[_t],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function hi(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(o(33))}function bs(e){return e[Gi]||null}var Ed=[],Cs=-1;function Qi(e){return{current:e}}function Nt(e){0>Cs||(e.current=Ed[Cs],Ed[Cs]=null,Cs--)}function Dt(e,n){Cs++,Ed[Cs]=e.current,e.current=n}var $i={},Cn=Qi($i),jn=Qi(!1),So=$i;function Es(e,n){var s=e.type.contextTypes;if(!s)return $i;var c=e.stateNode;if(c&&c.__reactInternalMemoizedUnmaskedChildContext===n)return c.__reactInternalMemoizedMaskedChildContext;var x={},_;for(_ in s)x[_]=n[_];return c&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=n,e.__reactInternalMemoizedMaskedChildContext=x),x}function zn(e){return e=e.childContextTypes,e!=null}function Wl(){Nt(jn),Nt(Cn)}function K0(e,n,s){if(Cn.current!==$i)throw Error(o(168));Dt(Cn,n),Dt(jn,s)}function Q0(e,n,s){var c=e.stateNode;if(n=n.childContextTypes,typeof c.getChildContext!="function")return s;c=c.getChildContext();for(var x in c)if(!(x in n))throw Error(o(108,ke(e)||"Unknown",x));return q({},s,c)}function ql(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||$i,So=Cn.current,Dt(Cn,e),Dt(jn,jn.current),!0}function $0(e,n,s){var c=e.stateNode;if(!c)throw Error(o(169));s?(e=Q0(e,n,So),c.__reactInternalMemoizedMergedChildContext=e,Nt(jn),Nt(Cn),Dt(Cn,e)):Nt(jn),Dt(jn,s)}var pi=null,Gl=!1,kd=!1;function Y0(e){pi===null?pi=[e]:pi.push(e)}function $x(e){Gl=!0,Y0(e)}function Yi(){if(!kd&&pi!==null){kd=!0;var e=0,n=ht;try{var s=pi;for(ht=1;e<s.length;e++){var c=s[e];do c=c(!0);while(c!==null)}pi=null,Gl=!1}catch(x){throw pi!==null&&(pi=pi.slice(e+1)),yl(oa,Yi),x}finally{ht=n,kd=!1}}return null}var ks=[],Ss=0,Kl=null,Ql=0,hr=[],pr=0,Bo=null,mi=1,gi="";function Po(e,n){ks[Ss++]=Ql,ks[Ss++]=Kl,Kl=e,Ql=n}function X0(e,n,s){hr[pr++]=mi,hr[pr++]=gi,hr[pr++]=Bo,Bo=e;var c=mi;e=gi;var x=32-tt(c)-1;c&=~(1<<x),s+=1;var _=32-tt(n)+x;if(30<_){var M=x-x%5;_=(c&(1<<M)-1).toString(32),c>>=M,x-=M,mi=1<<32-tt(n)+x|s<<x|c,gi=_+e}else mi=1<<_|s<<x|c,gi=e}function Sd(e){e.return!==null&&(Po(e,1),X0(e,1,0))}function Bd(e){for(;e===Kl;)Kl=ks[--Ss],ks[Ss]=null,Ql=ks[--Ss],ks[Ss]=null;for(;e===Bo;)Bo=hr[--pr],hr[pr]=null,gi=hr[--pr],hr[pr]=null,mi=hr[--pr],hr[pr]=null}var nr=null,rr=null,It=!1,kr=null;function J0(e,n){var s=xr(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 eh(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,nr=e,rr=Zr(n.firstChild),!0):!1;case 6:return n=e.pendingProps===""||n.nodeType!==3?null:n,n!==null?(e.stateNode=n,nr=e,rr=null,!0):!1;case 13:return n=n.nodeType!==8?null:n,n!==null?(s=Bo!==null?{id:mi,overflow:gi}:null,e.memoizedState={dehydrated:n,treeContext:s,retryLane:1073741824},s=xr(18,null,null,0),s.stateNode=n,s.return=e,e.child=s,nr=e,rr=null,!0):!1;default:return!1}}function Pd(e){return(e.mode&1)!==0&&(e.flags&128)===0}function Td(e){if(It){var n=rr;if(n){var s=n;if(!eh(e,n)){if(Pd(e))throw Error(o(418));n=Zr(s.nextSibling);var c=nr;n&&eh(e,n)?J0(c,s):(e.flags=e.flags&-4097|2,It=!1,nr=e)}}else{if(Pd(e))throw Error(o(418));e.flags=e.flags&-4097|2,It=!1,nr=e}}}function th(e){for(e=e.r
Line truncated
|
||||
Error generating stack: `+_.message+`
|
||||
`+_.stack}return{value:e,source:n,stack:x,digest:null}}function Xd(e,n,s){return{value:e,source:null,stack:s??null,digest:n??null}}function Jd(e,n){try{console.error(n.value)}catch(s){setTimeout(function(){throw s})}}var ov=typeof WeakMap=="function"?WeakMap:Map;function Lh(e,n,s){s=xi(-1,s),s.tag=3,s.payload={element:null};var c=n.value;return s.callback=function(){mu||(mu=!0,mf=c),Jd(e,n)},s}function Mh(e,n,s){s=xi(-1,s),s.tag=3;var c=e.type.getDerivedStateFromError;if(typeof c=="function"){var x=n.value;s.payload=function(){return c(x)},s.callback=function(){Jd(e,n)}}var _=e.stateNode;return _!==null&&typeof _.componentDidCatch=="function"&&(s.callback=function(){Jd(e,n),typeof c!="function"&&(to===null?to=new Set([this]):to.add(this));var M=n.stack;this.componentDidCatch(n.value,{componentStack:M!==null?M:""})}),s}function Ih(e,n,s){var c=e.pingCache;if(c===null){c=e.pingCache=new ov;var x=new Set;c.set(n,x)}else x=c.get(n),x===void 0&&(x=new Set,c.set(n,x));x.has(s)||(x.add(s),e=vv.bind(null,e,n,s),n.then(e,e))}function Oh(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 jh(e,n,s,c,x){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=xi(-1,1),n.tag=2,Ji(s,n,1))),s.lanes|=1),e):(e.flags|=65536,e.lanes=x,e)}var sv=N.ReactCurrentOwner,Hn=!1;function Nn(e,n,s,c){n.child=e===null?oh(n,null,s,c):Ps(n,e.child,s,c)}function zh(e,n,s,c,x){s=s.render;var _=n.ref;return Ds(n,x),c=Zd(e,n,s,c,_,x),s=Wd(),e!==null&&!Hn?(n.updateQueue=e.updateQueue,n.flags&=-2053,e.lanes&=~x,vi(e,n,x)):(It&&s&&Sd(n),n.flags|=1,Nn(e,n,c,x),n.child)}function Hh(e,n,s,c,x){if(e===null){var _=s.type;return typeof _=="function"&&!wf(_)&&_.defaultProps===void 0&&s.compare===null&&s.defaultProps===void 0?(n.tag=15,n.type=_,Vh(e,n,_,c,x)):(e=_u(s.type,null,c,n,n.mode,x),e.ref=n.ref,e.return=n,n.child=e)}if(_=e.child,(e.lanes&x)===0){var M=_.memoizedProps;if(s=s.compare,s=s!==null?s:Hr,s(M,c)&&e.ref===n.ref)return vi(e,n,x)}return n.flags|=1,e=oo(_,c),e.ref=n.ref,e.return=n,n.child=e}function Vh(e,n,s,c,x){if(e!==null){var _=e.memoizedProps;if(Hr(_,c)&&e.ref===n.ref)if(Hn=!1,n.pendingProps=c=_,(e.lanes&x)!==0)(e.flags&131072)!==0&&(Hn=!0);else return n.lanes=e.lanes,vi(e,n,x)}return ef(e,n,s,c,x)}function Uh(e,n,s){var c=n.pendingProps,x=c.children,_=e!==null?e.memoizedState:null;if(c.mode==="hidden")if((n.mode&1)===0)n.memoizedState={baseLanes:0,cachePool:null,transitions:null},Dt(Ls,ir),ir|=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,Dt(Ls,ir),ir|=e,null;n.memoizedState={baseLanes:0,cachePool:null,transitions:null},c=_!==null?_.baseLanes:s,Dt(Ls,ir),ir|=c}else _!==null?(c=_.baseLanes|s,n.memoizedState=null):c=s,Dt(Ls,ir),ir|=c;return Nn(e,n,x,s),n.child}function Zh(e,n){var s=n.ref;(e===null&&s!==null||e!==null&&e.ref!==s)&&(n.flags|=512,n.flags|=2097152)}function ef(e,n,s,c,x){var _=zn(s)?So:Cn.current;return _=Es(n,_),Ds(n,x),s=Zd(e,n,s,c,_,x),c=Wd(),e!==null&&!Hn?(n.updateQueue=e.updateQueue,n.flags&=-2053,e.lanes&=~x,vi(e,n,x)):(It&&c&&Sd(n),n.flags|=1,Nn(e,n,s,x),n.child)}function Wh(e,n,s,c,x){if(zn(s)){var _=!0;ql(n)}else _=!1;if(Ds(n,x),n.stateNode===null)cu(e,n),Fh(n,s,c),Yd(n,s,c,x),c=!0;else if(e===null){var M=n.stateNode,W=n.memoizedProps;M.props=W;var X=M.context,ce=s.contextType;typeof ce=="object"&&ce!==null?ce=mr(ce):(ce=zn(s)?So:Cn.current,ce=Es(n,ce));var _e=s.getDerivedStateFromProps,we=typeof _e=="function"||typeof M.getSnapshotBeforeUpdate=="function";we||typeof M.UNSAFE_componentWillReceiveProps!="function"&&typeof M.componentWillReceiveProps!="function"||(W!==c||X!==ce)&&Nh(n,M,c,ce),Xi=!1;var ye=n.memoizedState;M.state=ye,tu(n,c,M,x),X=n.memoizedState,W!==c||ye!==X||jn.current||Xi?(typeof _e=="function"&&($d(n,s,_e,c),X=n.memoizedState),(W=Xi||Rh(n,s,W,c,ye,X,ce))?(we||typeof M.UNSAFE_componentWillMount!="function"&&typeof M.componentWillMount!="function"||(typeof M.componentWillMount=="function"&&M.componentWillMount(),typeof M.UNSAFE_componentWillMount=="function"&&M.UNSAFE_componentWillMount()),typeof M.componentDidMount=="function"&&(n.flags|=4194308)):(typeof M.componentDidMount=="function"&&(n.flags|=4194308),n.memoizedProps=c,n.memoizedState=X),M.props=c,M.state=X,M.context=ce,c=W):(typeof M.componentDidMount=="function"&&(n.flags|=4194308),c=!1)}else{M=n.stateNode,ah(e,n),W=n.memoizedProps,ce=n.type===n.elementType?W:Sr(n.type,W),M.props=ce,we=n.pendingProps,ye=M.context,X=s.contextType,typeof X=="object"&&X!==null?X=mr(X):(X=zn(s)?So:Cn.current,X=Es(n,X));var Fe=s.getDerivedStateFromProps;(_e=typeof Fe=="function"||typeof M.getSnapshotBeforeUpdate=="function")||typeof M.UNSAFE_componentWillReceiveProps!="function"&&typeof M.componentWillReceiveProps!="function"||(W!==we||ye!==X)&&Nh(n,M,c,X),Xi=!1,ye=n.m
Line truncated
|
||||
`)}function Os(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,paths:null,txt_type:0,signature:null,outgoing:o,acked:1}}function Gv(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 p=a.current;if(!p)return;let h=null,g=null;for(let m=0;m<2;m++)try{h=await ot.requestTelemetry(p.conversationId,"");break}catch(v){g=v instanceof Error?v:new Error("Unknown error"),m===0&&await new Promise(y=>setTimeout(y,2e3))}if(h){const m=Os(p.conversationId,qv(p,h),!1);r(v=>[...v,m])}else{const m=Os(p.conversationId,`Duty cycle poll failed after retry: ${(g==null?void 0:g.message)??"Unknown error"}`,!1);r(v=>[...v,m])}},[r]),f=F.useCallback(async(p,h)=>{const g=p.trim().toLowerCase();if(g==="dutycycle_start"){l();let m=null,v=null;for(let B=0;B<2;B++)try{m=await ot.requestTelemetry(h,"");break}catch(T){v=T instanceof Error?T:new Error("Unknown error"),B===0&&await new Promise(C=>setTimeout(C,2e3))}if(!m){const B=Os(h,`Failed to start duty cycle tracking after retry: ${(v==null?void 0:v.message)??"Unknown error"}`,!1);return r(T=>[...T,B]),!0}const y=Math.floor(Date.now()/1e3);a.current={startTime:y,uptime:m.uptime_seconds,txAirtime:m.airtime_seconds,rxAirtime:m.rx_airtime_seconds,sentFlood:m.sent_flood,sentDirect:m.sent_direct,recvFlood:m.recv_flood,recvDirect:m.recv_direct,conversationId:h};const w=Os(h,`Airtime/duty cycle statistics gathering begins at ${y}. Logs will follow every 5 minutes. To stop, run dutycycle_stop or navigate away from this conversation.`,!1);return r(B=>[...B,w]),o.current=window.setInterval(d,Zv),!0}if(g==="dutycycle_stop"){if(a.current&&a.current.conversationId===h){await d(),l();const m=Os(h,"Airtime/duty cycle statistics gathering stopped.",!1);r(v=>[...v,m])}else{const m=Os(h,"Duty cycle tracking is not active.",!1);r(v=>[...v,m])}return!0}return!1},[r,l,d]);return F.useEffect(()=>()=>{o.current!==null&&clearInterval(o.current)},[]),{handleAirtimeCommand:f,stopTracking:l}}function Hu(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 Kv(r){return["Telemetry",`Battery Voltage: ${r.battery_volts.toFixed(3)}V`,`Uptime: ${Hu(r.uptime_seconds)}`,`TX Airtime: ${Hu(r.airtime_seconds)}`,`RX Airtime: ${Hu(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(`
|
||||
`+_.stack}return{value:e,source:n,stack:x,digest:null}}function Xd(e,n,s){return{value:e,source:null,stack:s??null,digest:n??null}}function Jd(e,n){try{console.error(n.value)}catch(s){setTimeout(function(){throw s})}}var ov=typeof WeakMap=="function"?WeakMap:Map;function Lh(e,n,s){s=xi(-1,s),s.tag=3,s.payload={element:null};var c=n.value;return s.callback=function(){mu||(mu=!0,mf=c),Jd(e,n)},s}function Mh(e,n,s){s=xi(-1,s),s.tag=3;var c=e.type.getDerivedStateFromError;if(typeof c=="function"){var x=n.value;s.payload=function(){return c(x)},s.callback=function(){Jd(e,n)}}var _=e.stateNode;return _!==null&&typeof _.componentDidCatch=="function"&&(s.callback=function(){Jd(e,n),typeof c!="function"&&(to===null?to=new Set([this]):to.add(this));var M=n.stack;this.componentDidCatch(n.value,{componentStack:M!==null?M:""})}),s}function Ih(e,n,s){var c=e.pingCache;if(c===null){c=e.pingCache=new ov;var x=new Set;c.set(n,x)}else x=c.get(n),x===void 0&&(x=new Set,c.set(n,x));x.has(s)||(x.add(s),e=vv.bind(null,e,n,s),n.then(e,e))}function Oh(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 jh(e,n,s,c,x){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=xi(-1,1),n.tag=2,Ji(s,n,1))),s.lanes|=1),e):(e.flags|=65536,e.lanes=x,e)}var sv=N.ReactCurrentOwner,Hn=!1;function Nn(e,n,s,c){n.child=e===null?oh(n,null,s,c):Ps(n,e.child,s,c)}function zh(e,n,s,c,x){s=s.render;var _=n.ref;return Ds(n,x),c=Zd(e,n,s,c,_,x),s=Wd(),e!==null&&!Hn?(n.updateQueue=e.updateQueue,n.flags&=-2053,e.lanes&=~x,vi(e,n,x)):(It&&s&&Sd(n),n.flags|=1,Nn(e,n,c,x),n.child)}function Hh(e,n,s,c,x){if(e===null){var _=s.type;return typeof _=="function"&&!wf(_)&&_.defaultProps===void 0&&s.compare===null&&s.defaultProps===void 0?(n.tag=15,n.type=_,Vh(e,n,_,c,x)):(e=_u(s.type,null,c,n,n.mode,x),e.ref=n.ref,e.return=n,n.child=e)}if(_=e.child,(e.lanes&x)===0){var M=_.memoizedProps;if(s=s.compare,s=s!==null?s:Hr,s(M,c)&&e.ref===n.ref)return vi(e,n,x)}return n.flags|=1,e=oo(_,c),e.ref=n.ref,e.return=n,n.child=e}function Vh(e,n,s,c,x){if(e!==null){var _=e.memoizedProps;if(Hr(_,c)&&e.ref===n.ref)if(Hn=!1,n.pendingProps=c=_,(e.lanes&x)!==0)(e.flags&131072)!==0&&(Hn=!0);else return n.lanes=e.lanes,vi(e,n,x)}return ef(e,n,s,c,x)}function Uh(e,n,s){var c=n.pendingProps,x=c.children,_=e!==null?e.memoizedState:null;if(c.mode==="hidden")if((n.mode&1)===0)n.memoizedState={baseLanes:0,cachePool:null,transitions:null},Dt(Ls,ir),ir|=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,Dt(Ls,ir),ir|=e,null;n.memoizedState={baseLanes:0,cachePool:null,transitions:null},c=_!==null?_.baseLanes:s,Dt(Ls,ir),ir|=c}else _!==null?(c=_.baseLanes|s,n.memoizedState=null):c=s,Dt(Ls,ir),ir|=c;return Nn(e,n,x,s),n.child}function Zh(e,n){var s=n.ref;(e===null&&s!==null||e!==null&&e.ref!==s)&&(n.flags|=512,n.flags|=2097152)}function ef(e,n,s,c,x){var _=zn(s)?So:Cn.current;return _=Es(n,_),Ds(n,x),s=Zd(e,n,s,c,_,x),c=Wd(),e!==null&&!Hn?(n.updateQueue=e.updateQueue,n.flags&=-2053,e.lanes&=~x,vi(e,n,x)):(It&&c&&Sd(n),n.flags|=1,Nn(e,n,s,x),n.child)}function Wh(e,n,s,c,x){if(zn(s)){var _=!0;ql(n)}else _=!1;if(Ds(n,x),n.stateNode===null)cu(e,n),Fh(n,s,c),Yd(n,s,c,x),c=!0;else if(e===null){var M=n.stateNode,W=n.memoizedProps;M.props=W;var X=M.context,ce=s.contextType;typeof ce=="object"&&ce!==null?ce=mr(ce):(ce=zn(s)?So:Cn.current,ce=Es(n,ce));var _e=s.getDerivedStateFromProps,we=typeof _e=="function"||typeof M.getSnapshotBeforeUpdate=="function";we||typeof M.UNSAFE_componentWillReceiveProps!="function"&&typeof M.componentWillReceiveProps!="function"||(W!==c||X!==ce)&&Nh(n,M,c,ce),Xi=!1;var ye=n.memoizedState;M.state=ye,tu(n,c,M,x),X=n.memoizedState,W!==c||ye!==X||jn.current||Xi?(typeof _e=="function"&&($d(n,s,_e,c),X=n.memoizedState),(W=Xi||Rh(n,s,W,c,ye,X,ce))?(we||typeof M.UNSAFE_componentWillMount!="function"&&typeof M.componentWillMount!="function"||(typeof M.componentWillMount=="function"&&M.componentWillMount(),typeof M.UNSAFE_componentWillMount=="function"&&M.UNSAFE_componentWillMount()),typeof M.componentDidMount=="function"&&(n.flags|=4194308)):(typeof M.componentDidMount=="function"&&(n.flags|=4194308),n.memoizedProps=c,n.memoizedState=X),M.props=c,M.state=X,M.context=ce,c=W):(typeof M.componentDidMount=="function"&&(n.flags|=4194308),c=!1)}else{M=n.stateNode,ah(e,n),W=n.memoizedProps,ce=n.type===n.elementType?W:Sr(n.type,W),M.props=ce,we=n.pendingProps,ye=M.context,X=s.contextType,typeof X=="object"&&X!==null?X=mr(X):(X=zn(s)?So:Cn.current,X=Es(n,X));var Fe=s.getDerivedStateFromProps;(_e=typeof Fe=="function"||typeof M.getSnapshotBeforeUpdate=="function")||typeof M.UNSAFE_componentWillReceiveProps!="function"&&typeof M.componentWillReceiveProps!="function"||(W!==we||ye!==X)&&Nh(n,M,c,X),Xi=!1,ye=n.m
Line truncated
|
||||
`)}function Os(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,paths:null,txt_type:0,signature:null,outgoing:o,acked:1}}function Gv(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 p=a.current;if(!p)return;let h=null,g=null;for(let m=0;m<2;m++)try{h=await rt.requestTelemetry(p.conversationId,"");break}catch(v){g=v instanceof Error?v:new Error("Unknown error"),m===0&&await new Promise(y=>setTimeout(y,2e3))}if(h){const m=Os(p.conversationId,qv(p,h),!1);r(v=>[...v,m])}else{const m=Os(p.conversationId,`Duty cycle poll failed after retry: ${(g==null?void 0:g.message)??"Unknown error"}`,!1);r(v=>[...v,m])}},[r]),f=F.useCallback(async(p,h)=>{const g=p.trim().toLowerCase();if(g==="dutycycle_start"){l();let m=null,v=null;for(let B=0;B<2;B++)try{m=await rt.requestTelemetry(h,"");break}catch(T){v=T instanceof Error?T:new Error("Unknown error"),B===0&&await new Promise(C=>setTimeout(C,2e3))}if(!m){const B=Os(h,`Failed to start duty cycle tracking after retry: ${(v==null?void 0:v.message)??"Unknown error"}`,!1);return r(T=>[...T,B]),!0}const y=Math.floor(Date.now()/1e3);a.current={startTime:y,uptime:m.uptime_seconds,txAirtime:m.airtime_seconds,rxAirtime:m.rx_airtime_seconds,sentFlood:m.sent_flood,sentDirect:m.sent_direct,recvFlood:m.recv_flood,recvDirect:m.recv_direct,conversationId:h};const w=Os(h,`Airtime/duty cycle statistics gathering begins at ${y}. Logs will follow every 5 minutes. To stop, run dutycycle_stop or navigate away from this conversation.`,!1);return r(B=>[...B,w]),o.current=window.setInterval(d,Zv),!0}if(g==="dutycycle_stop"){if(a.current&&a.current.conversationId===h){await d(),l();const m=Os(h,"Airtime/duty cycle statistics gathering stopped.",!1);r(v=>[...v,m])}else{const m=Os(h,"Duty cycle tracking is not active.",!1);r(v=>[...v,m])}return!0}return!1},[r,l,d]);return F.useEffect(()=>()=>{o.current!==null&&clearInterval(o.current)},[]),{handleAirtimeCommand:f,stopTracking:l}}function Hu(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 Kv(r){return["Telemetry",`Battery Voltage: ${r.battery_volts.toFixed(3)}V`,`Uptime: ${Hu(r.uptime_seconds)}`,`TX Airtime: ${Hu(r.airtime_seconds)}`,`RX Airtime: ${Hu(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 Qv(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 [${Hu(l.last_heard_seconds)} ago]`)}return o.join(`
|
||||
`)}function $v(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 jo(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,paths:null,txt_type:0,signature:null,outgoing:o,acked:1}}function Yv(r,a,o){const[l,d]=F.useState(!1),{handleAirtimeCommand:f,stopTracking:p}=Gv(o);F.useEffect(()=>{d(!1),p()},[r==null?void 0:r.id,p]);const h=F.useMemo(()=>{if(!r||r.type!=="contact")return!1;const v=a.find(y=>y.public_key===r.id);return(v==null?void 0:v.type)===Oc},[r,a]),g=F.useCallback(async v=>{if(!(!r||r.type!=="contact")&&h)try{const y=await ot.requestTelemetry(r.id,v),w=jo(r.id,Kv(y),!1,0),B=jo(r.id,Qv(y.neighbors),!1,1),T=jo(r.id,$v(y.acl),!1,2);o(C=>[...C,w,B,T]),d(!0)}catch(y){const w=jo(r.id,`Telemetry request failed: ${y instanceof Error?y.message:"Unknown error"}`,!1,0);o(B=>[...B,w])}},[r,h,o]),m=F.useCallback(async v=>{if(!r||r.type!=="contact"||!h||!l||await f(v,r.id))return;const w=jo(r.id,`> ${v}`,!0,0);o(B=>[...B,w]);try{const B=await ot.sendRepeaterCommand(r.id,v),T=jo(r.id,B.response,!1,1);B.sender_timestamp&&(T.sender_timestamp=B.sender_timestamp),o(C=>[...C,T])}catch(B){const T=jo(r.id,`Command failed: ${B instanceof Error?B.message:"Unknown error"}`,!1,1);o(C=>[...C,T])}},[r,h,l,o,f]);return{repeaterLoggedIn:l,activeContactIsRepeater:h,handleTelemetryRequest:g,handleRepeaterCommand:m}}const Xv=12;function Ks(r){return r.slice(0,Xv)}function x0(r,a){return!r||!a?!1:Ks(r)===Ks(a)}function Ei(r,a){return r||Ks(a)}const Xf="remoteterm-lastMessageTime";function Og(r){try{const a=localStorage.getItem(r);return a?JSON.parse(a):{}}catch{return{}}}function Jv(r,a){try{localStorage.setItem(r,JSON.stringify(a))}catch{}}function Mp(){return Og(Xf)}function Lf(r,a){const o=Og(Xf);return(!o[r]||a>o[r])&&(o[r]=a,Jv(Xf,o)),o}function Rr(r,a){return r==="channel"?`channel-${a}`:`contact-${Ks(a)}`}function Ip(r,a){if(!a)return!1;const o=a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`@\\[${o}\\]`,"i").test(r)}function ey(r,a,o,l=null){const[d,f]=F.useState({}),[p,h]=F.useState({}),[g,m]=F.useState(Mp),v=F.useRef(l);F.useEffect(()=>{v.current=l},[l]);const y=F.useRef(new Set),w=F.useRef(new Set);F.useEffect(()=>{const D=r.filter(N=>!y.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=>y.current.add(N.key)),P.forEach(N=>w.current.add(N.public_key)),(async()=>{const N=[...D.map(I=>({type:"CHAN",conversation_key:I.key})),...P.map(I=>({type:"PRIV",conversation_key:I.public_key}))];if(N.length!==0)try{const I=await ot.getMessagesBulk(N,A0),z={},H={},j={};for(const ee of D){const Z=I[`CHAN:${ee.key}`]||[];if(Z.length>0){const V=Rr("channel",ee.key),ne=ee.last_read_at||0,J=Z.filter(he=>!he.outgoing&&he.received_at>ne);J.length>0&&(z[V]=J.length,J.some(he=>Ip(he.text,v.current))&&(H[V]=!0));const ie=Math.max(...Z.map(he=>he.received_at));j[V]=ie,Lf(V,ie)}}for(const ee of P){const Z=I[`PRIV:${ee.public_key}`]||[];if(Z.length>0){const V=Rr("contact",ee.public_key),ne=ee.last_read_at||0,J=Z.filter(he=>!he.outgoing&&he.received_at>ne);J.length>0&&(z[V]=J.length,J.some(he=>Ip(he.text,v.current))&&(H[V]=!0));const ie=Math.max(...Z.map(he=>he.received_at));j[V]=ie,Lf(V,ie)}}Object.keys(z).length>0&&f(ee=>({...ee,...z})),Object.keys(H).length>0&&h(ee=>({...ee,...H})),m(Mp())}catch(I){console.error("Failed to fetch messages bulk:",I)}})()},[r,a]),F.useEffect(()=>{if(o&&o.type!=="raw"&&o.type!=="map"){const D=Rr(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"?ot.markChannelRead(o.id).catch(P=>{console.error("Failed to mark channel as read on server:",P)}):o.type==="contact"&&ot.markContactRead(o.id).catch(P=>{console.error("Failed to mark contact as read on server:",P)})}},[o]);const B=F.useCallback((D,P)=>{f(S=>({...S,[D]:(S[D]||0)+1})),P&&h(S=>({...S,[D]:!0}))},[]),T=F.useCallback(()=>{f({}),h({}),ot.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=Rr(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"?ot.markChannelRead(D.id).catch(S=>{console.error("Failed to mark channel as read on server:",S)}):D.type==="contact"&&ot.markContactRead(D.id).catch(S=>{console.error("Failed to mark contact as read on server:",S)})},[]),k=F.useCallback(D=>{let P=null;if(D.type==="CHAN"&&D.conversation_key?P=Rr("channel",D.conversation_key):D.type==="PRIV"&&D.conversation_key&&(P=Rr("contact",D.conversation_key)),P){const S=D.received_at||Math.floor(Date.now()/1e3),N=Lf(P,S);m(N)}},[]);return{unreadCounts:d,mentions:p,lastMessageTimes:g,incrementUnread:B,markAllRead:T,markConversationRead:C,trackNewMessage:k}}var v0=Ig();const jg=Xs(v0);function ty(r){if(typeof document>"u")return;let a=document.head||document.getEle
Line truncated
|
||||
`)}function jo(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,paths:null,txt_type:0,signature:null,outgoing:o,acked:1}}function Yv(r,a,o){const[l,d]=F.useState(!1),{handleAirtimeCommand:f,stopTracking:p}=Gv(o);F.useEffect(()=>{d(!1),p()},[r==null?void 0:r.id,p]);const h=F.useMemo(()=>{if(!r||r.type!=="contact")return!1;const v=a.find(y=>y.public_key===r.id);return(v==null?void 0:v.type)===Oc},[r,a]),g=F.useCallback(async v=>{if(!(!r||r.type!=="contact")&&h)try{const y=await rt.requestTelemetry(r.id,v),w=jo(r.id,Kv(y),!1,0),B=jo(r.id,Qv(y.neighbors),!1,1),T=jo(r.id,$v(y.acl),!1,2);o(C=>[...C,w,B,T]),d(!0)}catch(y){const w=jo(r.id,`Telemetry request failed: ${y instanceof Error?y.message:"Unknown error"}`,!1,0);o(B=>[...B,w])}},[r,h,o]),m=F.useCallback(async v=>{if(!r||r.type!=="contact"||!h||!l||await f(v,r.id))return;const w=jo(r.id,`> ${v}`,!0,0);o(B=>[...B,w]);try{const B=await rt.sendRepeaterCommand(r.id,v),T=jo(r.id,B.response,!1,1);B.sender_timestamp&&(T.sender_timestamp=B.sender_timestamp),o(C=>[...C,T])}catch(B){const T=jo(r.id,`Command failed: ${B instanceof Error?B.message:"Unknown error"}`,!1,1);o(C=>[...C,T])}},[r,h,l,o,f]);return{repeaterLoggedIn:l,activeContactIsRepeater:h,handleTelemetryRequest:g,handleRepeaterCommand:m}}const Xv=12;function Ks(r){return r.slice(0,Xv)}function x0(r,a){return!r||!a?!1:Ks(r)===Ks(a)}function Ei(r,a){return r||Ks(a)}const Xf="remoteterm-lastMessageTime";function Og(r){try{const a=localStorage.getItem(r);return a?JSON.parse(a):{}}catch{return{}}}function Jv(r,a){try{localStorage.setItem(r,JSON.stringify(a))}catch{}}function Mp(){return Og(Xf)}function Lf(r,a){const o=Og(Xf);return(!o[r]||a>o[r])&&(o[r]=a,Jv(Xf,o)),o}function Rr(r,a){return r==="channel"?`channel-${a}`:`contact-${Ks(a)}`}function Ip(r,a){if(!a)return!1;const o=a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`@\\[${o}\\]`,"i").test(r)}function ey(r,a,o,l=null){const[d,f]=F.useState({}),[p,h]=F.useState({}),[g,m]=F.useState(Mp),v=F.useRef(l);F.useEffect(()=>{v.current=l},[l]);const y=F.useRef(new Set),w=F.useRef(new Set);F.useEffect(()=>{const D=r.filter(N=>!y.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=>y.current.add(N.key)),P.forEach(N=>w.current.add(N.public_key)),(async()=>{const N=[...D.map(I=>({type:"CHAN",conversation_key:I.key})),...P.map(I=>({type:"PRIV",conversation_key:I.public_key}))];if(N.length!==0)try{const I=await rt.getMessagesBulk(N,A0),z={},H={},j={};for(const ee of D){const Z=I[`CHAN:${ee.key}`]||[];if(Z.length>0){const V=Rr("channel",ee.key),ne=ee.last_read_at||0,J=Z.filter(he=>!he.outgoing&&he.received_at>ne);J.length>0&&(z[V]=J.length,J.some(he=>Ip(he.text,v.current))&&(H[V]=!0));const ie=Math.max(...Z.map(he=>he.received_at));j[V]=ie,Lf(V,ie)}}for(const ee of P){const Z=I[`PRIV:${ee.public_key}`]||[];if(Z.length>0){const V=Rr("contact",ee.public_key),ne=ee.last_read_at||0,J=Z.filter(he=>!he.outgoing&&he.received_at>ne);J.length>0&&(z[V]=J.length,J.some(he=>Ip(he.text,v.current))&&(H[V]=!0));const ie=Math.max(...Z.map(he=>he.received_at));j[V]=ie,Lf(V,ie)}}Object.keys(z).length>0&&f(ee=>({...ee,...z})),Object.keys(H).length>0&&h(ee=>({...ee,...H})),m(Mp())}catch(I){console.error("Failed to fetch messages bulk:",I)}})()},[r,a]),F.useEffect(()=>{if(o&&o.type!=="raw"&&o.type!=="map"){const D=Rr(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"?rt.markChannelRead(o.id).catch(P=>{console.error("Failed to mark channel as read on server:",P)}):o.type==="contact"&&rt.markContactRead(o.id).catch(P=>{console.error("Failed to mark contact as read on server:",P)})}},[o]);const B=F.useCallback((D,P)=>{f(S=>({...S,[D]:(S[D]||0)+1})),P&&h(S=>({...S,[D]:!0}))},[]),T=F.useCallback(()=>{f({}),h({}),rt.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=Rr(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"?rt.markChannelRead(D.id).catch(S=>{console.error("Failed to mark channel as read on server:",S)}):D.type==="contact"&&rt.markContactRead(D.id).catch(S=>{console.error("Failed to mark contact as read on server:",S)})},[]),k=F.useCallback(D=>{let P=null;if(D.type==="CHAN"&&D.conversation_key?P=Rr("channel",D.conversation_key):D.type==="PRIV"&&D.conversation_key&&(P=Rr("contact",D.conversation_key)),P){const S=D.received_at||Math.floor(Date.now()/1e3),N=Lf(P,S);m(N)}},[]);return{unreadCounts:d,mentions:p,lastMessageTimes:g,incrementUnread:B,markAllRead:T,markConversationRead:C,trackNewMessage:k}}var v0=Ig();const jg=Xs(v0);function ty(r){if(typeof document>"u")return;let a=document.head||document.getEle
Line truncated
|
||||
* @license lucide-react v0.562.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
@@ -78,7 +78,7 @@ No ACL entries`;const a=[`ACL (${r.length})`];for(const o of r){const l=o.name||
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const jy=[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]],Hg=y0("x",jy);function zy({health:r,config:a,onSettingsClick:o,onMenuClick:l}){const d=(r==null?void 0:r.radio_connected)??!1,[f,p]=F.useState(!1),h=async()=>{p(!0);try{const g=await ot.reconnectRadio();g.connected&&bt.success("Reconnected",{description:g.message})}catch(g){bt.error("Reconnection failed",{description:g instanceof Error?g.message:"Check radio connection and power"})}finally{p(!1)}};return b.jsxs("div",{className:"flex items-center gap-4 px-4 py-2 bg-[#252525] border-b border-[#333] text-xs",children:[l&&b.jsx("button",{onClick:l,className:"md:hidden p-1 bg-transparent border-none text-[#e0e0e0] cursor-pointer","aria-label":"Open menu",children:b.jsx(Oy,{className:"h-5 w-5"})}),b.jsx("h1",{className:"hidden lg:block text-base font-semibold mr-auto",children:"RemoteTerm"}),b.jsxs("div",{className:"flex items-center gap-1 text-[#888]",children:[b.jsx("div",{className:`w-2 h-2 rounded-full ${d?"bg-[#4caf50]":"bg-[#666]"}`}),b.jsx("span",{className:"hidden lg:inline text-[#e0e0e0]",children:d?"Connected":"Disconnected"})]}),a&&b.jsx("div",{className:"hidden lg:flex items-center gap-1 text-[#888]",children:b.jsx("span",{className:"text-[#e0e0e0]",children:a.name||"Unnamed"})}),b.jsx("div",{className:"flex-1 lg:hidden"}),!d&&b.jsx("button",{onClick:h,disabled:f,className:"px-3 py-1 bg-[#4a3000] border border-[#6b4500] text-[#ffa500] rounded text-xs cursor-pointer hover:bg-[#5a3a00] disabled:opacity-50 disabled:cursor-not-allowed",children:f?"Reconnecting...":"Reconnect"}),b.jsxs("button",{onClick:o,className:"px-3 py-1 bg-[#333] border border-[#444] text-[#e0e0e0] rounded text-xs cursor-pointer hover:bg-[#444]",children:[b.jsx("span",{role:"img","aria-label":"Settings",children:"🔧"})," ","Radio & Config"]})]})}const e0=2,Hy={text:"🛜",background:"#444444",textColor:"#ffffff"};function Vy(r){let a=0;for(let o=0;o<r.length;o++){const l=r.charCodeAt(o);a=(a<<5)-a+l,a=a&a}return Math.abs(a)}const Uy=/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]/u;function Zy(r,a){if(!r)return a.slice(0,2).toUpperCase();const o=r.match(Uy);if(o)return o[0];const l=r.match(/[a-zA-Z]/g);if(!l||l.length===0)return a.slice(0,2).toUpperCase();const d=r.indexOf(" ");if(d!==-1){const f=l[0],p=r.slice(d+1).match(/[a-zA-Z]/);if(p)return(f+p[0]).toUpperCase()}return l[0].toUpperCase()}function Wy(r){const a=Vy(r),o=a%360,l=50+(a>>8)%30,d=35+(a>>16)%20,f=`hsl(${o}, ${l}%, ${d}%)`,p=d<45?"#ffffff":"#000000";return{background:f,text:p}}function qy(r,a,o){if(o===e0)return Hy;const l=Zy(r,a),d=Wy(a);return{text:l,background:d.background,textColor:d.text}}function t0({name:r,publicKey:a,size:o=28,contactType:l}){const d=qy(r,a,l);return b.jsx("div",{className:"flex items-center justify-center rounded-full font-semibold flex-shrink-0 select-none",style:{backgroundColor:d.background,color:d.textColor,width:o,height:o,fontSize:o*.45},children:d.text})}const Vg="remoteterm-favorites";function jc(){try{const r=localStorage.getItem(Vg);return r?JSON.parse(r):[]}catch{return[]}}function Ug(r){try{localStorage.setItem(Vg,JSON.stringify(r))}catch{}}function Gy(r,a){const o=jc();if(o.some(d=>d.type===r&&d.id===a))return o;const l=[...o,{type:r,id:a}];return Ug(l),l}function Ky(r,a){const l=jc().filter(d=>!(d.type===r&&d.id===a));return Ug(l),l}function Zs(r,a,o){return r.some(l=>l.type===a&&l.id===o)}function Qy(r,a){return jc().some(l=>l.type===r&&l.id===a)?Ky(r,a):Gy(r,a)}function Zg(r){var a,o,l="";if(typeof r=="string"||typeof r=="number")l+=r;else if(typeof r=="object")if(Array.isArray(r)){var d=r.length;for(a=0;a<d;a++)r[a]&&(o=Zg(r[a]))&&(l&&(l+=" "),l+=o)}else for(o in r)r[o]&&(l&&(l+=" "),l+=o);return l}function Wg(){for(var r,a,o=0,l="",d=arguments.length;o<d;o++)(r=arguments[o])&&(a=Zg(r))&&(l&&(l+=" "),l+=a);return l}const $y=(r,a)=>{const o=new Array(r.length+a.length);for(let l=0;l<r.length;l++)o[l]=r[l];for(let l=0;l<a.length;l++)o[r.length+l]=a[l];return o},Yy=(r,a)=>({classGroupId:r,validator:a}),qg=(r=new Map,a=null,o)=>({nextPart:r,validators:a,classGroupId:o}),Dc="-",Hp=[],Xy="arbitrary..",Jy=r=>{const a=t_(r),{conflictingClassGroups:o,conflictingClassGroupModifiers:l}=r;return{getClassGroupId:p=>{if(p.startsWith("[")&&p.endsWith("]"))return e_(p);const h=p.split(Dc),g=h[0]===""&&h.length>1?1:0;return Gg(h,g,a)},getConflictingClassGroupIds:(p,h)=>{if(h){const g=l[p],m=o[p];return g?m?$y(m,g):g:m||Hp}return o[p]||Hp}}},Gg=(r,a,o)=>{if(r.length-a===0)return o.classGroupId;const d=r[a],f=o.nextPart.get(d);if(f){const m=Gg(r,a+1,f);if(m)return m}const p=o.validators;if(p===null)return;const h=a===0?r.join(Dc):r.slice(a).join(Dc),g=p.length;for(let m=0;m<g;m++){const v=p[m];if(v.validator(h))return v.classGroupId}},e_=r=>r.slice(1,-1).indexOf(":")===-1?void 0:(()=>{const a=r.slice(1,-1),o=a.indexOf(":"),l=a.slice(0,o);return l?Xy+l:void 0})(),t_=r=>{conLine truncated
|
||||
*/const jy=[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]],Hg=y0("x",jy);function zy({health:r,config:a,onSettingsClick:o,onMenuClick:l}){const d=(r==null?void 0:r.radio_connected)??!1,[f,p]=F.useState(!1),h=async()=>{p(!0);try{const g=await rt.reconnectRadio();g.connected&&bt.success("Reconnected",{description:g.message})}catch(g){bt.error("Reconnection failed",{description:g instanceof Error?g.message:"Check radio connection and power"})}finally{p(!1)}};return b.jsxs("div",{className:"flex items-center gap-4 px-4 py-2 bg-[#252525] border-b border-[#333] text-xs",children:[l&&b.jsx("button",{onClick:l,className:"md:hidden p-1 bg-transparent border-none text-[#e0e0e0] cursor-pointer","aria-label":"Open menu",children:b.jsx(Oy,{className:"h-5 w-5"})}),b.jsx("h1",{className:"hidden lg:block text-base font-semibold mr-auto",children:"RemoteTerm"}),b.jsxs("div",{className:"flex items-center gap-1 text-[#888]",children:[b.jsx("div",{className:`w-2 h-2 rounded-full ${d?"bg-[#4caf50]":"bg-[#666]"}`}),b.jsx("span",{className:"hidden lg:inline text-[#e0e0e0]",children:d?"Connected":"Disconnected"})]}),a&&b.jsx("div",{className:"hidden lg:flex items-center gap-1 text-[#888]",children:b.jsx("span",{className:"text-[#e0e0e0]",children:a.name||"Unnamed"})}),b.jsx("div",{className:"flex-1 lg:hidden"}),!d&&b.jsx("button",{onClick:h,disabled:f,className:"px-3 py-1 bg-[#4a3000] border border-[#6b4500] text-[#ffa500] rounded text-xs cursor-pointer hover:bg-[#5a3a00] disabled:opacity-50 disabled:cursor-not-allowed",children:f?"Reconnecting...":"Reconnect"}),b.jsxs("button",{onClick:o,className:"px-3 py-1 bg-[#333] border border-[#444] text-[#e0e0e0] rounded text-xs cursor-pointer hover:bg-[#444]",children:[b.jsx("span",{role:"img","aria-label":"Settings",children:"🔧"})," ","Radio & Config"]})]})}const e0=2,Hy={text:"🛜",background:"#444444",textColor:"#ffffff"};function Vy(r){let a=0;for(let o=0;o<r.length;o++){const l=r.charCodeAt(o);a=(a<<5)-a+l,a=a&a}return Math.abs(a)}const Uy=/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]/u;function Zy(r,a){if(!r)return a.slice(0,2).toUpperCase();const o=r.match(Uy);if(o)return o[0];const l=r.match(/[a-zA-Z]/g);if(!l||l.length===0)return a.slice(0,2).toUpperCase();const d=r.indexOf(" ");if(d!==-1){const f=l[0],p=r.slice(d+1).match(/[a-zA-Z]/);if(p)return(f+p[0]).toUpperCase()}return l[0].toUpperCase()}function Wy(r){const a=Vy(r),o=a%360,l=50+(a>>8)%30,d=35+(a>>16)%20,f=`hsl(${o}, ${l}%, ${d}%)`,p=d<45?"#ffffff":"#000000";return{background:f,text:p}}function qy(r,a,o){if(o===e0)return Hy;const l=Zy(r,a),d=Wy(a);return{text:l,background:d.background,textColor:d.text}}function t0({name:r,publicKey:a,size:o=28,contactType:l}){const d=qy(r,a,l);return b.jsx("div",{className:"flex items-center justify-center rounded-full font-semibold flex-shrink-0 select-none",style:{backgroundColor:d.background,color:d.textColor,width:o,height:o,fontSize:o*.45},children:d.text})}const Vg="remoteterm-favorites";function jc(){try{const r=localStorage.getItem(Vg);return r?JSON.parse(r):[]}catch{return[]}}function Ug(r){try{localStorage.setItem(Vg,JSON.stringify(r))}catch{}}function Gy(r,a){const o=jc();if(o.some(d=>d.type===r&&d.id===a))return o;const l=[...o,{type:r,id:a}];return Ug(l),l}function Ky(r,a){const l=jc().filter(d=>!(d.type===r&&d.id===a));return Ug(l),l}function Zs(r,a,o){return r.some(l=>l.type===a&&l.id===o)}function Qy(r,a){return jc().some(l=>l.type===r&&l.id===a)?Ky(r,a):Gy(r,a)}function Zg(r){var a,o,l="";if(typeof r=="string"||typeof r=="number")l+=r;else if(typeof r=="object")if(Array.isArray(r)){var d=r.length;for(a=0;a<d;a++)r[a]&&(o=Zg(r[a]))&&(l&&(l+=" "),l+=o)}else for(o in r)r[o]&&(l&&(l+=" "),l+=o);return l}function Wg(){for(var r,a,o=0,l="",d=arguments.length;o<d;o++)(r=arguments[o])&&(a=Zg(r))&&(l&&(l+=" "),l+=a);return l}const $y=(r,a)=>{const o=new Array(r.length+a.length);for(let l=0;l<r.length;l++)o[l]=r[l];for(let l=0;l<a.length;l++)o[r.length+l]=a[l];return o},Yy=(r,a)=>({classGroupId:r,validator:a}),qg=(r=new Map,a=null,o)=>({nextPart:r,validators:a,classGroupId:o}),Dc="-",Hp=[],Xy="arbitrary..",Jy=r=>{const a=t_(r),{conflictingClassGroups:o,conflictingClassGroupModifiers:l}=r;return{getClassGroupId:p=>{if(p.startsWith("[")&&p.endsWith("]"))return e_(p);const h=p.split(Dc),g=h[0]===""&&h.length>1?1:0;return Gg(h,g,a)},getConflictingClassGroupIds:(p,h)=>{if(h){const g=l[p],m=o[p];return g?m?$y(m,g):g:m||Hp}return o[p]||Hp}}},Gg=(r,a,o)=>{if(r.length-a===0)return o.classGroupId;const d=r[a],f=o.nextPart.get(d);if(f){const m=Gg(r,a+1,f);if(m)return m}const p=o.validators;if(p===null)return;const h=a===0?r.join(Dc):r.slice(a).join(Dc),g=p.length;for(let m=0;m<g;m++){const v=p[m];if(v.validator(h))return v.classGroupId}},e_=r=>r.slice(1,-1).indexOf(":")===-1?void 0:(()=>{const a=r.slice(1,-1),o=a.indexOf(":"),l=a.slice(0,o);return l?Xy+l:void 0})(),t_=r=>{conLine truncated
|
||||
.`.concat(j1,` {
|
||||
overflow: hidden `).concat(l,`;
|
||||
padding-right: `).concat(h,"px ").concat(l,`;
|
||||
@@ -123,10 +123,10 @@ No ACL entries`;const a=[`ACL (${r.length})`];for(const o of r){const l=o.name||
|
||||
If you want to hide the \`${a.titleName}\`, you can wrap it with our VisuallyHidden component.
|
||||
|
||||
For more information, see https://radix-ui.com/primitives/docs/components/${a.docsSlug}`;return F.useEffect(()=>{r&&(document.getElementById(r)||console.error(o))},[o,r]),null},Ow="DialogDescriptionWarning",jw=({contentRef:r,descriptionId:a})=>{const l=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${NA(Ow).contentName}}.`;return F.useEffect(()=>{var f;const d=(f=r.current)==null?void 0:f.getAttribute("aria-describedby");a&&d&&(document.getElementById(a)||console.warn(l))},[l,r,a]),null},LA=_A,MA=CA,Uc=EA,Zc=kA,Wc=BA,qc=TA,IA=RA;const S0=LA,zw=MA,OA=F.forwardRef(({className:r,...a},o)=>b.jsx(Uc,{ref:o,className:at("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",r),...a}));OA.displayName=Uc.displayName;const Gc=F.forwardRef(({className:r,children:a,...o},l)=>b.jsxs(zw,{children:[b.jsx(OA,{}),b.jsxs(Zc,{ref:l,className:at("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",r),...o,children:[a,b.jsxs(IA,{className:"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",children:[b.jsx(Hg,{className:"h-4 w-4"}),b.jsx("span",{className:"sr-only",children:"Close"})]})]})]}));Gc.displayName=Zc.displayName;const Kc=({className:r,...a})=>b.jsx("div",{className:at("flex flex-col space-y-1.5 text-center sm:text-left",r),...a});Kc.displayName="DialogHeader";const B0=({className:r,...a})=>b.jsx("div",{className:at("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",r),...a});B0.displayName="DialogFooter";const Qc=F.forwardRef(({className:r,...a},o)=>b.jsx(Wc,{ref:o,className:at("text-lg font-semibold leading-none tracking-tight",r),...a}));Qc.displayName=Wc.displayName;const $c=F.forwardRef(({className:r,...a},o)=>b.jsx(qc,{ref:o,className:at("text-sm text-muted-foreground",r),...a}));$c.displayName=qc.displayName;function Hw(){const r=window.location.hash.slice(1);if(!r)return null;if(r==="raw")return{type:"raw",name:"raw"};if(r==="map")return{type:"map",name:"map"};if(r.startsWith("map/focus/")){const d=r.slice(10);return d?{type:"map",name:"map",mapFocusKey:decodeURIComponent(d)}:{type:"map",name:"map"}}const a=r.indexOf("/");if(a===-1)return null;const o=r.slice(0,a),l=decodeURIComponent(r.slice(a+1));return(o==="channel"||o==="contact")&&l?{type:o,name:l}:null}function jA(r){return`#map/focus/${encodeURIComponent(r)}`}function Vw(r){if(!r)return"";if(r.type==="raw")return"#raw";if(r.type==="map")return"#map";const a=r.type==="channel"&&r.name.startsWith("#")?r.name.slice(1):r.name;return`#${r.type}/${encodeURIComponent(a)}`}function Uw(r){const a=Vw(r);a!==window.location.hash&&window.history.replaceState(null,"",a||window.location.pathname)}function Zw({open:r,onClose:a,paths:o,senderInfo:l,contacts:d,config:f}){const p=o.map(g=>({...g,resolved:t1(g.path,l,d,f)})),h=o.length===1;return b.jsx(S0,{open:r,onOpenChange:g=>!g&&a(),children:b.jsxs(Gc,{className:"max-w-md max-h-[80vh] flex flex-col",children:[b.jsxs(Kc,{children:[b.jsxs(Qc,{children:["Message Path",!h&&`s (${o.length})`]}),b.jsx($c,{children:h?b.jsxs(b.Fragment,{children:["This shows ",b.jsx("em",{children:"one route"})," that this message traveled through the mesh network. Routers may be incorrectly identified due to prefix collisions between heard and non-heard router advertisements."]}):b.jsxs(b.Fragment,{children:["This message was received via ",b.jsxs("strong",{children:[o.length," different routes"]}),". Routers may be incorrectly identified due to prefix collisions."]})})]}),b.jsxs("div",{className:"flex-1 overflow-y-auto py-2 space-y-4",children:[p.map((g,m)=>b.jsxs("div",{children:[!h&&b.jsxs("div",{className:"text-xs text-muted-foreground font-medium mb-2 pb-1 border-b border-border",children:["Path ",m+1," — received ",Qs(g.received_at)]}),b.jsx(Ww,{resolved:g.resolved,senderInfo:l,hideStraightLine:!h})]},m)),!h&&p.length>0&&(()=>{const g=p[0].resolved;return Ct(g.sender.lat,g.sender.lon)&&Ct(g.receiver.lat,g.receiver.lon)?b.jsxs("div",{className:"pt-3 mt-1 border-t border-border",children:[b.jsx("span",{className:"text-sm text-muted-foreground",children:"Straight-line distance: "}),b.jsx("span",{className:"text-sm font-medium",children:Go(Fr(g.sender.lat,g.sender.lon,g.receiver.lat,g.receiver.lon))})]}):null})()]}),b.jsx(B0,{children:b
Line truncated
|
||||
`).map(($,de,fe)=>b.jsxs("span",{children:[Kw($,h),de<fe.length-1&&b.jsx("br",{})]},de)),!Y&&b.jsxs(b.Fragment,{children:[b.jsx("span",{className:"text-[10px] text-muted-foreground/50 ml-2",children:Qs(j.sender_timestamp||j.received_at)}),!j.outgoing&&j.paths&&j.paths.length>0&&b.jsx(um,{paths:j.paths,variant:"inline",onClick:()=>C({paths:j.paths,senderInfo:I(j,Z,ne)})})]}),j.outgoing&&(j.acked>0?` ✓${j.acked>1?j.acked:""}`:" ?")]})]})]},j.id)})]}),w&&b.jsx("button",{onClick:P,className:"absolute bottom-4 right-4 w-10 h-10 rounded-full bg-muted hover:bg-accent border border-border flex items-center justify-center shadow-lg transition-opacity",title:"Scroll to bottom",children:b.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"text-muted-foreground",children:b.jsx("polyline",{points:"6 9 12 15 18 9"})})}),T&&b.jsx(Zw,{open:!0,onClose:()=>C(null),paths:T.paths,senderInfo:T.senderInfo,contacts:a,config:g??null})]})}const cm=156,$w=140,Yw=156,Xw=120,Jw=8,e2=F.forwardRef(function({onSend:a,disabled:o,placeholder:l,isRepeaterMode:d,conversationType:f,senderName:p},h){const[g,m]=F.useState(""),[v,y]=F.useState(!1),w=F.useRef(null);F.useImperativeHandle(h,()=>({appendText:I=>{var z;m(H=>H+I),(z=w.current)==null||z.focus()}}));const B=F.useMemo(()=>{if(f==="contact")return{warningAt:$w,dangerAt:cm,hardLimit:cm};if(f==="channel"){const I=(p==null?void 0:p.length)??10,z=Math.max(1,Yw-I-2);return{warningAt:Xw,dangerAt:Math.max(1,z-Jw),hardLimit:z}}return null},[f,p]),{limitState:T,warningMessage:C}=F.useMemo(()=>{if(!B)return{limitState:"normal",warningMessage:null};const I=g.length;return I>=B.hardLimit?{limitState:"error",warningMessage:"likely truncated by radio"}:I>=B.dangerAt?{limitState:"danger",warningMessage:"may impact multi-repeater hop delivery"}:I>=B.warningAt?{limitState:"warning",warningMessage:"may impact multi-repeater hop delivery"}:{limitState:"normal",warningMessage:null}},[g.length,B]),k=B?B.hardLimit-g.length:0,D=F.useCallback(async I=>{I.preventDefault();const z=g.trim();if(d){if(v||o)return;y(!0);try{await a(z),m("")}catch(H){console.error("Failed to request telemetry:",H),bt.error("Failed to request telemetry",{description:H instanceof Error?H.message:"Check radio connection"});return}finally{y(!1)}setTimeout(()=>{var H;return(H=w.current)==null?void 0:H.focus()},0)}else{if(!z||v||o)return;y(!0);try{await a(z),m("")}catch(H){console.error("Failed to send message:",H),bt.error("Failed to send message",{description:H instanceof Error?H.message:"Check radio connection"});return}finally{y(!1)}setTimeout(()=>{var H;return(H=w.current)==null?void 0:H.focus()},0)}},[g,v,o,a,d]),P=F.useCallback(I=>{I.key==="Enter"&&!I.shiftKey&&(I.preventDefault(),D(I))},[D]),S=d?!0:g.trim().length>0,N=!d&&B!==null;return b.jsxs("form",{className:"px-4 py-3 border-t border-border flex flex-col gap-1",onSubmit:D,children:[b.jsxs("div",{className:"flex gap-2",children:[b.jsx(Xt,{ref:w,type:d?"password":"text",autoComplete:d?"off":void 0,value:g,onChange:I=>m(I.target.value),onKeyDown:P,placeholder:l||(d?"Enter password for admin login...":"Type a message..."),disabled:o||v,className:"flex-1 min-w-0"}),b.jsx(Pn,{type:"submit",disabled:o||v||!S,className:"flex-shrink-0",children:v?d?"Logging in...":"Sending...":d?g.trim()?"Log in with password":"Log in as guest/use repeater ACLs":"Send"})]}),N&&b.jsxs("div",{className:"flex items-center justify-end gap-2 text-xs",children:[b.jsxs("span",{className:at("tabular-nums",T==="error"||T==="danger"?"text-red-500 font-medium":T==="warning"?"text-yellow-500":"text-muted-foreground"),children:[g.length,"/",B.hardLimit,k<0&&` (${k})`]}),C&&b.jsxs("span",{className:at(T==="error"?"text-red-500":"text-yellow-500"),children:["— ",C]})]})]})});function dm(r){const a=t2(r),o=F.forwardRef((l,d)=>{const{children:f,...p}=l,h=F.Children.toArray(f),g=h.find(r2);if(g){const m=g.props.children,v=h.map(y=>y===g?F.Children.count(m)>1?F.Children.only(null):F.isValidElement(m)?m.props.children:null:y);return b.jsx(a,{...p,ref:d,children:F.isValidElement(m)?F.cloneElement(m,void 0,v):null})}return b.jsx(a,{...p,ref:d,children:f})});return o.displayName=`${r}.Slot`,o}function t2(r){const a=F.forwardRef((o,l)=>{const{children:d,...f}=o;if(F.isValidElement(d)){const p=o2(d),h=i2(f,d.props);return d.type!==F.Fragment&&(h.ref=l?fl(l,p):p),F.cloneElement(d,h)}return F.Children.count(d)>1?F.Children.only(null):null});return a.displayName=`${r}.SlotClone`,a}var n2=Symbol("radix.slottable");function r2(r){return F.isValidElement(r)&&typeof r.type=="function"&&"__radixId"in r.type&&r.type.__radixId===n2}function i2(r,a){const o={...a};for(const l in a){const d=r[l],f=a[l];/^on[A-Z]/.test(l)?d&&f?o[l]=(...h)=>{const g=f(...h);return d(...h),g}:d&&(o[l]=d):l==="style"?o[l]={...d,...f}:l==="className"&&(o[l]=[d,f].filter(Boolean).join(" "))}return{...r,...o}}function
Line truncated
|
||||
`).map(($,de,fe)=>b.jsxs("span",{children:[Kw($,h),de<fe.length-1&&b.jsx("br",{})]},de)),!Y&&b.jsxs(b.Fragment,{children:[b.jsx("span",{className:"text-[10px] text-muted-foreground/50 ml-2",children:Qs(j.sender_timestamp||j.received_at)}),!j.outgoing&&j.paths&&j.paths.length>0&&b.jsx(um,{paths:j.paths,variant:"inline",onClick:()=>C({paths:j.paths,senderInfo:I(j,Z,ne)})})]}),j.outgoing&&(j.acked>0?` ✓${j.acked>1?j.acked:""}`:" ?")]})]})]},j.id)})]}),w&&b.jsx("button",{onClick:P,className:"absolute bottom-4 right-4 w-10 h-10 rounded-full bg-muted hover:bg-accent border border-border flex items-center justify-center shadow-lg transition-opacity",title:"Scroll to bottom",children:b.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"text-muted-foreground",children:b.jsx("polyline",{points:"6 9 12 15 18 9"})})}),T&&b.jsx(Zw,{open:!0,onClose:()=>C(null),paths:T.paths,senderInfo:T.senderInfo,contacts:a,config:g??null})]})}const cm=156,$w=140,Yw=156,Xw=120,Jw=8,e2=F.forwardRef(function({onSend:a,disabled:o,placeholder:l,isRepeaterMode:d,conversationType:f,senderName:p},h){const[g,m]=F.useState(""),[v,y]=F.useState(!1),w=F.useRef(null);F.useImperativeHandle(h,()=>({appendText:I=>{var z;m(H=>H+I),(z=w.current)==null||z.focus()}}));const B=F.useMemo(()=>{if(f==="contact")return{warningAt:$w,dangerAt:cm,hardLimit:cm};if(f==="channel"){const I=(p==null?void 0:p.length)??10,z=Math.max(1,Yw-I-2);return{warningAt:Xw,dangerAt:Math.max(1,z-Jw),hardLimit:z}}return null},[f,p]),{limitState:T,warningMessage:C}=F.useMemo(()=>{if(!B)return{limitState:"normal",warningMessage:null};const I=g.length;return I>=B.hardLimit?{limitState:"error",warningMessage:"likely truncated by radio"}:I>=B.dangerAt?{limitState:"danger",warningMessage:"may impact multi-repeater hop delivery"}:I>=B.warningAt?{limitState:"warning",warningMessage:"may impact multi-repeater hop delivery"}:{limitState:"normal",warningMessage:null}},[g.length,B]),k=B?B.hardLimit-g.length:0,D=F.useCallback(async I=>{I.preventDefault();const z=g.trim();if(d){if(v||o)return;y(!0);try{await a(z),m("")}catch(H){console.error("Failed to request telemetry:",H),bt.error("Failed to request telemetry",{description:H instanceof Error?H.message:"Check radio connection"});return}finally{y(!1)}setTimeout(()=>{var H;return(H=w.current)==null?void 0:H.focus()},0)}else{if(!z||v||o)return;y(!0);try{await a(z),m("")}catch(H){console.error("Failed to send message:",H),bt.error("Failed to send message",{description:H instanceof Error?H.message:"Check radio connection"});return}finally{y(!1)}setTimeout(()=>{var H;return(H=w.current)==null?void 0:H.focus()},0)}},[g,v,o,a,d]),P=F.useCallback(I=>{I.key==="Enter"&&!I.shiftKey&&(I.preventDefault(),D(I))},[D]),S=d?!0:g.trim().length>0,N=!d&&B!==null;return b.jsxs("form",{className:"px-4 py-3 border-t border-border flex flex-col gap-1",onSubmit:D,children:[b.jsxs("div",{className:"flex gap-2",children:[b.jsx(Xt,{ref:w,type:d?"password":"text",autoComplete:d?"off":void 0,value:g,onChange:I=>m(I.target.value),onKeyDown:P,placeholder:l||(d?"Enter password for admin login...":"Type a message..."),disabled:o||v,className:"flex-1 min-w-0"}),b.jsx(Pn,{type:"submit",disabled:o||v||!S,className:"flex-shrink-0",children:v?d?"Logging in...":"Sending...":d?g.trim()?"Log in with password":"Log in as guest/use repeater ACLs":"Send"})]}),N&&b.jsxs("div",{className:"flex items-center justify-end gap-2 text-xs",children:[b.jsxs("span",{className:at("tabular-nums",T==="error"||T==="danger"?"text-red-500 font-medium":T==="warning"?"text-yellow-500":"text-muted-foreground"),children:[g.length,"/",B.hardLimit,k<0&&` (${k})`]}),C&&b.jsxs("span",{className:at(T==="error"?"text-red-500":"text-yellow-500"),children:["— ",C]})]})]})});function dm(r){const a=t2(r),o=F.forwardRef((l,d)=>{const{children:f,...p}=l,h=F.Children.toArray(f),g=h.find(r2);if(g){const m=g.props.children,v=h.map(y=>y===g?F.Children.count(m)>1?F.Children.only(null):F.isValidElement(m)?m.props.children:null:y);return b.jsx(a,{...p,ref:d,children:F.isValidElement(m)?F.cloneElement(m,void 0,v):null})}return b.jsx(a,{...p,ref:d,children:f})});return o.displayName=`${r}.Slot`,o}function t2(r){const a=F.forwardRef((o,l)=>{const{children:d,...f}=o;if(F.isValidElement(d)){const p=o2(d),h=i2(f,d.props);return d.type!==F.Fragment&&(h.ref=l?fl(l,p):p),F.cloneElement(d,h)}return F.Children.count(d)>1?F.Children.only(null):null});return a.displayName=`${r}.SlotClone`,a}var n2=Symbol("radix.slottable");function r2(r){return F.isValidElement(r)&&typeof r.type=="function"&&"__radixId"in r.type&&r.type.__radixId===n2}function i2(r,a){const o={...a};for(const l in a){const d=r[l],f=a[l];/^on[A-Z]/.test(l)?d&&f?o[l]=(...h)=>{const g=f(...h);return d(...h),g}:d&&(o[l]=d):l==="style"?o[l]={...d,...f}:l==="className"&&(o[l]=[d,f].filter(Boolean).join(" "))}return{...r,...o}}function
Line truncated
|
||||
* Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com
|
||||
* (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade
|
||||
*/var X2=sl.exports,pm;function J2(){return pm||(pm=1,(function(r,a){(function(o,l){l(a)})(X2,(function(o){var l="1.9.4";function d(t){var i,u,A,E;for(u=1,A=arguments.length;u<A;u++){E=arguments[u];for(i in E)t[i]=E[i]}return t}var f=Object.create||(function(){function t(){}return function(i){return t.prototype=i,new t}})();function p(t,i){var u=Array.prototype.slice;if(t.bind)return t.bind.apply(t,u.call(arguments,1));var A=u.call(arguments,2);return function(){return t.apply(i,A.length?A.concat(u.call(arguments)):arguments)}}var h=0;function g(t){return"_leaflet_id"in t||(t._leaflet_id=++h),t._leaflet_id}function m(t,i,u){var A,E,O,Q;return Q=function(){A=!1,E&&(O.apply(u,E),E=!1)},O=function(){A?E=arguments:(t.apply(u,arguments),setTimeout(Q,i),A=!0)},O}function v(t,i,u){var A=i[1],E=i[0],O=A-E;return t===A&&u?t:((t-E)%O+O)%O+E}function y(){return!1}function w(t,i){if(i===!1)return t;var u=Math.pow(10,i===void 0?6:i);return Math.round(t*u)/u}function B(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}function T(t){return B(t).split(/\s+/)}function C(t,i){Object.prototype.hasOwnProperty.call(t,"options")||(t.options=t.options?f(t.options):{});for(var u in i)t.options[u]=i[u];return t.options}function k(t,i,u){var A=[];for(var E in t)A.push(encodeURIComponent(u?E.toUpperCase():E)+"="+encodeURIComponent(t[E]));return(!i||i.indexOf("?")===-1?"?":"&")+A.join("&")}var D=/\{ *([\w_ -]+) *\}/g;function P(t,i){return t.replace(D,function(u,A){var E=i[A];if(E===void 0)throw new Error("No value provided for variable "+u);return typeof E=="function"&&(E=E(i)),E})}var S=Array.isArray||function(t){return Object.prototype.toString.call(t)==="[object Array]"};function N(t,i){for(var u=0;u<t.length;u++)if(t[u]===i)return u;return-1}var I="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";function z(t){return window["webkit"+t]||window["moz"+t]||window["ms"+t]}var H=0;function j(t){var i=+new Date,u=Math.max(0,16-(i-H));return H=i+u,window.setTimeout(t,u)}var ee=window.requestAnimationFrame||z("RequestAnimationFrame")||j,Z=window.cancelAnimationFrame||z("CancelAnimationFrame")||z("CancelRequestAnimationFrame")||function(t){window.clearTimeout(t)};function V(t,i,u){if(u&&ee===j)t.call(i);else return ee.call(window,p(t,i))}function ne(t){t&&Z.call(window,t)}var J={__proto__:null,extend:d,create:f,bind:p,get lastId(){return h},stamp:g,throttle:m,wrapNum:v,falseFn:y,formatNum:w,trim:B,splitWords:T,setOptions:C,getParamString:k,template:P,isArray:S,indexOf:N,emptyImageUrl:I,requestFn:ee,cancelFn:Z,requestAnimFrame:V,cancelAnimFrame:ne};function ie(){}ie.extend=function(t){var i=function(){C(this),this.initialize&&this.initialize.apply(this,arguments),this.callInitHooks()},u=i.__super__=this.prototype,A=f(u);A.constructor=i,i.prototype=A;for(var E in this)Object.prototype.hasOwnProperty.call(this,E)&&E!=="prototype"&&E!=="__super__"&&(i[E]=this[E]);return t.statics&&d(i,t.statics),t.includes&&(he(t.includes),d.apply(null,[A].concat(t.includes))),d(A,t),delete A.statics,delete A.includes,A.options&&(A.options=u.options?f(u.options):{},d(A.options,t.options)),A._initHooks=[],A.callInitHooks=function(){if(!this._initHooksCalled){u.callInitHooks&&u.callInitHooks.call(this),this._initHooksCalled=!0;for(var O=0,Q=A._initHooks.length;O<Q;O++)A._initHooks[O].call(this)}},i},ie.include=function(t){var i=this.prototype.options;return d(this.prototype,t),t.options&&(this.prototype.options=i,this.mergeOptions(t.options)),this},ie.mergeOptions=function(t){return d(this.prototype.options,t),this},ie.addInitHook=function(t){var i=Array.prototype.slice.call(arguments,1),u=typeof t=="function"?t:function(){this[t].apply(this,i)};return this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(u),this};function he(t){if(!(typeof L>"u"||!L||!L.Mixin)){t=S(t)?t:[t];for(var i=0;i<t.length;i++)t[i]===L.Mixin.Events&&console.warn("Deprecated include of L.Mixin.Events: this property will be removed in future releases, please inherit from L.Evented instead.",new Error().stack)}}var ae={on:function(t,i,u){if(typeof t=="object")for(var A in t)this._on(A,t[A],i);else{t=T(t);for(var E=0,O=t.length;E<O;E++)this._on(t[E],i,u)}return this},off:function(t,i,u){if(!arguments.length)delete this._events;else if(typeof t=="object")for(var A in t)this._off(A,t[A],i);else{t=T(t);for(var E=arguments.length===1,O=0,Q=t.length;O<Q;O++)E?this._off(t[O]):this._off(t[O],i,u)}return this},_on:function(t,i,u,A){if(typeof i!="function"){console.warn("wrong listener type: "+typeof i);return}if(this._listens(t,i,u)===!1){u===this&&(u=void 0);var E={fn:i,ctx:u};A&&(E.once=!0),this._events=this._events||{},this._events[t]=this._events[t]||[],this._events[t].push(E)}},_off:function(t,i,u){var A,E,O;if(this._events&&(A=this._events[t],!!A)){if(arguments.length===1){if(this._firingCount)for(E=0,O=A.length;E<O;E++)A[E].fn=y;delete this._events[t];return}if(typeof i!="function"){console.warn("wrong listener type: "+typeof i);return}var Q=tLine truncated
|
||||
*/var X2=sl.exports,pm;function J2(){return pm||(pm=1,(function(r,a){(function(o,l){l(a)})(X2,(function(o){var l="1.9.4";function d(t){var i,u,A,E;for(u=1,A=arguments.length;u<A;u++){E=arguments[u];for(i in E)t[i]=E[i]}return t}var f=Object.create||(function(){function t(){}return function(i){return t.prototype=i,new t}})();function p(t,i){var u=Array.prototype.slice;if(t.bind)return t.bind.apply(t,u.call(arguments,1));var A=u.call(arguments,2);return function(){return t.apply(i,A.length?A.concat(u.call(arguments)):arguments)}}var h=0;function g(t){return"_leaflet_id"in t||(t._leaflet_id=++h),t._leaflet_id}function m(t,i,u){var A,E,O,Q;return Q=function(){A=!1,E&&(O.apply(u,E),E=!1)},O=function(){A?E=arguments:(t.apply(u,arguments),setTimeout(Q,i),A=!0)},O}function v(t,i,u){var A=i[1],E=i[0],O=A-E;return t===A&&u?t:((t-E)%O+O)%O+E}function y(){return!1}function w(t,i){if(i===!1)return t;var u=Math.pow(10,i===void 0?6:i);return Math.round(t*u)/u}function B(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}function T(t){return B(t).split(/\s+/)}function C(t,i){Object.prototype.hasOwnProperty.call(t,"options")||(t.options=t.options?f(t.options):{});for(var u in i)t.options[u]=i[u];return t.options}function k(t,i,u){var A=[];for(var E in t)A.push(encodeURIComponent(u?E.toUpperCase():E)+"="+encodeURIComponent(t[E]));return(!i||i.indexOf("?")===-1?"?":"&")+A.join("&")}var D=/\{ *([\w_ -]+) *\}/g;function P(t,i){return t.replace(D,function(u,A){var E=i[A];if(E===void 0)throw new Error("No value provided for variable "+u);return typeof E=="function"&&(E=E(i)),E})}var S=Array.isArray||function(t){return Object.prototype.toString.call(t)==="[object Array]"};function N(t,i){for(var u=0;u<t.length;u++)if(t[u]===i)return u;return-1}var I="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";function z(t){return window["webkit"+t]||window["moz"+t]||window["ms"+t]}var H=0;function j(t){var i=+new Date,u=Math.max(0,16-(i-H));return H=i+u,window.setTimeout(t,u)}var ee=window.requestAnimationFrame||z("RequestAnimationFrame")||j,Z=window.cancelAnimationFrame||z("CancelAnimationFrame")||z("CancelRequestAnimationFrame")||function(t){window.clearTimeout(t)};function V(t,i,u){if(u&&ee===j)t.call(i);else return ee.call(window,p(t,i))}function ne(t){t&&Z.call(window,t)}var J={__proto__:null,extend:d,create:f,bind:p,get lastId(){return h},stamp:g,throttle:m,wrapNum:v,falseFn:y,formatNum:w,trim:B,splitWords:T,setOptions:C,getParamString:k,template:P,isArray:S,indexOf:N,emptyImageUrl:I,requestFn:ee,cancelFn:Z,requestAnimFrame:V,cancelAnimFrame:ne};function ie(){}ie.extend=function(t){var i=function(){C(this),this.initialize&&this.initialize.apply(this,arguments),this.callInitHooks()},u=i.__super__=this.prototype,A=f(u);A.constructor=i,i.prototype=A;for(var E in this)Object.prototype.hasOwnProperty.call(this,E)&&E!=="prototype"&&E!=="__super__"&&(i[E]=this[E]);return t.statics&&d(i,t.statics),t.includes&&(he(t.includes),d.apply(null,[A].concat(t.includes))),d(A,t),delete A.statics,delete A.includes,A.options&&(A.options=u.options?f(u.options):{},d(A.options,t.options)),A._initHooks=[],A.callInitHooks=function(){if(!this._initHooksCalled){u.callInitHooks&&u.callInitHooks.call(this),this._initHooksCalled=!0;for(var O=0,Q=A._initHooks.length;O<Q;O++)A._initHooks[O].call(this)}},i},ie.include=function(t){var i=this.prototype.options;return d(this.prototype,t),t.options&&(this.prototype.options=i,this.mergeOptions(t.options)),this},ie.mergeOptions=function(t){return d(this.prototype.options,t),this},ie.addInitHook=function(t){var i=Array.prototype.slice.call(arguments,1),u=typeof t=="function"?t:function(){this[t].apply(this,i)};return this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(u),this};function he(t){if(!(typeof L>"u"||!L||!L.Mixin)){t=S(t)?t:[t];for(var i=0;i<t.length;i++)t[i]===L.Mixin.Events&&console.warn("Deprecated include of L.Mixin.Events: this property will be removed in future releases, please inherit from L.Evented instead.",new Error().stack)}}var ae={on:function(t,i,u){if(typeof t=="object")for(var A in t)this._on(A,t[A],i);else{t=T(t);for(var E=0,O=t.length;E<O;E++)this._on(t[E],i,u)}return this},off:function(t,i,u){if(!arguments.length)delete this._events;else if(typeof t=="object")for(var A in t)this._off(A,t[A],i);else{t=T(t);for(var E=arguments.length===1,O=0,Q=t.length;O<Q;O++)E?this._off(t[O]):this._off(t[O],i,u)}return this},_on:function(t,i,u,A){if(typeof i!="function"){console.warn("wrong listener type: "+typeof i);return}if(this._listens(t,i,u)===!1){u===this&&(u=void 0);var E={fn:i,ctx:u};A&&(E.once=!0),this._events=this._events||{},this._events[t]=this._events[t]||[],this._events[t].push(E)}},_off:function(t,i,u){var A,E,O;if(this._events&&(A=this._events[t],!!A)){if(arguments.length===1){if(this._firingCount)for(E=0,O=A.length;E<O;E++)A[E].fn=y;delete this._events[t];return}if(typeof i!="function"){console.warn("wrong listener type: "+typeof i);return}var Q=tLine truncated
|
||||
(c) 2012 by Cédric Mesnil. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
@@ -139,7 +139,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${a.do
|
||||
* Counter block mode compatible with Dr Brian Gladman fileenc.c
|
||||
* derived from CryptoJS.mode.CTR
|
||||
* Jan Hruby jhruby.web@gmail.com
|
||||
*/return o.mode.CTRGladman=(function(){var l=o.lib.BlockCipherMode.extend();function d(h){if((h>>24&255)===255){var g=h>>16&255,m=h>>8&255,v=h&255;g===255?(g=0,m===255?(m=0,v===255?v=0:++v):++m):++g,h=0,h+=g<<16,h+=m<<8,h+=v}else h+=1<<24;return h}function f(h){return(h[0]=d(h[0]))===0&&(h[1]=d(h[1])),h}var p=l.Encryptor=l.extend({processBlock:function(h,g){var m=this._cipher,v=m.blockSize,y=this._iv,w=this._counter;y&&(w=this._counter=y.slice(0),this._iv=void 0),f(w);var B=w.slice(0);m.encryptBlock(B,0);for(var T=0;T<v;T++)h[g+T]^=B[T]}});return l.Decryptor=p,l})(),o.mode.CTRGladman})})(fc)),fc.exports}var hc={exports:{}},Jb=hc.exports,zm;function eC(){return zm||(zm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(Jb,function(o){return o.mode.OFB=(function(){var l=o.lib.BlockCipherMode.extend(),d=l.Encryptor=l.extend({processBlock:function(f,p){var h=this._cipher,g=h.blockSize,m=this._iv,v=this._keystream;m&&(v=this._keystream=m.slice(0),this._iv=void 0),h.encryptBlock(v,0);for(var y=0;y<g;y++)f[p+y]^=v[y]}});return l.Decryptor=d,l})(),o.mode.OFB})})(hc)),hc.exports}var pc={exports:{}},tC=pc.exports,Hm;function nC(){return Hm||(Hm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(tC,function(o){return o.mode.ECB=(function(){var l=o.lib.BlockCipherMode.extend();return l.Encryptor=l.extend({processBlock:function(d,f){this._cipher.encryptBlock(d,f)}}),l.Decryptor=l.extend({processBlock:function(d,f){this._cipher.decryptBlock(d,f)}}),l})(),o.mode.ECB})})(pc)),pc.exports}var mc={exports:{}},rC=mc.exports,Vm;function iC(){return Vm||(Vm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(rC,function(o){return o.pad.AnsiX923={pad:function(l,d){var f=l.sigBytes,p=d*4,h=p-f%p,g=f+h-1;l.clamp(),l.words[g>>>2]|=h<<24-g%4*8,l.sigBytes+=h},unpad:function(l){var d=l.words[l.sigBytes-1>>>2]&255;l.sigBytes-=d}},o.pad.Ansix923})})(mc)),mc.exports}var gc={exports:{}},oC=gc.exports,Um;function sC(){return Um||(Um=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(oC,function(o){return o.pad.Iso10126={pad:function(l,d){var f=d*4,p=f-l.sigBytes%f;l.concat(o.lib.WordArray.random(p-1)).concat(o.lib.WordArray.create([p<<24],1))},unpad:function(l){var d=l.words[l.sigBytes-1>>>2]&255;l.sigBytes-=d}},o.pad.Iso10126})})(gc)),gc.exports}var Ac={exports:{}},aC=Ac.exports,Zm;function lC(){return Zm||(Zm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(aC,function(o){return o.pad.Iso97971={pad:function(l,d){l.concat(o.lib.WordArray.create([2147483648],1)),o.pad.ZeroPadding.pad(l,d)},unpad:function(l){o.pad.ZeroPadding.unpad(l),l.sigBytes--}},o.pad.Iso97971})})(Ac)),Ac.exports}var xc={exports:{}},uC=xc.exports,Wm;function cC(){return Wm||(Wm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(uC,function(o){return o.pad.ZeroPadding={pad:function(l,d){var f=d*4;l.clamp(),l.sigBytes+=f-(l.sigBytes%f||f)},unpad:function(l){for(var d=l.words,f=l.sigBytes-1,f=l.sigBytes-1;f>=0;f--)if(d[f>>>2]>>>24-f%4*8&255){l.sigBytes=f+1;break}}},o.pad.ZeroPadding})})(xc)),xc.exports}var vc={exports:{}},dC=vc.exports,qm;function fC(){return qm||(qm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(dC,function(o){return o.pad.NoPadding={pad:function(){},unpad:function(){}},o.pad.NoPadding})})(vc)),vc.exports}var yc={exports:{}},hC=yc.exports,Gm;function pC(){return Gm||(Gm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(hC,function(o){return(function(l){var d=o,f=d.lib,p=f.CipherParams,h=d.enc,g=h.Hex,m=d.format;m.Hex={stringify:function(v){return v.ciphertext.toString(g)},parse:function(v){var y=g.parse(v);return p.create({ciphertext:y})}}})(),o.format.Hex})})(yc)),yc.exports}var _c={exports:{}},mC=_c.exports,Km;function gC(){return Km||(Km=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),$o(),Yo(),fo(),pn())})(mC,function(o){return(function(){var l=o,d=l.lib,f=d.BlockCipher,p=l.algo,h=[],g=[],m=[],v=[],y=[],w=[],B=[],T=[],C=[],k=[];(function(){for(var S=[],N=0;N<256;N++)N<128?S[N]=N<<1:S[N]=N<<1^283;for(var I=0,z=0,N=0;N<256;N++){var H=z^z<<1^z<<2^z<<3^z<<4;H=H>>>8^H&255^99,h[I]=H,g[H]=I;var j=S[I],ee=S[j],Z=S[ee],V=S[H]*257^H*16843008;m[I]=V<<24|V>>>8,v[I]=V<<16|V>>>16,y[I]=V<<8|V>>>24,w[I]=V;var V=Z*16843009^ee*65537^j*257^I*16843008;B[H]=V<<24|V>>>8,T[H]=V<<16|V>>>16,C[H]=V<<8|V>>>24,k[H]=V,I?(I=j^S[S[S[Z^j]]],z^=S[S[z]]):I=z=1}})();var D=[0,1,2,4,8,16,32,64,128,27,54],P=p.AES=f.extend({_doReset:function(){var S;if(!(this._nRounds&&this._keyPriorReset===this._key)){for(var N=this._keyPriorReset=this._key,I=N.words,z=N.sigBytes/4,H=this._nRounds=z+6,j=(H+1)*4,ee=this._keySchedule=[],Z=0;Z<j;Z++)Z<z?ee[Z]=I[Z]:(S=ee[Z-1],Z%z?z>6&&Z%z==4&&(S=h[S>>>24]<<24|h[S>>>16&255]<<16|h[S>>>8&255]<<8|h[S&255]):(S=S<<8|S>>>24,S=h[S>>>24]<<24|h[S>>>16&255]<<16|h[S>>>8&255]<<8|h[S&255],S^=D[Z/z|0]<<24),ee[Z]=ee[Z-z]^S);for(var V=this._invKeySchedule=[],ne=0;ne<j;ne++){var Z=j-ne;if(ne%4)var S=ee[Z];else var S=ee[Z-4];ne<4||Z<=4?V[ne]=S:V[ne]=B[h[S>>>24]]^T[h[S>>>16&255]]^C[h[S>>>8&255]]^k[h[S&255]Line truncated
|
||||
*/return o.mode.CTRGladman=(function(){var l=o.lib.BlockCipherMode.extend();function d(h){if((h>>24&255)===255){var g=h>>16&255,m=h>>8&255,v=h&255;g===255?(g=0,m===255?(m=0,v===255?v=0:++v):++m):++g,h=0,h+=g<<16,h+=m<<8,h+=v}else h+=1<<24;return h}function f(h){return(h[0]=d(h[0]))===0&&(h[1]=d(h[1])),h}var p=l.Encryptor=l.extend({processBlock:function(h,g){var m=this._cipher,v=m.blockSize,y=this._iv,w=this._counter;y&&(w=this._counter=y.slice(0),this._iv=void 0),f(w);var B=w.slice(0);m.encryptBlock(B,0);for(var T=0;T<v;T++)h[g+T]^=B[T]}});return l.Decryptor=p,l})(),o.mode.CTRGladman})})(fc)),fc.exports}var hc={exports:{}},Jb=hc.exports,zm;function eC(){return zm||(zm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(Jb,function(o){return o.mode.OFB=(function(){var l=o.lib.BlockCipherMode.extend(),d=l.Encryptor=l.extend({processBlock:function(f,p){var h=this._cipher,g=h.blockSize,m=this._iv,v=this._keystream;m&&(v=this._keystream=m.slice(0),this._iv=void 0),h.encryptBlock(v,0);for(var y=0;y<g;y++)f[p+y]^=v[y]}});return l.Decryptor=d,l})(),o.mode.OFB})})(hc)),hc.exports}var pc={exports:{}},tC=pc.exports,Hm;function nC(){return Hm||(Hm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(tC,function(o){return o.mode.ECB=(function(){var l=o.lib.BlockCipherMode.extend();return l.Encryptor=l.extend({processBlock:function(d,f){this._cipher.encryptBlock(d,f)}}),l.Decryptor=l.extend({processBlock:function(d,f){this._cipher.decryptBlock(d,f)}}),l})(),o.mode.ECB})})(pc)),pc.exports}var mc={exports:{}},rC=mc.exports,Vm;function iC(){return Vm||(Vm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(rC,function(o){return o.pad.AnsiX923={pad:function(l,d){var f=l.sigBytes,p=d*4,h=p-f%p,g=f+h-1;l.clamp(),l.words[g>>>2]|=h<<24-g%4*8,l.sigBytes+=h},unpad:function(l){var d=l.words[l.sigBytes-1>>>2]&255;l.sigBytes-=d}},o.pad.Ansix923})})(mc)),mc.exports}var gc={exports:{}},oC=gc.exports,Um;function sC(){return Um||(Um=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(oC,function(o){return o.pad.Iso10126={pad:function(l,d){var f=d*4,p=f-l.sigBytes%f;l.concat(o.lib.WordArray.random(p-1)).concat(o.lib.WordArray.create([p<<24],1))},unpad:function(l){var d=l.words[l.sigBytes-1>>>2]&255;l.sigBytes-=d}},o.pad.Iso10126})})(gc)),gc.exports}var Ac={exports:{}},aC=Ac.exports,Zm;function lC(){return Zm||(Zm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(aC,function(o){return o.pad.Iso97971={pad:function(l,d){l.concat(o.lib.WordArray.create([2147483648],1)),o.pad.ZeroPadding.pad(l,d)},unpad:function(l){o.pad.ZeroPadding.unpad(l),l.sigBytes--}},o.pad.Iso97971})})(Ac)),Ac.exports}var xc={exports:{}},uC=xc.exports,Wm;function cC(){return Wm||(Wm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(uC,function(o){return o.pad.ZeroPadding={pad:function(l,d){var f=d*4;l.clamp(),l.sigBytes+=f-(l.sigBytes%f||f)},unpad:function(l){for(var d=l.words,f=l.sigBytes-1,f=l.sigBytes-1;f>=0;f--)if(d[f>>>2]>>>24-f%4*8&255){l.sigBytes=f+1;break}}},o.pad.ZeroPadding})})(xc)),xc.exports}var vc={exports:{}},dC=vc.exports,qm;function fC(){return qm||(qm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(dC,function(o){return o.pad.NoPadding={pad:function(){},unpad:function(){}},o.pad.NoPadding})})(vc)),vc.exports}var yc={exports:{}},hC=yc.exports,Gm;function pC(){return Gm||(Gm=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),pn())})(hC,function(o){return(function(l){var d=o,f=d.lib,p=f.CipherParams,h=d.enc,g=h.Hex,m=d.format;m.Hex={stringify:function(v){return v.ciphertext.toString(g)},parse:function(v){var y=g.parse(v);return p.create({ciphertext:y})}}})(),o.format.Hex})})(yc)),yc.exports}var _c={exports:{}},mC=_c.exports,Km;function gC(){return Km||(Km=1,(function(r,a){(function(o,l,d){r.exports=l(dt(),$o(),Yo(),fo(),pn())})(mC,function(o){return(function(){var l=o,d=l.lib,f=d.BlockCipher,p=l.algo,h=[],g=[],m=[],v=[],y=[],w=[],B=[],T=[],C=[],k=[];(function(){for(var S=[],N=0;N<256;N++)N<128?S[N]=N<<1:S[N]=N<<1^283;for(var I=0,z=0,N=0;N<256;N++){var H=z^z<<1^z<<2^z<<3^z<<4;H=H>>>8^H&255^99,h[I]=H,g[H]=I;var j=S[I],ee=S[j],Z=S[ee],V=S[H]*257^H*16843008;m[I]=V<<24|V>>>8,v[I]=V<<16|V>>>16,y[I]=V<<8|V>>>24,w[I]=V;var V=Z*16843009^ee*65537^j*257^I*16843008;B[H]=V<<24|V>>>8,T[H]=V<<16|V>>>16,C[H]=V<<8|V>>>24,k[H]=V,I?(I=j^S[S[S[Z^j]]],z^=S[S[z]]):I=z=1}})();var D=[0,1,2,4,8,16,32,64,128,27,54],P=p.AES=f.extend({_doReset:function(){var S;if(!(this._nRounds&&this._keyPriorReset===this._key)){for(var N=this._keyPriorReset=this._key,I=N.words,z=N.sigBytes/4,H=this._nRounds=z+6,j=(H+1)*4,ee=this._keySchedule=[],Z=0;Z<j;Z++)Z<z?ee[Z]=I[Z]:(S=ee[Z-1],Z%z?z>6&&Z%z==4&&(S=h[S>>>24]<<24|h[S>>>16&255]<<16|h[S>>>8&255]<<8|h[S&255]):(S=S<<8|S>>>24,S=h[S>>>24]<<24|h[S>>>16&255]<<16|h[S>>>8&255]<<8|h[S&255],S^=D[Z/z|0]<<24),ee[Z]=ee[Z-z]^S);for(var V=this._invKeySchedule=[],ne=0;ne<j;ne++){var Z=j-ne;if(ne%4)var S=ee[Z];else var S=ee[Z-4];ne<4||Z<=4?V[ne]=S:V[ne]=B[h[S>>>24]]^T[h[S>>>16&255]]^C[h[S>>>8&255]]^k[h[S&255]Line truncated
|
||||
• Timestamp (4 bytes) - send time as unix timestamp
|
||||
• Request Type (1 byte) - type of request (GetStats, GetTelemetryData, etc.)
|
||||
• Request Data (remaining bytes) - additional request-specific data`,startByte:h+g,endByte:h+d.length-1,value:w});const B={type:r.PayloadType.Request,version:r.PayloadVersion.Version1,isValid:!0,timestamp:0,requestType:r.RequestType.GetStats,requestData:"",destinationHash:m,sourceHash:v,cipherMac:y,ciphertext:w};return f!=null&&f.includeSegments&&(B.segments=p),B}catch(p){return{type:r.PayloadType.Request,version:r.PayloadVersion.Version1,isValid:!1,errors:[p instanceof Error?p.message:"Failed to decode request payload"],timestamp:0,requestType:r.RequestType.GetStats,requestData:"",destinationHash:"",sourceHash:"",cipherMac:"",ciphertext:""}}}}return Ja.RequestPayloadDecoder=o,Ja}var el={},Ag;function XC(){if(Ag)return el;Ag=1,Object.defineProperty(el,"__esModule",{value:!0}),el.ResponsePayloadDecoder=void 0;const r=sr(),a=vn();class o{static decode(d,f){try{if(d.length<4){const T={type:r.PayloadType.Response,version:r.PayloadVersion.Version1,isValid:!1,errors:["Response payload too short (minimum 4 bytes: dest + source + MAC)"],destinationHash:"",sourceHash:"",cipherMac:"",ciphertext:"",ciphertextLength:0};return f!=null&&f.includeSegments&&(T.segments=[{name:"Invalid Response Data",description:"Response payload too short (minimum 4 bytes required)",startByte:f.segmentOffset||0,endByte:(f.segmentOffset||0)+d.length-1,value:(0,a.bytesToHex)(d)}]),T}const p=[],h=(f==null?void 0:f.segmentOffset)||0;let g=0;const m=(0,a.byteToHex)(d[g]);f!=null&&f.includeSegments&&p.push({name:"Destination Hash",description:"First byte of destination node public key",startByte:h+g,endByte:h+g,value:m}),g+=1;const v=(0,a.byteToHex)(d[g]);f!=null&&f.includeSegments&&p.push({name:"Source Hash",description:"First byte of source node public key",startByte:h+g,endByte:h+g,value:v}),g+=1;const y=(0,a.bytesToHex)(d.subarray(g,g+2));f!=null&&f.includeSegments&&p.push({name:"Cipher MAC",description:"MAC for encrypted data in next field",startByte:h+g,endByte:h+g+1,value:y}),g+=2;const w=(0,a.bytesToHex)(d.subarray(g));f!=null&&f.includeSegments&&d.length>g&&p.push({name:"Ciphertext",description:"Encrypted response data (tag + content)",startByte:h+g,endByte:h+d.length-1,value:w});const B={type:r.PayloadType.Response,version:r.PayloadVersion.Version1,isValid:!0,destinationHash:m,sourceHash:v,cipherMac:y,ciphertext:w,ciphertextLength:d.length-4};return f!=null&&f.includeSegments&&(B.segments=p),B}catch(p){return{type:r.PayloadType.Response,version:r.PayloadVersion.Version1,isValid:!1,errors:[p instanceof Error?p.message:"Failed to decode response payload"],destinationHash:"",sourceHash:"",cipherMac:"",ciphertext:"",ciphertextLength:0}}}}return el.ResponsePayloadDecoder=o,el}var tl={},xg;function JC(){if(xg)return tl;xg=1,Object.defineProperty(tl,"__esModule",{value:!0}),tl.AnonRequestPayloadDecoder=void 0;const r=sr(),a=vn();class o{static decode(d,f){try{if(d.length<35){const T={type:r.PayloadType.AnonRequest,version:r.PayloadVersion.Version1,isValid:!1,errors:["AnonRequest payload too short (minimum 35 bytes: dest + public key + MAC)"],destinationHash:"",senderPublicKey:"",cipherMac:"",ciphertext:"",ciphertextLength:0};return f!=null&&f.includeSegments&&(T.segments=[{name:"Invalid AnonRequest Data",description:"AnonRequest payload too short (minimum 35 bytes required: 1 for dest hash + 32 for public key + 2 for MAC)",startByte:f.segmentOffset||0,endByte:(f.segmentOffset||0)+d.length-1,value:(0,a.bytesToHex)(d)}]),T}const p=[],h=(f==null?void 0:f.segmentOffset)||0;let g=0;const m=(0,a.byteToHex)(d[0]);f!=null&&f.includeSegments&&p.push({name:"Destination Hash",description:`First byte of destination node public key: 0x${m}`,startByte:h+g,endByte:h+g,value:m}),g+=1;const v=(0,a.bytesToHex)(d.subarray(1,33));f!=null&&f.includeSegments&&p.push({name:"Sender Public Key",description:"Ed25519 public key of the sender (32 bytes)",startByte:h+g,endByte:h+g+31,value:v}),g+=32;const y=(0,a.bytesToHex)(d.subarray(33,35));f!=null&&f.includeSegments&&p.push({name:"Cipher MAC",description:"MAC for encrypted data verification (2 bytes)",startByte:h+g,endByte:h+g+1,value:y}),g+=2;const w=(0,a.bytesToHex)(d.subarray(35));f!=null&&f.includeSegments&&d.length>35&&p.push({name:"Ciphertext",description:`Encrypted message data (${d.length-35} bytes). Contains encrypted plaintext with this structure:
|
||||
@@ -538,5 +538,5 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
||||
See https://github.com/richtr/NoSleep.js/issues/15 for more details.
|
||||
`),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 Yf=d,Yf}var bE=wE();const CE=Xs(bE);function EE(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 kE({packets:r,channels:a,onChannelCreate:o,onRunningChange:l,visible:d=!1}){const[f,p]=F.useState(!1),[h,g]=F.useState(6),[m,v]=F.useState(!1),[y,w]=F.useState(!0),[B,T]=F.useState(!1),[C,k]=F.useState(null),[D,P]=F.useState(new Map),[S,N]=F.useState([]),[I,z]=F.useState(!1),[H,j]=F.useState(null),[ee,Z]=F.useState(null),[V,ne]=F.useState(0),J=F.useRef(null),ie=F.useRef(null),he=F.useRef(!1),ae=F.useRef(!1),pe=F.useRef(!1),G=F.useRef(new Map),Y=F.useRef(!1),q=F.useRef(6),R=F.useRef(!0),U=F.useRef(!1),K=F.useRef(new Set),$=F.useRef(new Set);F.useEffect(()=>{const Be=new yE;J.current=Be,j(Be.isGpuAvailable());const Ze=new CE;return ie.current=Ze,()=>{Be.destroy(),J.current=null,Ze.disable(),ie.current=null}},[]),F.useEffect(()=>{!d||I||xb(async()=>{const{ENGLISH_WORDLIST:Be}=await import("./wordlist-BtmChKSf.js");return{ENGLISH_WORDLIST:Be}},[]).then(({ENGLISH_WORDLIST:Be})=>{J.current&&(J.current.setWordlist(Be),z(!0))}).catch(Be=>{console.error("Failed to load wordlist:",Be),bt.error("Failed to load wordlist",{description:"Cracking will not be available"})})},[d,I]),F.useEffect(()=>{const Be=()=>{ot.getUndecryptedPacketCount().then(({count:lt})=>Z(lt)).catch(()=>Z(null))};Be();const Ze=setInterval(Be,3e4);return()=>clearInterval(Ze)},[]);const de=F.useMemo(()=>new Set(a.map(Be=>Be.key.toUpperCase())),[a]),fe=r.filter(Be=>Be.payload_type==="GROUP_TEXT"&&!Be.decrypted);F.useEffect(()=>{let Be=0;P(Ze=>{const lt=new Map(Ze);let Rt=!1;for(const xt of fe)if(!lt.has(xt.id)){const it=EE(xt.data);if(it&&$.current.has(it)){Be++;continue}it&&$.current.add(it),lt.set(xt.id,{packet:xt,attempts:0,lastAttemptLength:0,status:"pending"}),Rt=!0}return Rt?(G.current=lt,lt):Ze}),Be>0&&ne(Ze=>Ze+Be)},[fe.length]),F.useEffect(()=>{G.current=D},[D]),F.useEffect(()=>{Y.current=m},[m]),F.useEffect(()=>{q.current=h},[h]),F.useEffect(()=>{R.current=y},[y]),F.useEffect(()=>{U.current=B},[B]),F.useEffect(()=>{K.current=new Set(fe.map(Be=>Be.id))},[fe]),F.useEffect(()=>{l==null||l(f)},[f,l]);const ke=Array.from(D.values()).filter(Be=>Be.status==="pending").length,Ae=Array.from(D.values()).filter(Be=>Be.status==="cracked").length,We=Array.from(D.values()).filter(Be=>Be.status==="failed").length,$e=F.useCallback(async()=>{if(pe.current||!J.current||!he.current)return;const Be=G.current;let Ze=null,lt=null;for(const[Qe,Xe]of Be.entries())if(Xe.status==="pending"){Ze=Xe,lt=Qe;break}if(!Ze&&Y.current){const Qe=Array.from(Be.entries()).filter(([,Xe])=>Xe.status==="failed"&&Xe.lastAttemptLength<10);Qe.length>0&&(Qe.sort((Xe,xe)=>Xe[1].lastAttemptLength-xe[1].lastAttemptLength),[lt,Ze]=Qe[0])}if(!Ze||lt===null){he.current&&setTimeout(()=>$e(),1e3);return}if(!K.current.has(lt)){P(Qe=>{const Xe=new Map(Qe);return Xe.delete(lt),Xe}),he.current&&setTimeout(()=>$e(),10);return}pe.current=!0;const Rt=q.current,xt=Ze.lastAttemptLength>0,it=xt?Ze.lastAttemptLength+1:Rt;try{const Qe=await J.current.crack(Ze.packet.data,{maxLength:it,useSenderFilter:!0,useTimestampFilter:!0,useUtf8Filter:!0,...U.current&&{gpuDispatchMs:1e4},...xt&&{useDictionary:!1,startingLength:it}},Xe=>{k(Xe)});if(ae.current){ae.current=!1,pe.current=!1,k(null);return}if(Qe.found&&Qe.roomName&&Qe.key){P(Te=>{const pt=new Map(Te),Zt=pt.get(lt);return Zt&&pt.set(lt,{...Zt,status:"cracked",attempts:Zt.attempts+1,lastAttemptLength:it}),pt});const Xe={roomName:Qe.roomName,key:Qe.key,packetId:lt,message:Qe.decryptedMessage||"",crackedAt:Date.now()};N(Te=>[...Te,Xe]);const xe=Qe.key.toUpperCase();if(!de.has(xe))try{const Te="#"+Qe.roomName;await o(Te,Qe.key),R.current&&await ot.decryptHistoricalPackets({key_type:"channel",channel_name:Te})}catch(Te){console.error("Failed to create channel or decrypt historical:",Te),bt.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(Qe){console.error("Cracking error:",Qe),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,k(null),he.current&&setTimeout(()=>$e(),100)},[de,o]),wt=()=>{var Be;if(!H){bt.error("WebGPU not available",{description:"Cracking requires Chrome 113+ or Edge 113+ with WebGPU support."});return}p(!0),he.current=!0,ae.current=!1,(Be=ie.current)==null||Be.enable(),$e()},ft=()=>{var Be,Ze;p(!1),he.current=!1,ae.current=!0,(Be=J.current)==null||Be.abort(),(Ze=ie.current)==nu
Line truncated
|
||||
//# sourceMappingURL=index-C-0BydkT.js.map
|
||||
`),window.clearInterval(this.noSleepTimer),this.noSleepTimer=null):this.noSleepVideo.pause(),this.enabled=!1}}return Yf=d,Yf}var bE=wE();const CE=Xs(bE);function EE(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 kE({packets:r,channels:a,onChannelCreate:o,onRunningChange:l,visible:d=!1}){const[f,p]=F.useState(!1),[h,g]=F.useState(6),[m,v]=F.useState(!1),[y,w]=F.useState(!0),[B,T]=F.useState(!1),[C,k]=F.useState(null),[D,P]=F.useState(new Map),[S,N]=F.useState([]),[I,z]=F.useState(!1),[H,j]=F.useState(null),[ee,Z]=F.useState(null),[V,ne]=F.useState(0),J=F.useRef(null),ie=F.useRef(null),he=F.useRef(!1),ae=F.useRef(!1),pe=F.useRef(!1),G=F.useRef(new Map),Y=F.useRef(!1),q=F.useRef(6),R=F.useRef(!0),U=F.useRef(!1),K=F.useRef(new Set),$=F.useRef(new Set);F.useEffect(()=>{const Be=new yE;J.current=Be,j(Be.isGpuAvailable());const Ze=new CE;return ie.current=Ze,()=>{Be.destroy(),J.current=null,Ze.disable(),ie.current=null}},[]),F.useEffect(()=>{!d||I||xb(async()=>{const{ENGLISH_WORDLIST:Be}=await import("./wordlist-BtmChKSf.js");return{ENGLISH_WORDLIST:Be}},[]).then(({ENGLISH_WORDLIST:Be})=>{J.current&&(J.current.setWordlist(Be),z(!0))}).catch(Be=>{console.error("Failed to load wordlist:",Be),bt.error("Failed to load wordlist",{description:"Cracking will not be available"})})},[d,I]),F.useEffect(()=>{const Be=()=>{rt.getUndecryptedPacketCount().then(({count:lt})=>Z(lt)).catch(()=>Z(null))};Be();const Ze=setInterval(Be,3e4);return()=>clearInterval(Ze)},[]);const de=F.useMemo(()=>new Set(a.map(Be=>Be.key.toUpperCase())),[a]),fe=r.filter(Be=>Be.payload_type==="GROUP_TEXT"&&!Be.decrypted);F.useEffect(()=>{let Be=0;P(Ze=>{const lt=new Map(Ze);let Rt=!1;for(const vt of fe)if(!lt.has(vt.id)){const ot=EE(vt.data);if(ot&&$.current.has(ot)){Be++;continue}ot&&$.current.add(ot),lt.set(vt.id,{packet:vt,attempts:0,lastAttemptLength:0,status:"pending"}),Rt=!0}return Rt?(G.current=lt,lt):Ze}),Be>0&&ne(Ze=>Ze+Be)},[fe.length]),F.useEffect(()=>{G.current=D},[D]),F.useEffect(()=>{Y.current=m},[m]),F.useEffect(()=>{q.current=h},[h]),F.useEffect(()=>{R.current=y},[y]),F.useEffect(()=>{U.current=B},[B]),F.useEffect(()=>{K.current=new Set(fe.map(Be=>Be.id))},[fe]),F.useEffect(()=>{l==null||l(f)},[f,l]);const ke=Array.from(D.values()).filter(Be=>Be.status==="pending").length,Ae=Array.from(D.values()).filter(Be=>Be.status==="cracked").length,We=Array.from(D.values()).filter(Be=>Be.status==="failed").length,$e=F.useCallback(async()=>{if(pe.current||!J.current||!he.current)return;const Be=G.current;let Ze=null,lt=null;for(const[Qe,Xe]of Be.entries())if(Xe.status==="pending"){Ze=Xe,lt=Qe;break}if(!Ze&&Y.current){const Qe=Array.from(Be.entries()).filter(([,Xe])=>Xe.status==="failed"&&Xe.lastAttemptLength<10);Qe.length>0&&(Qe.sort((Xe,xe)=>Xe[1].lastAttemptLength-xe[1].lastAttemptLength),[lt,Ze]=Qe[0])}if(!Ze||lt===null){he.current&&setTimeout(()=>$e(),1e3);return}if(!K.current.has(lt)){P(Qe=>{const Xe=new Map(Qe);return Xe.delete(lt),Xe}),he.current&&setTimeout(()=>$e(),10);return}pe.current=!0;const Rt=q.current,vt=Ze.lastAttemptLength>0,ot=vt?Ze.lastAttemptLength+1:Rt;try{const Qe=await J.current.crack(Ze.packet.data,{maxLength:ot,useSenderFilter:!0,useTimestampFilter:!0,useUtf8Filter:!0,...U.current&&{gpuDispatchMs:1e4},...vt&&{useDictionary:!1,startingLength:ot}},Xe=>{k(Xe)});if(ae.current){ae.current=!1,pe.current=!1,k(null);return}if(Qe.found&&Qe.roomName&&Qe.key){P(Te=>{const pt=new Map(Te),Zt=pt.get(lt);return Zt&&pt.set(lt,{...Zt,status:"cracked",attempts:Zt.attempts+1,lastAttemptLength:ot}),pt});const Xe={roomName:Qe.roomName,key:Qe.key,packetId:lt,message:Qe.decryptedMessage||"",crackedAt:Date.now()};N(Te=>[...Te,Xe]);const xe=Qe.key.toUpperCase();if(!de.has(xe))try{const Te="#"+Qe.roomName;await o(Te,Qe.key),R.current&&await rt.decryptHistoricalPackets({key_type:"channel",channel_name:Te})}catch(Te){console.error("Failed to create channel or decrypt historical:",Te),bt.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:ot}),xe})}catch(Qe){console.error("Cracking error:",Qe),P(Xe=>{const xe=new Map(Xe),Te=xe.get(lt);return Te&&xe.set(lt,{...Te,status:"failed",attempts:Te.attempts+1,lastAttemptLength:ot}),xe})}pe.current=!1,k(null),he.current&&setTimeout(()=>$e(),100)},[de,o]),wt=()=>{var Be;if(!H){bt.error("WebGPU not available",{description:"Cracking requires Chrome 113+ or Edge 113+ with WebGPU support."});return}p(!0),he.current=!0,ae.current=!1,(Be=ie.current)==null||Be.enable(),$e()},ft=()=>{var Be,Ze;p(!1),he.current=!1,ae.current=!0,(Be=J.current)==null||Be.abort(),(Ze=ie.current)==nu
Line truncated
|
||||
//# sourceMappingURL=index-Ck-mz-Y8.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"mappings":"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 truncated
|
||||
Vendored
+1
-1
@@ -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-C-0BydkT.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-Ck-mz-Y8.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CcSjAzwK.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+5
-22
@@ -467,32 +467,15 @@ export function App() {
|
||||
// Create contact handler
|
||||
const handleCreateContact = useCallback(
|
||||
async (name: string, publicKey: string, tryHistorical: boolean) => {
|
||||
const newContact: Contact = {
|
||||
public_key: publicKey,
|
||||
name,
|
||||
type: 0,
|
||||
flags: 0,
|
||||
last_path: null,
|
||||
last_path_len: -1,
|
||||
last_advert: null,
|
||||
lat: null,
|
||||
lon: null,
|
||||
last_seen: null,
|
||||
on_radio: false,
|
||||
last_contacted: null,
|
||||
last_read_at: null,
|
||||
};
|
||||
setContacts((prev) => [...prev, newContact]);
|
||||
const created = await api.createContact(publicKey, name || undefined, tryHistorical);
|
||||
const data = await api.getContacts();
|
||||
setContacts(data);
|
||||
|
||||
setActiveConversation({
|
||||
type: 'contact',
|
||||
id: publicKey,
|
||||
name: getContactDisplayName(name, publicKey),
|
||||
id: created.public_key,
|
||||
name: getContactDisplayName(created.name, created.public_key),
|
||||
});
|
||||
|
||||
if (tryHistorical) {
|
||||
console.log('Contact historical decryption not yet supported');
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
@@ -94,6 +94,11 @@ export const api = {
|
||||
fetchJson<{ status: string }>(`/contacts/${publicKey}`, {
|
||||
method: 'DELETE',
|
||||
}),
|
||||
createContact: (publicKey: string, name?: string, tryHistorical?: boolean) =>
|
||||
fetchJson<Contact>('/contacts', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ public_key: publicKey, name, try_historical: tryHistorical }),
|
||||
}),
|
||||
markContactRead: (publicKey: string) =>
|
||||
fetchJson<{ status: string; public_key: string }>(`/contacts/${publicKey}/mark-read`, {
|
||||
method: 'POST',
|
||||
|
||||
@@ -10,6 +10,7 @@ dependencies = [
|
||||
"pydantic-settings>=2.0.0",
|
||||
"aiosqlite>=0.19.0",
|
||||
"pycryptodome>=3.20.0",
|
||||
"pynacl>=1.5.0",
|
||||
"meshcore",
|
||||
]
|
||||
|
||||
|
||||
@@ -10,11 +10,17 @@ import hmac
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
from app.decoder import (
|
||||
DecryptedDirectMessage,
|
||||
PayloadType,
|
||||
RouteType,
|
||||
_clamp_scalar,
|
||||
calculate_channel_hash,
|
||||
decrypt_direct_message,
|
||||
decrypt_group_text,
|
||||
derive_public_key,
|
||||
derive_shared_secret,
|
||||
parse_packet,
|
||||
try_decrypt_dm,
|
||||
try_decrypt_packet_with_channel_key,
|
||||
)
|
||||
|
||||
@@ -355,3 +361,368 @@ class TestAdvertisementParsing:
|
||||
result = try_parse_advertisement(packet)
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestScalarClamping:
|
||||
"""Test X25519 scalar clamping for ECDH."""
|
||||
|
||||
def test_clamp_scalar_modifies_first_byte(self):
|
||||
"""Clamping clears the lower 3 bits of the first byte."""
|
||||
# Input with all bits set in first byte
|
||||
scalar = bytes([0xFF]) + bytes(31)
|
||||
|
||||
result = _clamp_scalar(scalar)
|
||||
|
||||
# First byte should have lower 3 bits cleared: 0xFF & 248 = 0xF8
|
||||
assert result[0] == 0xF8
|
||||
|
||||
def test_clamp_scalar_modifies_last_byte(self):
|
||||
"""Clamping modifies the last byte for correct group operations."""
|
||||
# Input with all bits set in last byte
|
||||
scalar = bytes(31) + bytes([0xFF])
|
||||
|
||||
result = _clamp_scalar(scalar)
|
||||
|
||||
# Last byte: (0xFF & 63) | 64 = 0x7F
|
||||
assert result[31] == 0x7F
|
||||
|
||||
def test_clamp_scalar_preserves_middle_bytes(self):
|
||||
"""Clamping preserves the middle bytes unchanged."""
|
||||
# Known middle bytes
|
||||
scalar = bytes([0xAB]) + bytes([0x12, 0x34, 0x56] * 10)[:30] + bytes([0xCD])
|
||||
|
||||
result = _clamp_scalar(scalar)
|
||||
|
||||
# Middle bytes should be unchanged
|
||||
assert result[1:31] == scalar[1:31]
|
||||
|
||||
def test_clamp_scalar_truncates_to_32_bytes(self):
|
||||
"""Clamping uses only first 32 bytes of input."""
|
||||
# 64-byte input (typical Ed25519 private key)
|
||||
scalar = bytes(64)
|
||||
|
||||
result = _clamp_scalar(scalar)
|
||||
|
||||
assert len(result) == 32
|
||||
|
||||
|
||||
class TestPublicKeyDerivation:
|
||||
"""Test deriving Ed25519 public key from MeshCore private key."""
|
||||
|
||||
# Test data from real MeshCore keys
|
||||
# The private key's first 32 bytes are the scalar (post-SHA-512 clamped)
|
||||
# The public key is derived via scalar × basepoint, NOT from the last 32 bytes
|
||||
#
|
||||
# IMPORTANT: The last 32 bytes of a MeshCore private key are the signing prefix,
|
||||
# NOT the public key! Standard Ed25519 libraries will give wrong results because
|
||||
# they expect a seed, not a raw scalar.
|
||||
FACE12_PRIV = bytes.fromhex(
|
||||
"58BA1940E97099CBB4357C62CE9C7F4B245C94C90D722E67201B989F9FEACF7B"
|
||||
"77ACADDB84438514022BDB0FC3140C2501859BE1772AC7B8C7E41DC0F40490A1"
|
||||
)
|
||||
# Expected public key derived from scalar × basepoint
|
||||
# Note: This starts with "face12" - the derived public key, NOT the signing prefix
|
||||
FACE12_PUB_EXPECTED = bytes.fromhex(
|
||||
"FACE123334789E2B81519AFDBC39A3C9EB7EA3457AD367D3243597A484847E46"
|
||||
)
|
||||
|
||||
def test_derive_public_key_from_meshcore_private(self):
|
||||
"""derive_public_key correctly derives public key from MeshCore private key."""
|
||||
result = derive_public_key(self.FACE12_PRIV)
|
||||
|
||||
assert len(result) == 32
|
||||
assert result == self.FACE12_PUB_EXPECTED
|
||||
|
||||
def test_derive_public_key_from_scalar_only(self):
|
||||
"""derive_public_key works with just the 32-byte scalar."""
|
||||
scalar_only = self.FACE12_PRIV[:32]
|
||||
|
||||
result = derive_public_key(scalar_only)
|
||||
|
||||
assert len(result) == 32
|
||||
assert result == self.FACE12_PUB_EXPECTED
|
||||
|
||||
def test_derive_public_key_deterministic(self):
|
||||
"""Same private key always produces same public key."""
|
||||
result1 = derive_public_key(self.FACE12_PRIV)
|
||||
result2 = derive_public_key(self.FACE12_PRIV)
|
||||
|
||||
assert result1 == result2
|
||||
|
||||
|
||||
class TestSharedSecretDerivation:
|
||||
"""Test ECDH shared secret derivation from Ed25519 keys."""
|
||||
|
||||
# Test data from real MeshCore keys
|
||||
# The private key's first 32 bytes are the scalar (post-SHA-512 clamped)
|
||||
# The last 32 bytes are the signing prefix (NOT the public key, though they may match)
|
||||
FACE12_PRIV = bytes.fromhex(
|
||||
"58BA1940E97099CBB4357C62CE9C7F4B245C94C90D722E67201B989F9FEACF7B"
|
||||
"77ACADDB84438514022BDB0FC3140C2501859BE1772AC7B8C7E41DC0F40490A1"
|
||||
)
|
||||
# a1b2c3 public key (32 bytes)
|
||||
A1B2C3_PUB = bytes.fromhex("a1b2c3d3ba9f5fa8705b9845fe11cc6f01d1d49caaf4d122ac7121663c5beec7")
|
||||
|
||||
def test_derive_shared_secret_returns_32_bytes(self):
|
||||
"""Shared secret derivation returns 32-byte value."""
|
||||
result = derive_shared_secret(self.FACE12_PRIV, self.A1B2C3_PUB)
|
||||
|
||||
assert len(result) == 32
|
||||
|
||||
def test_derive_shared_secret_deterministic(self):
|
||||
"""Same inputs always produce same shared secret."""
|
||||
result1 = derive_shared_secret(self.FACE12_PRIV, self.A1B2C3_PUB)
|
||||
result2 = derive_shared_secret(self.FACE12_PRIV, self.A1B2C3_PUB)
|
||||
|
||||
assert result1 == result2
|
||||
|
||||
def test_derive_shared_secret_different_keys_different_result(self):
|
||||
"""Different key pairs produce different shared secrets."""
|
||||
other_pub = bytes.fromhex(
|
||||
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
|
||||
)
|
||||
|
||||
result1 = derive_shared_secret(self.FACE12_PRIV, self.A1B2C3_PUB)
|
||||
# This may raise an exception for invalid public key, which is also acceptable
|
||||
try:
|
||||
result2 = derive_shared_secret(self.FACE12_PRIV, other_pub)
|
||||
assert result1 != result2
|
||||
except Exception:
|
||||
# Invalid public keys may fail, which is fine
|
||||
pass
|
||||
|
||||
|
||||
class TestDirectMessageDecryption:
|
||||
"""Test TEXT_MESSAGE (direct message) payload decryption."""
|
||||
|
||||
# Real test vector from user
|
||||
# Payload: [dest_hash:1][src_hash:1][mac:2][ciphertext]
|
||||
PAYLOAD = bytes.fromhex(
|
||||
"FAA1295471ADB44A98B13CA528A4B5C4FBC29B4DA3CED477519B2FBD8FD5467C31E5D58B"
|
||||
)
|
||||
|
||||
# Keys for deriving shared secret
|
||||
FACE12_PRIV = bytes.fromhex(
|
||||
"58BA1940E97099CBB4357C62CE9C7F4B245C94C90D722E67201B989F9FEACF7B"
|
||||
"77ACADDB84438514022BDB0FC3140C2501859BE1772AC7B8C7E41DC0F40490A1"
|
||||
)
|
||||
A1B2C3_PUB = bytes.fromhex("a1b2c3d3ba9f5fa8705b9845fe11cc6f01d1d49caaf4d122ac7121663c5beec7")
|
||||
|
||||
EXPECTED_MESSAGE = "Hello there, Mr. Face!"
|
||||
|
||||
def test_decrypt_real_dm_payload(self):
|
||||
"""Decrypt a real DM payload with known shared secret."""
|
||||
shared_secret = derive_shared_secret(self.FACE12_PRIV, self.A1B2C3_PUB)
|
||||
|
||||
result = decrypt_direct_message(self.PAYLOAD, shared_secret)
|
||||
|
||||
assert result is not None
|
||||
assert result.message == self.EXPECTED_MESSAGE
|
||||
assert result.dest_hash == "fa" # First byte of payload
|
||||
assert result.src_hash == "a1" # Second byte, matches a1b2c3
|
||||
|
||||
def test_decrypt_extracts_timestamp(self):
|
||||
"""Decrypted message contains valid timestamp."""
|
||||
shared_secret = derive_shared_secret(self.FACE12_PRIV, self.A1B2C3_PUB)
|
||||
|
||||
result = decrypt_direct_message(self.PAYLOAD, shared_secret)
|
||||
|
||||
assert result is not None
|
||||
assert result.timestamp > 0 # Non-zero timestamp
|
||||
assert result.timestamp < 2**32 # Within uint32 range
|
||||
|
||||
def test_decrypt_extracts_flags(self):
|
||||
"""Decrypted message contains flags byte."""
|
||||
shared_secret = derive_shared_secret(self.FACE12_PRIV, self.A1B2C3_PUB)
|
||||
|
||||
result = decrypt_direct_message(self.PAYLOAD, shared_secret)
|
||||
|
||||
assert result is not None
|
||||
assert isinstance(result.flags, int)
|
||||
assert 0 <= result.flags <= 255
|
||||
|
||||
def test_decrypt_with_wrong_secret_fails(self):
|
||||
"""Decryption with incorrect shared secret fails MAC verification."""
|
||||
wrong_secret = bytes(32) # All zeros
|
||||
|
||||
result = decrypt_direct_message(self.PAYLOAD, wrong_secret)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_decrypt_with_corrupted_mac_fails(self):
|
||||
"""Corrupted MAC causes decryption to fail."""
|
||||
shared_secret = derive_shared_secret(self.FACE12_PRIV, self.A1B2C3_PUB)
|
||||
|
||||
# Corrupt the MAC (bytes 2-3)
|
||||
corrupted = self.PAYLOAD[:2] + bytes([0xFF, 0xFF]) + self.PAYLOAD[4:]
|
||||
|
||||
result = decrypt_direct_message(corrupted, shared_secret)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_decrypt_too_short_payload_returns_none(self):
|
||||
"""Payloads shorter than minimum (4 bytes) return None."""
|
||||
shared_secret = derive_shared_secret(self.FACE12_PRIV, self.A1B2C3_PUB)
|
||||
|
||||
result = decrypt_direct_message(bytes(3), shared_secret)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_decrypt_invalid_ciphertext_length_returns_none(self):
|
||||
"""Ciphertext not a multiple of 16 bytes returns None."""
|
||||
shared_secret = derive_shared_secret(self.FACE12_PRIV, self.A1B2C3_PUB)
|
||||
|
||||
# 4-byte header + 15-byte ciphertext (not multiple of 16)
|
||||
invalid_payload = bytes(4 + 15)
|
||||
|
||||
result = decrypt_direct_message(invalid_payload, shared_secret)
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestTryDecryptDM:
|
||||
"""Test full packet decryption for direct messages."""
|
||||
|
||||
# Full packet: header + path_length + payload
|
||||
# Header byte = 0x09: route_type=FLOOD(1), payload_type=TEXT_MESSAGE(2)
|
||||
# Header byte = (0 << 6) | (2 << 2) | 1 = 0x09
|
||||
# Path length = 0
|
||||
FULL_PACKET = bytes.fromhex(
|
||||
"0900FAA1295471ADB44A98B13CA528A4B5C4FBC29B4DA3CED477519B2FBD8FD5467C31E5D58B"
|
||||
)
|
||||
|
||||
# Keys
|
||||
FACE12_PRIV = bytes.fromhex(
|
||||
"58BA1940E97099CBB4357C62CE9C7F4B245C94C90D722E67201B989F9FEACF7B"
|
||||
"77ACADDB84438514022BDB0FC3140C2501859BE1772AC7B8C7E41DC0F40490A1"
|
||||
)
|
||||
# FACE12 public key - derived via scalar × basepoint, NOT the last 32 bytes!
|
||||
# The last 32 bytes (77AC...) are the signing prefix, not the public key.
|
||||
FACE12_PUB = bytes.fromhex("FACE123334789E2B81519AFDBC39A3C9EB7EA3457AD367D3243597A484847E46")
|
||||
|
||||
A1B2C3_PUB = bytes.fromhex("a1b2c3d3ba9f5fa8705b9845fe11cc6f01d1d49caaf4d122ac7121663c5beec7")
|
||||
|
||||
EXPECTED_MESSAGE = "Hello there, Mr. Face!"
|
||||
|
||||
def test_try_decrypt_dm_full_packet(self):
|
||||
"""Decrypt a full TEXT_MESSAGE packet."""
|
||||
result = try_decrypt_dm(
|
||||
self.FULL_PACKET,
|
||||
self.FACE12_PRIV,
|
||||
self.A1B2C3_PUB,
|
||||
our_public_key=self.FACE12_PUB,
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.message == self.EXPECTED_MESSAGE
|
||||
|
||||
def test_try_decrypt_dm_inbound_message(self):
|
||||
"""Decrypt an inbound message (from them to us)."""
|
||||
# src_hash = a1 matches A1B2C3's first byte
|
||||
result = try_decrypt_dm(
|
||||
self.FULL_PACKET,
|
||||
self.FACE12_PRIV,
|
||||
self.A1B2C3_PUB,
|
||||
our_public_key=None, # Without our pubkey, only checks inbound
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.src_hash == "a1"
|
||||
|
||||
def test_try_decrypt_dm_non_text_message_returns_none(self):
|
||||
"""Non-TEXT_MESSAGE packets return None."""
|
||||
# GROUP_TEXT packet (payload_type=5)
|
||||
# Header byte = (0 << 6) | (5 << 2) | 1 = 0x15
|
||||
group_text_packet = bytes([0x15, 0x00]) + self.FULL_PACKET[2:]
|
||||
|
||||
result = try_decrypt_dm(
|
||||
group_text_packet,
|
||||
self.FACE12_PRIV,
|
||||
self.A1B2C3_PUB,
|
||||
)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_try_decrypt_dm_wrong_src_hash_returns_none(self):
|
||||
"""Packets from unknown senders return None."""
|
||||
# Create a packet with different src_hash
|
||||
# Original: FA A1 ... -> dest=FA, src=A1
|
||||
# Modified: FA BB ... -> dest=FA, src=BB (doesn't match A1B2C3)
|
||||
modified_payload = bytes([0xFA, 0xBB]) + self.FULL_PACKET[4:]
|
||||
modified_packet = self.FULL_PACKET[:2] + modified_payload
|
||||
|
||||
result = try_decrypt_dm(
|
||||
modified_packet,
|
||||
self.FACE12_PRIV,
|
||||
self.A1B2C3_PUB,
|
||||
our_public_key=self.FACE12_PUB,
|
||||
)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_try_decrypt_dm_empty_packet_returns_none(self):
|
||||
"""Empty packets return None."""
|
||||
result = try_decrypt_dm(
|
||||
b"",
|
||||
self.FACE12_PRIV,
|
||||
self.A1B2C3_PUB,
|
||||
)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_try_decrypt_dm_truncated_packet_returns_none(self):
|
||||
"""Truncated packets return None."""
|
||||
result = try_decrypt_dm(
|
||||
self.FULL_PACKET[:5], # Only header + partial payload
|
||||
self.FACE12_PRIV,
|
||||
self.A1B2C3_PUB,
|
||||
)
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestRealWorldDMPacket:
|
||||
"""End-to-end test with exact real-world test data."""
|
||||
|
||||
def test_full_dm_decryption_flow(self):
|
||||
"""
|
||||
Complete decryption flow with real test vectors.
|
||||
|
||||
Test data from user:
|
||||
- face12 private key (64 bytes Ed25519)
|
||||
- a1b2c3 public key (32 bytes)
|
||||
- Encrypted payload producing "Hello there, Mr. Face!"
|
||||
"""
|
||||
# Keys
|
||||
face12_priv = bytes.fromhex(
|
||||
"58BA1940E97099CBB4357C62CE9C7F4B245C94C90D722E67201B989F9FEACF7B"
|
||||
"77ACADDB84438514022BDB0FC3140C2501859BE1772AC7B8C7E41DC0F40490A1"
|
||||
)
|
||||
# Derived public key (scalar × basepoint) - NOT the signing prefix from bytes 32-64
|
||||
# First byte is 0xFA, matching dest_hash in test packet
|
||||
face12_pub = bytes.fromhex(
|
||||
"FACE123334789E2B81519AFDBC39A3C9EB7EA3457AD367D3243597A484847E46"
|
||||
)
|
||||
a1b2c3_pub = bytes.fromhex(
|
||||
"a1b2c3d3ba9f5fa8705b9845fe11cc6f01d1d49caaf4d122ac7121663c5beec7"
|
||||
)
|
||||
|
||||
# Full packet with header
|
||||
full_packet = bytes.fromhex(
|
||||
"0900FAA1295471ADB44A98B13CA528A4B5C4FBC29B4DA3CED477519B2FBD8FD5467C31E5D58B"
|
||||
)
|
||||
|
||||
# Decrypt
|
||||
result = try_decrypt_dm(
|
||||
full_packet,
|
||||
face12_priv,
|
||||
a1b2c3_pub,
|
||||
our_public_key=face12_pub,
|
||||
)
|
||||
|
||||
# Verify
|
||||
assert result is not None
|
||||
assert isinstance(result, DecryptedDirectMessage)
|
||||
assert result.message == "Hello there, Mr. Face!"
|
||||
assert result.dest_hash == "fa" # First byte of derived face12 pubkey (0xFA)
|
||||
assert result.src_hash == "a1" # First byte of a1b2c3 pubkey
|
||||
@@ -589,3 +589,299 @@ class TestRawPacketStorage:
|
||||
assert raw_broadcast["decrypted"] is True
|
||||
assert "decrypted_info" in raw_broadcast
|
||||
assert raw_broadcast["decrypted_info"]["channel_name"] == fixture["channel_name"]
|
||||
|
||||
|
||||
class TestCreateDMMessageFromDecrypted:
|
||||
"""Test the DM message creation function for direct message decryption."""
|
||||
|
||||
# Test data from real MeshCore DM example
|
||||
FACE12_PRIV = (
|
||||
"58BA1940E97099CBB4357C62CE9C7F4B245C94C90D722E67201B989F9FEACF7B"
|
||||
"77ACADDB84438514022BDB0FC3140C2501859BE1772AC7B8C7E41DC0F40490A1"
|
||||
)
|
||||
# FACE12 public key - derived via scalar × basepoint, NOT the last 32 bytes!
|
||||
# The last 32 bytes (77AC...) are the signing prefix, not the public key.
|
||||
FACE12_PUB = "FACE123334789E2B81519AFDBC39A3C9EB7EA3457AD367D3243597A484847E46"
|
||||
A1B2C3_PUB = "a1b2c3d3ba9f5fa8705b9845fe11cc6f01d1d49caaf4d122ac7121663c5beec7"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_creates_dm_message_and_broadcasts(self, test_db, captured_broadcasts):
|
||||
"""create_dm_message_from_decrypted creates message and broadcasts correctly."""
|
||||
from app.decoder import DecryptedDirectMessage
|
||||
from app.packet_processor import create_dm_message_from_decrypted
|
||||
|
||||
# Create a raw packet first
|
||||
packet_id, _ = await RawPacketRepository.create(b"test_dm_packet", 1700000000)
|
||||
|
||||
# Create a mock decrypted message
|
||||
decrypted = DecryptedDirectMessage(
|
||||
timestamp=1700000000,
|
||||
flags=0,
|
||||
message="Hello, World!",
|
||||
dest_hash="fa",
|
||||
src_hash="a1",
|
||||
)
|
||||
|
||||
broadcasts, mock_broadcast = captured_broadcasts
|
||||
|
||||
with patch("app.packet_processor.broadcast_event", mock_broadcast):
|
||||
msg_id = await create_dm_message_from_decrypted(
|
||||
packet_id=packet_id,
|
||||
decrypted=decrypted,
|
||||
their_public_key=self.A1B2C3_PUB,
|
||||
our_public_key=self.FACE12_PUB,
|
||||
received_at=1700000001,
|
||||
outgoing=False,
|
||||
)
|
||||
|
||||
# Should return a message ID
|
||||
assert msg_id is not None
|
||||
assert isinstance(msg_id, int)
|
||||
|
||||
# Verify message was stored in database
|
||||
messages = await MessageRepository.get_all(
|
||||
msg_type="PRIV", conversation_key=self.A1B2C3_PUB.lower(), limit=10
|
||||
)
|
||||
assert len(messages) == 1
|
||||
assert messages[0].text == "Hello, World!"
|
||||
assert messages[0].sender_timestamp == 1700000000
|
||||
assert messages[0].outgoing is False
|
||||
|
||||
# Verify broadcast was sent with correct structure
|
||||
message_broadcasts = [b for b in broadcasts if b["type"] == "message"]
|
||||
assert len(message_broadcasts) == 1
|
||||
|
||||
broadcast = message_broadcasts[0]["data"]
|
||||
assert broadcast["id"] == msg_id
|
||||
assert broadcast["type"] == "PRIV"
|
||||
assert broadcast["conversation_key"] == self.A1B2C3_PUB.lower()
|
||||
assert broadcast["text"] == "Hello, World!"
|
||||
assert broadcast["outgoing"] is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handles_outgoing_dm(self, test_db, captured_broadcasts):
|
||||
"""create_dm_message_from_decrypted handles outgoing messages correctly."""
|
||||
from app.decoder import DecryptedDirectMessage
|
||||
from app.packet_processor import create_dm_message_from_decrypted
|
||||
|
||||
packet_id, _ = await RawPacketRepository.create(b"test_outgoing_dm", 1700000000)
|
||||
|
||||
decrypted = DecryptedDirectMessage(
|
||||
timestamp=1700000000,
|
||||
flags=0,
|
||||
message="My outgoing message",
|
||||
dest_hash="a1", # Destination is contact (first byte of A1B2C3_PUB)
|
||||
src_hash="fa", # Source is us (first byte of derived FACE12_PUB)
|
||||
)
|
||||
|
||||
broadcasts, mock_broadcast = captured_broadcasts
|
||||
|
||||
with patch("app.packet_processor.broadcast_event", mock_broadcast):
|
||||
msg_id = await create_dm_message_from_decrypted(
|
||||
packet_id=packet_id,
|
||||
decrypted=decrypted,
|
||||
their_public_key=self.A1B2C3_PUB,
|
||||
our_public_key=self.FACE12_PUB,
|
||||
received_at=1700000001,
|
||||
outgoing=True,
|
||||
)
|
||||
|
||||
assert msg_id is not None
|
||||
|
||||
# Verify outgoing flag is set correctly
|
||||
messages = await MessageRepository.get_all(
|
||||
msg_type="PRIV", conversation_key=self.A1B2C3_PUB.lower(), limit=10
|
||||
)
|
||||
assert len(messages) == 1
|
||||
assert messages[0].outgoing is True
|
||||
|
||||
# Verify broadcast shows outgoing
|
||||
message_broadcasts = [b for b in broadcasts if b["type"] == "message"]
|
||||
assert message_broadcasts[0]["data"]["outgoing"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_none_for_duplicate_dm(self, test_db, captured_broadcasts):
|
||||
"""create_dm_message_from_decrypted returns None for duplicate DM."""
|
||||
from app.decoder import DecryptedDirectMessage
|
||||
from app.packet_processor import create_dm_message_from_decrypted
|
||||
|
||||
packet_id_1, _ = await RawPacketRepository.create(b"dm_packet_1", 1700000000)
|
||||
packet_id_2, _ = await RawPacketRepository.create(b"dm_packet_2", 1700000001)
|
||||
|
||||
decrypted = DecryptedDirectMessage(
|
||||
timestamp=1700000000,
|
||||
flags=0,
|
||||
message="Duplicate DM test",
|
||||
dest_hash="fa",
|
||||
src_hash="a1",
|
||||
)
|
||||
|
||||
broadcasts, mock_broadcast = captured_broadcasts
|
||||
|
||||
with patch("app.packet_processor.broadcast_event", mock_broadcast):
|
||||
# First call creates the message
|
||||
msg_id_1 = await create_dm_message_from_decrypted(
|
||||
packet_id=packet_id_1,
|
||||
decrypted=decrypted,
|
||||
their_public_key=self.A1B2C3_PUB,
|
||||
our_public_key=self.FACE12_PUB,
|
||||
received_at=1700000001,
|
||||
outgoing=False,
|
||||
)
|
||||
|
||||
# Second call with same content returns None
|
||||
msg_id_2 = await create_dm_message_from_decrypted(
|
||||
packet_id=packet_id_2,
|
||||
decrypted=decrypted,
|
||||
their_public_key=self.A1B2C3_PUB,
|
||||
our_public_key=self.FACE12_PUB,
|
||||
received_at=1700000002,
|
||||
outgoing=False,
|
||||
)
|
||||
|
||||
assert msg_id_1 is not None
|
||||
assert msg_id_2 is None # Duplicate detected
|
||||
|
||||
# Only one message broadcast
|
||||
message_broadcasts = [b for b in broadcasts if b["type"] == "message"]
|
||||
assert len(message_broadcasts) == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_links_raw_packet_to_dm_message(self, test_db, captured_broadcasts):
|
||||
"""create_dm_message_from_decrypted links raw packet to message."""
|
||||
from app.decoder import DecryptedDirectMessage
|
||||
from app.packet_processor import create_dm_message_from_decrypted
|
||||
|
||||
packet_id, _ = await RawPacketRepository.create(b"link_test_dm", 1700000000)
|
||||
|
||||
decrypted = DecryptedDirectMessage(
|
||||
timestamp=1700000000,
|
||||
flags=0,
|
||||
message="Link test DM",
|
||||
dest_hash="fa",
|
||||
src_hash="a1",
|
||||
)
|
||||
|
||||
broadcasts, mock_broadcast = captured_broadcasts
|
||||
|
||||
with patch("app.packet_processor.broadcast_event", mock_broadcast):
|
||||
await create_dm_message_from_decrypted(
|
||||
packet_id=packet_id,
|
||||
decrypted=decrypted,
|
||||
their_public_key=self.A1B2C3_PUB,
|
||||
our_public_key=self.FACE12_PUB,
|
||||
received_at=1700000001,
|
||||
outgoing=False,
|
||||
)
|
||||
|
||||
# Verify packet is marked decrypted
|
||||
undecrypted = await RawPacketRepository.get_undecrypted(limit=100)
|
||||
packet_ids = [p.id for p in undecrypted]
|
||||
assert packet_id not in packet_ids
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dm_includes_path_in_broadcast(self, test_db, captured_broadcasts):
|
||||
"""create_dm_message_from_decrypted includes path in broadcast when provided."""
|
||||
from app.decoder import DecryptedDirectMessage
|
||||
from app.packet_processor import create_dm_message_from_decrypted
|
||||
|
||||
packet_id, _ = await RawPacketRepository.create(b"path_test_dm", 1700000000)
|
||||
|
||||
decrypted = DecryptedDirectMessage(
|
||||
timestamp=1700000000,
|
||||
flags=0,
|
||||
message="Path test DM",
|
||||
dest_hash="fa",
|
||||
src_hash="a1",
|
||||
)
|
||||
|
||||
broadcasts, mock_broadcast = captured_broadcasts
|
||||
|
||||
with patch("app.packet_processor.broadcast_event", mock_broadcast):
|
||||
await create_dm_message_from_decrypted(
|
||||
packet_id=packet_id,
|
||||
decrypted=decrypted,
|
||||
their_public_key=self.A1B2C3_PUB,
|
||||
our_public_key=self.FACE12_PUB,
|
||||
received_at=1700000001,
|
||||
path="aabbcc", # Path through 3 repeaters
|
||||
outgoing=False,
|
||||
)
|
||||
|
||||
message_broadcasts = [b for b in broadcasts if b["type"] == "message"]
|
||||
assert len(message_broadcasts) == 1
|
||||
|
||||
broadcast = message_broadcasts[0]["data"]
|
||||
assert broadcast["paths"] is not None
|
||||
assert len(broadcast["paths"]) == 1
|
||||
assert broadcast["paths"][0]["path"] == "aabbcc"
|
||||
assert broadcast["paths"][0]["received_at"] == 1700000001
|
||||
|
||||
|
||||
class TestDMDecryptionFunction:
|
||||
"""Test the DM decryption function with real crypto."""
|
||||
|
||||
# Same test data
|
||||
FACE12_PRIV = bytes.fromhex(
|
||||
"58BA1940E97099CBB4357C62CE9C7F4B245C94C90D722E67201B989F9FEACF7B"
|
||||
"77ACADDB84438514022BDB0FC3140C2501859BE1772AC7B8C7E41DC0F40490A1"
|
||||
)
|
||||
# FACE12 public key - derived via scalar × basepoint, NOT the last 32 bytes!
|
||||
# The last 32 bytes (77AC...) are the signing prefix, not the public key.
|
||||
FACE12_PUB = bytes.fromhex("FACE123334789E2B81519AFDBC39A3C9EB7EA3457AD367D3243597A484847E46")
|
||||
A1B2C3_PUB = bytes.fromhex("a1b2c3d3ba9f5fa8705b9845fe11cc6f01d1d49caaf4d122ac7121663c5beec7")
|
||||
FULL_PACKET = bytes.fromhex(
|
||||
"0900FAA1295471ADB44A98B13CA528A4B5C4FBC29B4DA3CED477519B2FBD8FD5467C31E5D58B"
|
||||
)
|
||||
EXPECTED_MESSAGE = "Hello there, Mr. Face!"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_full_dm_decryption_pipeline(self, test_db, captured_broadcasts):
|
||||
"""Test complete DM decryption from raw packet through to stored message."""
|
||||
from app.decoder import try_decrypt_dm
|
||||
from app.packet_processor import create_dm_message_from_decrypted
|
||||
|
||||
# Store the raw packet
|
||||
packet_id, _ = await RawPacketRepository.create(self.FULL_PACKET, 1700000000)
|
||||
|
||||
# Decrypt the packet
|
||||
decrypted = try_decrypt_dm(
|
||||
self.FULL_PACKET,
|
||||
self.FACE12_PRIV,
|
||||
self.A1B2C3_PUB,
|
||||
our_public_key=self.FACE12_PUB,
|
||||
)
|
||||
|
||||
assert decrypted is not None
|
||||
assert decrypted.message == self.EXPECTED_MESSAGE
|
||||
|
||||
# Determine direction (src_hash = a1 matches A1B2C3, so it's inbound)
|
||||
outgoing = decrypted.src_hash == format(self.FACE12_PUB[0], "02x")
|
||||
assert outgoing is False # This is an inbound message
|
||||
|
||||
broadcasts, mock_broadcast = captured_broadcasts
|
||||
|
||||
# Create the message
|
||||
with patch("app.packet_processor.broadcast_event", mock_broadcast):
|
||||
msg_id = await create_dm_message_from_decrypted(
|
||||
packet_id=packet_id,
|
||||
decrypted=decrypted,
|
||||
their_public_key=self.A1B2C3_PUB.hex(),
|
||||
our_public_key=self.FACE12_PUB.hex(),
|
||||
received_at=1700000000,
|
||||
outgoing=outgoing,
|
||||
)
|
||||
|
||||
assert msg_id is not None
|
||||
|
||||
# Verify the message is stored correctly
|
||||
messages = await MessageRepository.get_all(
|
||||
msg_type="PRIV", conversation_key=self.A1B2C3_PUB.hex().lower(), limit=10
|
||||
)
|
||||
assert len(messages) == 1
|
||||
assert messages[0].text == self.EXPECTED_MESSAGE
|
||||
assert messages[0].outgoing is False
|
||||
|
||||
# Verify raw packet is linked
|
||||
undecrypted = await RawPacketRepository.get_undecrypted(limit=100)
|
||||
assert packet_id not in [p.id for p in undecrypted]
|
||||
@@ -96,6 +96,88 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079 },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475 },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829 },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211 },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184 },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790 },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374 },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574 },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820 },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271 },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572 },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557 },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422 },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928 },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302 },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909 },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780 },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320 },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300 },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013 },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584 },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.1"
|
||||
@@ -325,6 +407,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/9a/79e6623e3b65b2ecf3294d3bb14aae74ea529da30d8ff5db1f9cb19c0aac/pycayennelpp-2.4.0-py3-none-any.whl", hash = "sha256:a3e69ea4da9e2971a44d1e275d5555617b2345eea752325f28c0356f64901d62", size = 10417 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.23"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycryptodome"
|
||||
version = "3.23.0"
|
||||
@@ -516,6 +607,41 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pynacl"
|
||||
version = "1.6.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/79/0e3c34dc3c4671f67d251c07aa8eb100916f250ee470df230b0ab89551b4/pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594", size = 390064 },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/1c/23a26e931736e13b16483795c8a6b2f641bf6a3d5238c22b070a5112722c/pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0", size = 809370 },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/74/8d4b718f8a22aea9e8dcc8b95deb76d4aae380e2f5b570cc70b5fd0a852d/pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9", size = 1408304 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/73/be4fdd3a6a87fe8a4553380c2b47fbd1f7f58292eb820902f5c8ac7de7b0/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574", size = 844871 },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/ad/6efc57ab75ee4422e96b5f2697d51bbcf6cdcc091e66310df91fbdc144a8/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634", size = 1446356 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b7/928ee9c4779caa0a915844311ab9fb5f99585621c5d6e4574538a17dca07/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88", size = 826814 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/a9/1bdba746a2be20f8809fee75c10e3159d75864ef69c6b0dd168fc60e485d/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14", size = 1411742 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/2f/5e7ea8d85f9f3ea5b6b87db1d8388daa3587eed181bdeb0306816fdbbe79/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444", size = 801714 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/ea/43fe2f7eab5f200e40fb10d305bf6f87ea31b3bbc83443eac37cd34a9e1e/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b", size = 1372257 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/54/c9ea116412788629b1347e415f72195c25eb2f3809b2d3e7b25f5c79f13a/pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145", size = 231319 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/04/64e9d76646abac2dccf904fccba352a86e7d172647557f35b9fe2a5ee4a1/pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590", size = 244044 },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/33/7873dc161c6a06f43cda13dec67b6fe152cb2f982581151956fa5e5cdb47/pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2", size = 188740 },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174 },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859 },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101 },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-core"
|
||||
version = "12.1"
|
||||
@@ -736,6 +862,7 @@ dependencies = [
|
||||
{ name = "meshcore" },
|
||||
{ name = "pycryptodome" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "pynacl" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
|
||||
@@ -763,6 +890,7 @@ requires-dist = [
|
||||
{ name = "meshcore" },
|
||||
{ name = "pycryptodome", specifier = ">=3.20.0" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.0.0" },
|
||||
{ name = "pynacl", specifier = ">=1.5.0" },
|
||||
{ name = "pytest", marker = "extra == 'test'", specifier = ">=8.0.0" },
|
||||
{ name = "pytest-asyncio", marker = "extra == 'test'", specifier = ">=0.24.0" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.32.0" },
|
||||
|
||||
Reference in new issue
Block a user